2D Tutorial: Managing 2D Text

This step reviews how to use various text attributes to improve the quality of text geometry.

soccer_field_text.gif

Customizing Text Attributes

Alignment

You may have noticed that the text denoting the field measurements is not nicely centered between the measurement lines. This is due to the fact that, by default, the system arranges your string so that the 'middle baseline' of the group of characters will land where your 'reference point' x-y-z specifies. (Insert_Text contains some additional information in this regard.) The 'baseline' of text is the lower line that text would be drawn on if you were writing on a a ledger pad. We can instruct the system to have the overall center of the text string be located at the 'reference point', by calling Set_Text_Alignment("**") Let's update the various "text" segments in HSoccerModel::Init() to include this call. Let's look at the 'goals' text segment as an example:

      seg_key = HC_KOpen_Segment("goals");
        vlist_add_last(m_pLayers, (void*)seg_key);
        HSoccerModel::InsertFilledRectangle(".", -0.75, -0.15, -0.70, 0.15);
        HSoccerModel::InsertFilledRectangle(".", 0.75, -0.15, 0.70, 0.15);
        HC_Set_Visibility("faces = on");
        HC_Set_Color("faces = red");

        HC_Open_Segment("text");
          
          HC_Set_Text_Alignment("**");

          HC_Insert_Line(0.85, 0.15, 0.0, 0.85, 0.05, 0.0);
          HC_Insert_Line(0.85, -0.15, 0.0, 0.85, -0.05, 0.0);
          HC_Insert_Line(0.80, 0.15, 0.0, 0.90, 0.15, 0.0);
          HC_Insert_Line(0.80, -0.15, 0.0, 0.90, -0.15, 0.0);
          HC_Insert_Text(0.85, 0.0, 0.0, "6m");
        HC_Close_Segment();

      HC_Close_Segment();

Rerun the application after your change and note that text is now nicely centered between the measurement lines.

Font Name

The HBaseView class' Init method sets the starting font to be 'sans serif', so let's change it to something more interesting. We can change the font by calling Set_Text_Font ("name = [font_name]") HOOPS/3dGS supports TrueType fonts as well as system (GDI or X11) fonts. However, before choosing a font, you should of course find out what fonts are actually available on the system. This is achieved by calling Begin_Font_Search and enumerating through the list of fonts. HOOPS/3dGS will search in the directories contained in the env/registry setting called HOOPS_FONT_DIRECTORY. The font directory(s) can also be programmatically set by calling Define_System_Options("font directory = [pathlist]") Let's set it programmatically, and the right place to set these system-wide options is typically the main application object's Init method (in this case, CSoccerApp::InitInstance)

BOOL CSoccerApp::InitInstance()
{
    .
    .
    .
    char fontDirectory[MAX_PATH+32];
    ::GetWindowsDirectory(fontDirectory, MAX_PATH);
    strcat(fontDirectory, "\\Fonts");
    char buf[MAX_PATH+64];
    sprintf(buf, "font directory = %s", fontDirectory);
    HC_Define_System_Options(buf);
    .
    .
    .
}

For our purposes, we'll cheat a little bit when changing the font name, and 'blindly' set the font to arial.ttf without doing a Begin_Font_Search enumeration. This is safe (knock on plastic) since we know that it's included in your \Windows\Fonts directory. Let's assume that the end-user may want to change the fonts that they are viewing, so we'll consider this a viewing attribute rather than an attribute of the model itself, and thus set the font name in HSoccerView::Init()


 

Scaling (Size)

Note that the text does not change size when zooming in and out. This is due to the size sub-attribute of the text, which is modified by calling Set_Text_Font("size = [size-spec]") The HBaseView::Init() method set the 'size' to be '12 pts'. Since 'points' are a fixed size, the text size does not change as you zoom in and out. If we want the text to scale, then we can pick a 'size-spec' that is not fixed. A good choice would be 'oru', or 'object relative units', which will cause the text to scale in relation to the measurement lines associated with it. Since the extends of the field are 1.5 units in object space, a reasonable value for the 'oru' is .05


Now try zooming in and out. Also try doing a zoom-by-area to get a closeup of one of the pieces of text. It's up to you to decide whether you want to text to scale while the user zooms in and out. You may want the end-user to be able to be able to always see all the text at the same size regardless of their zoom factor, in which case you wouldn't want the text to scale.

Transforms

Another attribute that can be set on text is the transforms suboption of of Set_Text_Font. When transforms is turned off, only the starting position of the text (as determined by the Text Alignment) is affected by the camera, the net modelling matrix, etc. After that, the text is drawn in the screen coordinate system, so it is always facing the screen. When transforms is turned on, the system subjects the text to transformations just like regular geometry. In other words, a "transformable" font is subject to scaling, rotations, perspective transformations, etc. The HBaseView::Init() method turns transforms off, thus text is being drawn in screen space. As we saw previously, setting the size attribute to 'oru' caused the text to scale relative to the coordinate system of the parent segment, but it was only scaling and not being fully transformed.

Since we have a 2d model, we don't want to turn transforms on. As a matter of fact, turning transforms on won't even have any noticeable affect unless you begin to orbit the camera (since the initial view of the model is non-transformed), and orbiting around a 'flat' 2d model isn't very useful! However, go ahead and experiment with orbiting the camera with transforms turned off and on.

 

Determining Text Extents

Calculating Extents of Existing Text

Let's place a tight-fitting box around the title text. We need to know the extents of the text string to achieve this, and the HOOPS/3dGS function Compute_Text_Extent does just that. Ideally, we would simply define this text at the time that we define the model inside HSoccerModel::Init(). However, there are 2 problems with this approach.

