In this tutorial we will see how to use the Measurement commands of GLC and what they are intended for. It is assumed that you have a basic knowledge of the GLC API (the First Steps tutorial is a good place to start with) and that you have read the Glyph Conventions so that you know what a bounding box and a baseline are.
Basically, in order to display some text an application have to go through the following stages :
- The text layout determines where and how the text must be layed out : this task can be as simple as computing the location of the word "Score" so that it appears in the upper left corner of the screen or as complex as laying out a complete document with titles, fonts, styles, paragraphs and chapters; just like word processors do.
- The text rendering draws the text that has been layed out in the previous stage : the application determines which glyphs of which fonts it should use in order to obtain the desired result, eventually converts data into graphic primitives, and finally sends the required commands to the output device which can be either a screen or a printer.
The GLC API only covers the later stage and provides no layout services : it has no concept of sentence, line break or paragraph. GLC only manipulates strings that it views as a succession of characters that has to be rendered on the screen in a row. So in order to be able to handle complex text layout the client application has to decompose text in several strings that can be rendered by GLC. This can be done by rendering engines like Pango or ICU.
For instance, when a sentence is larger than the width of the output device, it has to be broken into two or more lines before being rendered so that it can be read. In order to determine where to put the line break(s), the layout engine needs to know the width of the output device as well as the size of the characters used to render the text (actually it also needs to understand the exact structure of the sentence to prevent the line break to take place in the middle of a word or between a word and a comma for example). This is where Measurement commands take place.
General layout management is far beyond the scope of this tutorial but you can find some more informations at the Unicode web site.
In this tutorial we will see how to render the string Hello world! with a bounding box around the capital letter "H" and another bounding box around the 4 last letters of the word "Hello" as shown in the image below.
Figure 1 - The result of the tutorial
The code presented in the First Steps tutorial is the starting point of the current example. Notice however that it has been slightly changed : a call to the function glcRotate() has been added in order to rotate the text of 10 counterclockwise. The call to glcAppendCatalog() has also been commented out : it is not needed if Fontconfig has been carefully installed (which is the case on most of the recent Linux distributions).
GLC defines the geometry of a bounding box by the coordinates [ xlb ylb xrb yrb xrt yrt xlt ylt ] of its corners. Each point is given in 2D coordinates : z is always assumed to be 0 and w to be 1. They are object coordinates which means that they are affected by both the GL_MODELVIEW and the GL_PROJECTION transformations. Moreover if the value of the variable GLC_RENDER_STYLE is GLC_BITMAP then each point is transformed by the 2x2 GLC_BITMAP_MATRIX.
Figure 2 - Metrics coordinates
Since there are 4 corners, we need to define an array of 8 floating point numbers which will contain the (x, y) coordinates of the bounding box.
GLfloat bbox[8] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
The bounding box of the letter "H" is stored in the bbox array by calling glcGetCharMetric() :
GLC assumes that the origin of the layout is (0,0) but in our example the string "Hello world!" has been rendered from the coordinates (50,50) so we need to translate the bounding box before it is rendered :
glTranslatef(50.f, 50.f, 0.f);
glBegin(GL_LINE_LOOP);
for (i = 0; i < 4; i++)
glVertex2fv(&bbox[2*i]);
glEnd();
- Note:
- We do not need to rotate the bounding box of 10 since it has already been done by GLC. If we were using a GLC_RENDER_STYLE that were not GLC_BITMAP, we would need to add a call to
glRotatef(0., 0., 1., 10.);
We now want to draw the bounding box of the 4 last letters of the word Hello. Although this can be achieved by computing the bounding box of the bounding boxes of each character, this would be rather tedious, so GLC provides a function that does the work for us : glcMeasureString().
Actually, glcMeasureString() is designed to measure the whole string metrics and can also store on demand the metrics of each character of the string. Here we only want the "ello" string metrics so we set the first parameter of glcMeasureString() to GL_FALSE :
At this stage, the metrics are computed and stored in the GLC state machine so we now have to call glcGetStringMetric() to get the bounding box of the string :
If we would render the bounding box now, it would be drawn at the wrong location since we do not have taken the advance of the character "H" into account :
Figure 3 - The bounding box of 'ello' is misplaced
Hence we need first to translate the bounding box of the advance of the character "H". Since the advance of a character is equal to the length of its baseline, the translation must be equal to the vector according to the notations of the Figure 2.
- Note:
- It must be stressed that this formula assumes that the string is rendered in left-to-right order. If you intend to render strings in right-to-left order you must use the opposite of the vector defined above.
An array of 4 floating point numbers must be used in order to store the coordinates [ xl yl xr yr ] of the baseline : GLfloat baseline[4] = {0.f, 0.f, 0.f, 0.f};
Then the baseline of "H" is obtained by calling glcGetCharMetric() with the second parameter set to GLC_BASELINE. The advance of the character is then computed and added to the GL_MODELVIEW matrix :
glcGetCharMetric('H', GLC_BASELINE, baseline);
glTranslatef(baseline[2] - baseline[0], baseline[3] - baseline[1], 0.f);
Afterwards the bounding box of "ello" is rendered at the right place :
glBegin(GL_LINE_LOOP);
for (i = 0; i < 4; i++)
glVertex2fv(&bbox[2*i]);
glEnd();
In this tutorial, the basic concepts of character metrics available in GLC have been presented. Although the chosen example is somewhat artificial since there are few chances that you need to display the bounding boxes of a character or a string, it illustrates how the character metrics are handled by GLC and how they should be manipulated.
Character metrics are primarily intended for layout management, although this topic is far beyond the scope of this tutorial, the concepts exposed here are sufficient to handle basic cases like processing the location of a centered string on the screen.
Finally, a program that display the bounding boxes of a character and a string has been elaborated in this tutorial. Its listing is shown hereafter (the commands that bind a GL context to the current thread are not included) :
GLint ctx, myFont, i;
GLfloat bbox[8] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f};
GLfloat baseline[4] = {0.f, 0.f, 0.f, 0.f};
ctx = glcGenContext();
glcContext(ctx);
myFont = glcGenFontID();
glcNewFontFromFamily(myFont, "Palatino");
glcFontFace(myFont, "Bold");
glcFont(myFont);
glcScale(100.f, 100.f);
glcRotate(10.f);
glColor3f(1.f, 0.f, 0.f);
glRasterPos2f(50.f, 50.f);
glcRenderString("Hello world!");
glColor3f(0.f, 1.f, 1.f);
glcGetCharMetric('H', GLC_BOUNDS, bbox);
glTranslatef(50.f, 50.f, 0.f);
glBegin(GL_LINE_LOOP);
for (i = 0; i < 4; i++)
glVertex2fv(&bbox[2*i]);
glEnd();
glcGetCharMetric('H', GLC_BASELINE, baseline);
glTranslatef(baseline[2] - baseline[0], baseline[3] - baseline[1], 0.f);
glcMeasureString(GL_FALSE, "ello");
glcGetStringMetric(GLC_BOUNDS, bbox);
glBegin(GL_LINE_LOOP);
for (i = 0; i < 4; i++)
glVertex2fv(&bbox[2*i]);
glEnd();
|