Handmade Hero»Forums»Code
107 posts
why dont I have to use q.y1 from stbtt_GetBakedQuad(). and scale character
Edited by C_Worm on Reason: Initial post
I have a printText() function that renders some text.

Im wondering why i didn't have to use the q.y1 value returned from stbtt_GetBakedQuad()

I have this scale paramter that i wanted to scale the character while keeping the x/y pos.

if i scale anything other than 1.0f some chars gets displaced ( mostly lower case characters ).
______________________________________________________



I copied GetBakedQuad() definition in the printText() function for help only.



loading FontBitmap in main:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
	stbtt_bakedchar BCD[96] = {};
	unsigned char fontBuffer[30000000] = {};
	const int fontMapWidth  = 3500;
	const int fontMapHeight = 2080;
	float pixelHeight 	= 256.0f;
	unsigned char fontData[fontMapWidth*fontMapHeight] = {};

//	FILE *hFontFile = fopen("C:\\windows\\fonts\\times.ttf", "rb");
	FILE *hFontFile = fopen("C:\\windows\\fonts\\cour.ttf", "rb");
//	FILE *hFontFile = fopen("C:\\CJ_FONTS\\FFF_Tusj.ttf", "rb");
	if(!hFontFile)
	{
		printf("fail open file\n");
	}

	fread(fontBuffer, 1, sizeof(fontBuffer), hFontFile);
	stbtt_BakeFontBitmap(fontBuffer, 0, pixelHeight, fontData, fontMapWidth, fontMapHeight, 32, 126, BCD);

	fclose(hFontFile)




printText():

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
textIndexRange printText(stbtt_bakedchar *BCD, GameBuffer *gameBuffer, int *index, float x, float y, float in_scale, char *text, CJ_V4f vec4)
{

	float advance = 0;
	float baseLineBreak = 0;
	float baseLine = 0;
	float result_X, result_Y, result_W, result_H;
	float scale = in_scale;
	int fontMapWidth = 3500;
	int fontMapHeight = 2080;

	textIndexRange tir = {};
	tir.start = *index;

	while(*text)
	{
		float pX = 0;
		float pY = 0;
		int character = *text - 32;
		stbtt_aligned_quad q = {};

		stbtt_GetBakedQuad(BCD, fontMapWidth, fontMapHeight, character, &pX, &pY, &q, 1); 

		//typedef struct
		//{
		//   float x0,y0,s0,t0; // top-left
		//   float x1,y1,s1,t1; // bottom-right
		//} stbtt_aligned_quad;
		//
		//STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph,  // same data as above
		//                               int char_index,             // character to display
		//                               float *xpos, float *ypos,   // pointers to current position in screen pixel space
		//                               stbtt_aligned_quad *q,      // output: quad to draw
		//                               int opengl_fillrule);       // true if opengl fill rule; false if DX9 or earlier
		// Call GetBakedQuad with char_index = 'character - first_char', and it
		// creates the quad you need to draw and advances the current position.
		//
		// The coordinate system used assumes y increases downwards.
		//
		// Characters will extend both above and below the current position;
		// see discussion of "BASELINE" above.
		//
		// It's inefficient; you might want to c&p it and optimize it.

		
		if(*text == (int)' ')
		{
			q.x1 = 50.5f;
		}

		if(*index > TEXT_LAST)
		{
			MessageBox(0, "Need to increase number of characters (TEXT_LAST)", "MAX TEXT INDEX REACHED", MB_OK);
			*text = 0;
		}

		// baseLineBreak sets the baseline for the textstring according to the first Character in the string
		if(baseLineBreak == 0)
		{
			baseLine = (y - q.y0);
			baseLineBreak++;
		}
		else
		{
		}

		// Dont scale the X-Value or Y-Value of the first character
		result_X = (x + advance);
		result_Y = (baseLine + q.y0);
		result_W = scale * (q.x1);
		result_H = scale * (-q.y0);

		gameBuffer[*index].setSolidColor(1.0f, 1.0f, 1.0f, 1.0f); 
		gameBuffer[*index].createRect(result_X, result_Y, result_W, result_H);
		gameBuffer[*index].setTextureCoordinates(q.s0, q.t0, q.s1, q.t1);
		gameBuffer[*index].setSolidColor(vec4.r, vec4.g, vec4.b, vec4.a);
		
		advance += (scale * q.x1);   // advance the x-value for the next character
		*index += 1;	   // increases the gameBuffer Index
		text++;
	}

	tir.end = (*index) - 1;

	return tir;
}
Simon Anciaux
1341 posts
why dont I have to use q.y1 from stbtt_GetBakedQuad(). and scale character
When you use baked quad I think you're not supposed to try to align things yourself.

You should pass a pointer to the x and y position you want the text to start (the parameters xpos, ypos) and let the lib increment them. And use only the returned quad information to draw (the content of q) not xpos or ypos.

The returned quad contains a tight rectangle for the character, meaning it is the smallest area that contains pixel for that character. So it can start below or above the base line (current y position) and before or after the current x position.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* From the documentation. */
#define STB_TRUETYPE_IMPLEMENTATION  // force following include to generate implementation
#include "stb_truetype.h"

unsigned char ttf_buffer[1<<20];
unsigned char temp_bitmap[512*512];

stbtt_bakedchar cdata[96]; // ASCII 32..126 is 95 glyphs
GLuint ftex;

void my_stbtt_initfont(void)
{
   fread(ttf_buffer, 1, 1<<20, fopen("c:/windows/fonts/times.ttf", "rb"));
   stbtt_BakeFontBitmap(ttf_buffer,0, 32.0, temp_bitmap,512,512, 32,96, cdata); // no guarantee this fits!
   // can free ttf_buffer at this point
   glGenTextures(1, &ftex);
   glBindTexture(GL_TEXTURE_2D, ftex);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, 512,512, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap);
   // can free temp_bitmap at this point
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}

void my_stbtt_print(float x, float y, char *text)
{
   // assume orthographic projection with units = screen pixels, origin at top left
   glEnable(GL_TEXTURE_2D);
   glBindTexture(GL_TEXTURE_2D, ftex);
   glBegin(GL_QUADS);
   while (*text) {
      if (*text >= 32 && *text < 128) {
         stbtt_aligned_quad q;
         stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9
         glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0);
         glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0);
         glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1);
         glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1);
      }
      ++text;
   }
   glEnd();
}


You shouldn't use a scaling factor with GetBakedQuad as it will probably not give you a good visual result. Its probably better to generate a second baked font texture with the desired scale.

But if you must, you need to scale things around the origin of the character:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/* Not tested, this could be totally wrong. */
while ( ... ) {
    float previous_x = x;
    float previous_y = y;
    stbtt_GetBakedQuad( ..., &x, &y, &q, 1 );
    float x0 = previous_x + ( q.x0 - previous_x ) * scale;
    float x1 = previous_x + ( q.x1 - previous_x ) * scale;
    float y0 = previous_y + ( q.y0 - previous_y ) * scale;
    float y1 = previous_y + ( q.y1 - previous_y ) * scale;
    x = previous_x + ( x - previous_x ) * scale;
    y = previous_y + ( y - previous_y ) * scale;
    /* Draw using x0, x1, y0, y1. */
}