1. Compute_Text_Extent requires the context of a 'driver-instance' (segment), which in our application is the segment referred to by HBaseView::m_SceneKey. If we just call Compute_Text_Extent from within HBaseModel::Init, we'll get an error similar to: 'there is no active driver at or above the currently open segment' This is because the HBaseModel's 'model_key' segment is just a segment sitting out in the include library somewhere, and doesn't have any information above what driver segment is including it from up above. For the function call to succeed, it either needs to be called from within a segment that is 'directly' in a segment tree underneath a driver segment (and not simply 'included' by a driver segment), or one must call the 'Path' variant of the routine which takes a list of segment keys for context (and one of those keys must refer to a segment directly underneath a driver segment.)

2. A regular display update must already have been performed for Compute_Text_Extent to properly determine what the text size would be if it were drawn to the screen. Therefore, if the GUI window doesn't exist yet, the HC function will return an error. No active GUI window exists yet at the time of the call to HBaseModel::Init, so the call to the Text_Extent function would also fail for this reason.

The above requirements aren't really that onerous given the purpose of Compute_Text_Extent. Since it returns a view-dependent screen-space value, it only makes sense in the context of a window on the screen, and that in turn implies that there is a known driver-instance available.

Let's pick a place to perform the work after we know that the window has been mapped to the screen. In the MFC environment, everything is ready to go at the end of CSoccerView::OnInitialUpdate() We'll define a new method on our HBaseView object called HSoccerView::ProcessText and call it at the end of OnInitialUpdate()

void CSoccerView::OnInitialUpdate() 
{
    .
    .
    .
    ((HSoccerView *)m_pHView)->ProcessText();
}

Let's take a look at ProcessText. The first thing it does is call HBaseView::Update(), since an update hasn't been performed yet, and one is required for Compute_Text_Extent to produce correct results (as discussed in #2 above). Then it passes the keys of the segments in the path up to the 'driver-instance' segment, which is denoted by m_SceneKey. Since Compute_Text_Extent returns the extents of the the text in 'window' coordinates, we must first convert to 'object' coordinates and use those values to insert our text box. Compute_Coordinates does such conversion work, and since it also requires a driver-instance segment for context, we call it's Path variant.

Note: The code assumes that the transforms option of HC_Set_Text_Font is set to 'off'. (If transforms=on, HC_Compute_Text_Extent returns object space values instead of window space values, but the sample code below assumes window space.)

void HSoccerView::ProcessText()
{
    float xfrac, yfrac;

    HPoint win_ref_pt, obj_ref_pt;

    HC_KEY keys[2];
    keys[0] = GetModelKey();
    keys[1] = m_SceneKey;

    // call Update() so that the Compute fcns below have all the info they need to work properly
    Update();

    HC_Open_Segment_By_Key(GetModelKey());
      HC_Open_Segment("title");

        // calculate the extents of the title text
        HC_Compute_Text_Extent_By_Path(2, keys, "Sample Soccer Field", &xfrac, &yfrac);

        win_ref_pt.x = xfrac;
        win_ref_pt.y = yfrac;
        win_ref_pt.z = 0.0;

        // convert the coordinates into object space
        HC_Compute_Coordinates_By_Path(2, keys, "outer window", &win_ref_pt, "object", &obj_ref_pt);
 
        // base the border rectangle off of the object space coordinates computed above
        HUtility::InsertRectangle(".", -obj_ref_pt.x, 0.8 + obj_ref_pt.y, obj_ref_pt.x, .8 - obj_ref_pt.y);

      HC_Close_Segment();
    HC_Close_Segment();
}

We're not done yet. Notice that the text is located in the upper-half of the box. This is because the default text alignment attribute uses the text's baseline as the reference point. To use the horizontal centerline as the 'reference', we need to call Set_Text_Alignment("*") at the location where the text was originally inserted:

void HSoccerModel::Init()
{
    .
    .

      HC_Open_Segment("title");
        HC_Insert_Text(0.0, 0.8, 0.0, "Sample Soccer Field");
        HC_Set_Text_Alignment("*");
      HC_Close_Segment();
    .
    .
}

 

Making Text Fit within a Specified Area

Calculating the extents of a piece of text can be useful, but since it returns a view-dependent, screen-space value that becomes invalid if the text shrinks or grows, it may not always be what you want (as soon as you change the font size, the box around the text would no longer be correct). Instead you may want to specify a region and have text automatically size to fit into that region. Defining a text region can be done by 'opening up' a piece of text geometry (by accessing it's key), and calling Set_Text_Region.

Let's insert a piece of text in the lower left hand corner that denotes the author of the drawing, and specify a region for the text. We can safely do this in HSoccerModel::Init()

    HC_KEY author_text_key;
    HPoint line[2];

    line[0].Set(0.0, -0.8, 0.0);
    line[1].Set(1.5, -0.8, 0.0);

    HC_Open_Segment_By_Key(GetModelKey());
      .
      .
      HC_Open_Segment("author");
        author_text_key = HC_KInsert_Text(0.8, -0.8, 0.0, "Author:  David Beckham");
        HC_Open_Geometry(author_text_key);
          HC_Set_Text_Region(2, line, "");
        HC_Close_Geometry();
      HC_Close_Segment();
      .
      .
    HC_Close_Segment();

Exercise: Modify the insertion code for the title text to use HOOPS/3dGS 'multiline' text support, by placing the phrase "(Not to Scale)" underneath of 'Sample Soccer Field'. Multiline text is supported by placing a return character "\n" in the text string. The line spacing is controlled using the suboption line spacing in Set_Text_Font.

All the text properties discussed above, along with others, are reveiwed in Section 2.4 of the HOOPS/3dGS Programming Guide