Code:text-quads.h: Difference between revisions

From the change wiki
(Created page with "<syntaxhighlight lang="c"> // text-quads.h // Functions for drawing text in OpenGL, using textured quads. #define TQ_TEXTURE_WIDTH 1004 #define TQ_TEXTURE_HEIGHT 19 #define TQ_TEXTURE_FILENAME "font.data-uint8-1004x19" // DEPENDENCY: This texture file. /* Copyright 2022, Elie Goldman Smith This program is FREE SOFTWARE: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either ver...")
 
No edit summary
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
C header for drawing text in OpenGL.
Depends on font bitmap: [[:File:font.data-uint8-1004x19]]
==Code==
<syntaxhighlight lang="c">
<syntaxhighlight lang="c">
// text-quads.h
// text-quads.h

Latest revision as of 09:40, 30 November 2022

C header for drawing text in OpenGL. Depends on font bitmap: File:font.data-uint8-1004x19

Code

// text-quads.h
// Functions for drawing text in OpenGL, using textured quads.
#define TQ_TEXTURE_WIDTH 1004
#define TQ_TEXTURE_HEIGHT 19
#define TQ_TEXTURE_FILENAME "font.data-uint8-1004x19" // DEPENDENCY: This texture file.

/*
 Copyright 2022, Elie Goldman Smith

 This program is FREE SOFTWARE: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include <string.h>
typedef struct { float x, y, tx, ty;                 } TQ_Vertex;  // XXX: maybe use 16-bit snorm instead of 32-bit float?
typedef struct { TQ_Vertex *v; int n; int isComplete;} TQ_Drawable;// XXX: in future versions, isComplete might get renamed to 'flags'
TQ_Vertex _tq_alphabet[1024]; // 256 quads (one for every char value)
GLuint    _tq_texture;



void tq_init() {
 // load font bitmap
 size_t SIZE = TQ_TEXTURE_WIDTH*TQ_TEXTURE_HEIGHT;
 unsigned char *bitmap = malloc(SIZE);
 if (!bitmap) return; // TODO: handle error better
 FILE *f = fopen(TQ_TEXTURE_FILENAME,"r");
 if (!f) { perror(TQ_TEXTURE_FILENAME); return; } // TODO: handle error better
 int n = fread(bitmap, 1, SIZE, f);
 if (n != SIZE) { printf("read %d chars\n", n); fclose(f); return; } // TODO: handle error better
 fclose(f);

 // create font texture
 glGenTextures(1, &_tq_texture);
 glEnable(GL_TEXTURE_2D);
 glActiveTexture(GL_TEXTURE0);
 glBindTexture(GL_TEXTURE_2D, _tq_texture);
 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TQ_TEXTURE_WIDTH, TQ_TEXTURE_HEIGHT-1, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, &bitmap[TQ_TEXTURE_WIDTH]); // we skip first row of bitmap because that's the indicator for where each character is.  XXX: maybe use a smaller internalformat? we really only need 4-bit monochrome
 glGenerateMipmap(GL_TEXTURE_2D); // XXX: maybe get rid of mipmaps idk
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

 // create the alphabet
 memset(_tq_alphabet, 0, 1024*sizeof(TQ_Vertex));
 int c = 32; // bitmapped font starts at char 32 (space character)
 int lasti = 0;
 for (int i = 0; i<=TQ_TEXTURE_WIDTH; i++) {
  if (bitmap[i]||i==TQ_TEXTURE_WIDTH) {
   float x = (float)(i-lasti)/TQ_TEXTURE_HEIGHT;
   float tx =         (i-0.5f)/TQ_TEXTURE_WIDTH;
   float lasttx = (lasti+0.5f)/TQ_TEXTURE_WIDTH;
   lasti = i;
   _tq_alphabet[c*4  ].x = x;
   _tq_alphabet[c*4  ].y = -1.f;
   _tq_alphabet[c*4  ].tx= tx;
   _tq_alphabet[c*4  ].ty= 1;
   _tq_alphabet[c*4+1].x = x;
   _tq_alphabet[c*4+1].y = 0.f;
   _tq_alphabet[c*4+1].tx= tx;
   _tq_alphabet[c*4+1].ty= 0;
   _tq_alphabet[c*4+2].x = 0;
   _tq_alphabet[c*4+2].y = 0.f;
   _tq_alphabet[c*4+2].tx= lasttx;
   _tq_alphabet[c*4+2].ty= 0;
   _tq_alphabet[c*4+3].x = 0;
   _tq_alphabet[c*4+3].y = -1.f;
   _tq_alphabet[c*4+3].tx= lasttx;
   _tq_alphabet[c*4+3].ty= 1;
   c++;
  }
 }
 free(bitmap);
}



void tq_mode() {
 glEnable(GL_TEXTURE_2D);
 glActiveTexture(GL_TEXTURE0);
 glBindTexture(GL_TEXTURE_2D, _tq_texture);
 glEnable(GL_BLEND);
 glBlendFunc(GL_ONE, GL_ONE);
 glEnableClientState(GL_VERTEX_ARRAY);
 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}



void tq_draw(TQ_Drawable td) {
 glVertexPointer  (2, GL_FLOAT, sizeof(TQ_Vertex), &td.v[0].x);
 glTexCoordPointer(2, GL_FLOAT, sizeof(TQ_Vertex), &td.v[0].tx);
 glDrawArrays(GL_QUADS, 0, td.n);
}



TQ_Drawable tq_centered_fitted(const char *str, float width, float height) { // Nominal font size is 1. Actual font size may vary slightly to fit.
 TQ_Drawable td; td.n=0; td.v=NULL; td.isComplete=0;
 if (!str   )return td; // null string
 if (!str[0])return td; // blank string
 td.v = malloc((strlen(str)+3+1) * 4 * sizeof(TQ_Vertex));
 if (!td.v  )return td; // malloc error
 const float SPACING = 0.05f;
 const char *p = str;
 float       x=0, y=0;
 const char *ls = p; // line start
 const char *le = p; // line end
 float       lw = 0; // line width
 float       widest=0;// widest line
 while (*p && y > -height+0.99f) {
  // determine where the next line should end...
  lw = x = 0;
  while (1) {
   if (*p=='\0'||*p=='\n'){ le=p; lw=x; break; }
   if (isspace(*p))       { le=p; lw=x;        }
   x += _tq_alphabet[*(unsigned char*)p * 4].x + SPACING;
   if (x >= width && lw>0)  {       break; }
   if (*p=='-' || *p==','){ le=p; lw=x;        }
   p++;
  } lw -= SPACING;
  // generate quads of that line...
  if (lw <= width) { // usual case
   x = -0.5f * lw;
   if (widest<lw) widest=lw;
   for (p=ls; p<=le; p++) {
    int base = *(unsigned char*)p * 4;
    if (*p != ' ') { // skipping spaces is an optimization
     for (int i=0;i<4;i++) {
      td.v[td.n] = _tq_alphabet[base+i];
      td.v[td.n].x += x;
      td.v[td.n].y += y;
      td.n++;
     }
    } x += _tq_alphabet[base].x + SPACING;
   } y -= 1.f;
  }
  else { // case where line is one word and too wide: shrink
   x = -0.5f * width;
   widest = width;
   float scale = width/lw;
   float spacing = SPACING*scale;
   for (p=ls; p<=le; p++) {
    int base = *(unsigned char*)p * 4;
    if (*p != ' ') {
     for (int i=0;i<4;i++) {
      td.v[td.n] = _tq_alphabet[base+i];
      td.v[td.n].x *= scale;
      td.v[td.n].x += x;
      td.v[td.n].y *= scale;
      td.v[td.n].y += y;
      td.n++;
     }
    } x += _tq_alphabet[base].x*scale + spacing;
   } y -= scale;
  }
  if (*le == '\0') { td.isComplete = 1; break; }
  ls = p = ++le;
 }
 // expand text if small
 if (widest < width && y > -height) {
  float sx = width/widest;
  float sy = -height/y;
  float scale = sx<sy?sx:sy;
  for (int i=0; i<td.n; i++) {
   td.v[i].x *= scale;
   td.v[i].y *= scale;
  } y *= scale;
 }
 // add ellipsis if text didn't all fit
 if (*le) {
  int base = 4 * (unsigned char)'.';
  for (int h=0;h<3;h++) {
   x = 0.5f*lw + h * (_tq_alphabet[base].x + 0.02f);
   for (int i=0;i<4;i++) {
    td.v[td.n] = _tq_alphabet[base+i];
    td.v[td.n].x += x;
    td.v[td.n].y += y + 1.f;
    td.n++;
   }
  }
 }
 // center vertically
 y *= 0.5f; for (int i=0; i<td.n; i++) td.v[i].y -= y;
 // done
 return td;
}



void tq_delete(TQ_Drawable *td) {
 free(td->v); td->v = NULL;
 td->n = 0; td->isComplete = 0;
}



void tq_done() {
 glDeleteTextures(1, &_tq_texture);
}