Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Open Source

A Portable Font Specification


MAR95: A Portable Font Specification

A Portable Font Specification

Here's how one cross-platform tool solves the problem

Ronald G. White and John Biard

Ron is a consultant currently working with XVT Software on its next release. He can be reached at [email protected] or on CompuServe at 71500,3566. John is a member of engineering at XVT Software and the primary designer of the font code. He can be reached at [email protected].


When it comes to portability, fonts present a variety of problems. For one thing, available fonts differ from platform to platform. The Macintosh comes with fonts such as Chicago, Geneva, Monaco, and New York. Windows, on the other hand, has Arial, Courier, Serif, and Times New Roman. Moreover, thousands of fonts are available from third-party vendors. To further complicate matters, it is even possible to uninstall original fonts. The end result is that you can make very few assumptions about what fonts will be available on any system.

Even if you are developing for a single GUI system, you can't assume that a particular font is available on the system on which your program runs. While it's safe to assume that at least one font is available (but not what that font might be), you cannot assume that the fonts available when your program is installed will be available later--or that new fonts may not be installed after your program is installed.

Furthermore, similar fonts may differ on different platforms or even on the same platform. If the font is implemented on two platforms as a bit-mapped font, the available sizes may not be the same. The font might be bit-mapped on one platform and scalable on another, making some sizes unavailable on the first platform. Also, at some sizes, there may be subtle differences in the appearance of the font--even if available on both platforms. Even on one platform, the same font family might be available from different companies in different formats such as bit-mapped, TrueType, or PostScript, with significant differences in appearance or available attributes.

Nor are fonts specified in the same way on different platforms. The Macintosh represents a font by a family name, a font size, and some combination of the font styles bold, italic, outline, and shadow. X Window-based systems add more attributes by specifying foundry, weight, width, spacing, and more. Also, fonts have different physical implementations and internal formats on different systems and from different vendors. Unfortunately, no standard has emerged for specifying fonts across different platforms.

What Developers Want

To develop for multiple platforms, you need a standard way to specify fonts such that they will have the same characteristics both across platforms and between the screen and printers on the same platform. This is a hard nut to crack. Taking the lowest-common-denominator approach, only those font attributes available on all systems (font family, size, and a limited subset of styles) would be used. This would make many fonts (or attributes of fonts) unavailable on most systems, which precludes another feature developers want: access to all fonts, printer and screen, currently installed on a system.

Although some programs might be satisfied with a small subset of fonts, others (word-processing programs, for example) need to be able to pick and access any font the user has installed. Allowing fonts to be chosen via some limited set of attributes will not work. On the other hand, trying to use the superset of all possible attributes doesn't work either. Besides creating an unmanageably large set of attributes to work with, sometimes a font attribute that seems to be the same on two different platforms will, on closer inspection, differ in subtle ways. For example, on some systems the weight attribute is limited to two values (normal and bold) while on other systems new weights can be added by font designers. In addition, attributes are often not orthogonal and will interact with each other, or one attribute on one platform will only partly overlap with one or more attributes on a different platform. For example, sometimes italic is considered part of the font family name and other times it is merely an attribute of a font family.

Developers would also like to have font operations repeatable, both across platforms and on the same platform across invocations of a program. In a word processor, for example, if a user picks a font for part of a document and saves the document, the next time the document is retrieved, the user expects to get the same font. If the program runs on multiple platforms, the user expects, if not the same font, at least a similar font when the same document is viewed or printed on different platforms. The font information saved with the document must be complete enough to support this.

This leads to the question of font mapping. When a document is created on one system and moved to another, the fonts used on the first system may not exist on the second. It then becomes necessary to "map" the unavailable fonts to new fonts (that hopefully have similar characteristics). Font mapping is difficult, and, because different applications have different needs, it may not be achieved by a single method. You may need only a simple, built-in solution that is reasonably accurate, or you may want the ability to take over the font mapping with your own code.

One Approach to Font Handling

XVT supplies cross-platform development tools for GUI environments such as Windows, Macintosh, and Motif. We have grappled with the problem of portable fonts since our earliest releases. Our current release (4.0) represents a major rewrite of the font-handling code.

We approach the problem of portable fonts by using handles to opaque objects that represent fonts in application software, a technique that simplifies the handling of fonts inside a program. This approach also allows us to modify or add information in the font's internal representation without requiring changes to existing code. Internally, a structure is used to store the font information. Listing One shows a simplified version of this structure and the definition of the font object's handle, XVT_FNTID. The family field is a pointer to a string that can have any value; size is the font size in points; and style is a bit mask for style flags such as bold, italic, underline, outline, shadow, inverse, blink, and strikeout. This is not a complete list of all possible styles, and not all XVT font styles are available on all systems. This set of styles is a compromise that covers the more-common styles that can be represented by a single bit. We made this mask large enough for future expansion. native_desc is a string that gives enough information about the font, as represented natively on a particular system, to uniquely identify the font and locate it. (See the text box "Native Font Descriptors.") Is_print is a Boolean that indicates whether the font is available on a printer instead of the screen; unfortunately, on some systems the screen fonts and print fonts are different and have to be handled differently. Is_mapped is a Boolean that indicates whether this font has been mapped. Finally, plat_font_data points to platform-specific information.

The font specification has two major parts: the logical (portable) part and the native (nonportable) part. The family, size, and style fields are the logical font. These can be set independent of a particular system and are an attempt to represent a portable font specification. The native_desc field contains the nonportable information necessary to identify and locate a specific font on a specific platform. native_desc is an ASCII string with platform-specific fields that describes the physical font that exists on the system. Several font-mapping mechanisms establish the links between the logical font, the native font description, and the physical font.

A font is said to be "mapped" when the native-descriptor string has been set and the XVT font code has associated the font object with a particular physical font. Application code can force this to take place (via a function call with the font object as a parameter) if it needs to get information about the font that can only be obtained once the actual physical font is known. The font is also implicitly mapped by the XVT font code, if it is not already mapped, before it is used to display (or print) any text.

The mapping process is multistepped, controllable by the application, and guaranteed to result in a mapping to some (though not necessarily the best) physical font; see Figure 1. If the application has provided only the logical-font specification, the mapping process sets the native-descriptor string. The application can archive a persistent copy of the logical specification and the native descriptor between document sessions. If the application has provided a native-descriptor string for the font (as it might if it restored this font from a document), the mapping process uses the native font descriptor to do the mapping.

The XVT font code provides a default, "last chance" mapper that tries to do a reasonable job of mapping a font specification onto a physical font. It will, if necessary, do an unreasonable job, but it's guaranteed to map the font to some physical font. "Reasonable" is a relative term and is dependent on the application. Some applications might not care what font family is picked as long as the size is correct. Different applications may demand that the font family be exact or at least related (whatever that means), but not care about other style attributes. Writing a single mapper that would please everyone would be a formidable, if not impossible, task; we didn't try. Instead we provide several methods that allow applications to control the mapping.

One means for controlling the mapping is via a mapping table in the resource file. This table specifies a correspondence between the logical font (family, size, and styles) and a native-descriptor string. By allowing certain fields in both sides of the correspondence to be wildcarded and having the table entries ordered, complicated mapping schemes can be set up.

If the resource-mapping table is not flexible enough, the application can install its own mapper. This mapper can use XVT-supplied, portable utility functions or can be written to use nonportable code and functions. When a font needs to be mapped, the XVT font code calls the application-supplied mapper (if available) first. If this mapper fills in the native-descriptor string (using information in the font object and whatever other information it can access) and this string can be mapped to a physical font, then the XVT font code marks the font as "mapped." If the font is not successfully mapped after this (the application-supplied mapper is not obligated to attempt a mapping), then the XVT font code checks for a mapping via the resource-mapping table, if one is available. If this too fails, then the mapping process calls the default mapper.

A standard font-selection dialog allows the user of an XVT application access to all fonts on a system; see Figure 2. Though this dialog is platform specific, the programmatic interface to it is portable so the application need not be aware of any differences. (The application can also supply its own dialog, or other means of selecting fonts, if the standard dialog does not meet its needs.) If the user selects a font via this dialog, a font object is returned that has both the logical-font fields and the native-descriptor set.

Because font mapping can be expensive, the font code caches the most-recently mapped fonts. The application can set the cache size according to its own needs. Apart from word processors, many applications may never need more than a half dozen fonts. With a properly sized cache, the overhead of font mapping can be kept to the minimum--only once per font.

More on Font Mapping

The default font mapper supports four "standard" families. This is done for backward compatibility but also provides some measure of font portability for applications with simple font requirements. Although these font families (System, Courier, Times, and Helvetica) may not be available on every system, the default mapper recognizes these names and makes an effort to match them to the best physical font on the system. Other font families are more problematic, and the default mapper may or may not be able to find reasonable matches.

To see how default mapping works, we'll examine the default mapper for Motif, which is divided into two major parts. The first part is table driven and similar to the resource-mapping table discussed earlier. This table is used to map the four standard font families. One difference from the resource-mapping table is that each logical font in the table can correspond to more than one physical font. During a search of this table, if a match is found on the logical font, a search is performed for each of the corresponding physical fonts. If one is found, the search ends and the font is successfully mapped. The reason for having multiple physical fonts is that the Motif toolkit runs on many different systems from different companies and has no standard set of fonts. From experience, XVT has accumulated a list of the fonts most likely to be available. For example, two common Courier fonts are those from ITC and Adobe, so we include both.

Listing Two is the second part of the Motif default mapper. This function uses the font family, size, and some style attributes to find the best match to an existing physical font. First it builds an X Logical Font Descriptor (XLFD) with all fields wildcarded except the font family, which it copies from the logical-font family. The Xlib function XListFonts is called to find the available physical fonts that have this family name. For each font found, a score is calculated based on the font weight (bold or medium), slant (italic or not), size (the score is based on how close the size is), and whether the font is iso8859 (to avoid fonts with non-ANSI encodings).

A "perfect" match (based on the given criteria) exits from the loop; otherwise, the current font is marked as the best match so far if its score exceeds the last best score. After the loop, some font has been picked as the best match (even if no perfect match was found), and its XLFD is converted into a native descriptor and put into the font object. If no physical font was found with the same family name, the font is mapped to a standard font ("system") to guarantee some mapping.

Conclusion

The problem of portable fonts is complex. Some applications attempt to solve it by supplying the same fonts on all systems on which they run. This, of course, means limiting the number of available fonts. A newer approach is to put enough information into the document about the font to allow a program to reconstruct the font if it doesn't exist on the system. Although this may ultimately be the best solution, it is not currently supported by any standards; it is therefore proprietary and requires a major commitment of resources to implement.

In the approach described here, a font contains both a portable and a nonportable part and multiple levels of mapping are possible. This provides a reasonable solution for applications that don't need much control over fonts and a flexible and extensible solution for applications that do.

Native Font Descriptors

The XVT native font-descriptor string borrows conceptually from the X Logical Font Descriptor (XLFD) string. The XLFD consists of fields separated by dashes (--); unspecified fields can be given as asterisks (*). XVT's descriptor consists of fields separated by slashes (/); unspecified fields can be given as asterisks (*).

The first field of an XVT native descriptor is an encoded value that gives the window system and a font-descriptor version number. This makes it possible for the font code to determine if the descriptor is being used in the same environment in which it was created. Although the other fields are platform specific, the format is consistent: attribute fields separated by slashes. On Motif, for example, the fields are the same as those of the XLFD; see Example 1(a). The Macintosh, on the other hand, has a much simpler format and is shown in Example 1(b).

--J.B. and R.W.

Example 1: (a) Attribute fields on Motif; (b) attribute fields on Macintosh.

(a)
"X1101/<foundry>/<family>/<weight>/<slant>/<sWdth>/
<adStyl>/<pixel_size>/<point_size>/<X_res>/<Y_res>/
<spacing>/<average_height>/<registry>/<encoding>"

(b)
      "MAC01/<family>/<face>/<size>"

Figure 1 XVT font mapping. Figure 2 Windows font-selection dialog.

Listing One


typedef struct {
    char* family;             /* font family */
    long size;                /* font size in points */
    unsigned long style;      /* font style bitfield */
    char* native_desc;        /* native font descriptor */
    char is_print;            /* window is a print window */
    char is_mapped;           /* mapped flag */
    void* plat_font_data;     /* pointer to platform font info */
      .
      .
}  FONT_OBJ;

typedef struct {
    long far * dummy;         /* cast internally to (FONT-OBJ*)*/
} *XVT_FNTID;


Listing Two


static void font_fabricate_map(XVT_FNTID font_id)
{
    int i;
    int count = 0;
    const char* family; 
    unsigned long style; 
    long size;
    char **mlist = NULL;
    char* native_desc = NULL;
    int best_fit = -1;
    int best_score, cur_score;
    char *X_attr;
    int attr_length;
    long ptsize;
    char result[MAX_FONTNAME_SIZE];
    char xlfd[MAX_FONTNAME_SIZE];
    BOOLEAN scalable;
    PLAT_FONT_DATA *plat_font_data;

    family = xvtv_font_get_family(font_id); 
    style = xvtv_font_get_style(font_id); 
    size = xvtv_font_get_size(font_id);
    plat_font_data = FONT_GET_PLAT_DATA(font_id);

    /* Start with a score of 0 indicating no match */
    best_score = 0;

    /* build an XLFD that brings up the font families that match */
    strcpy(xlfd, "-*-");

    strcat(xlfd, family);
    strcat(xlfd, "-*-*-*-*-*-*-*-*-*-*-*-*");

    if ((mlist = XListFonts(xvt_display, xlfd, 100, &count)) != NULL) {

       /* loop through the list of XLFD's, weighing the components of 
       * size, boldness, and italics. */
      for (i = 0; i < count; i++) {
            /* Start at 200 points for matching on family */
            cur_score = 200;
            /* Measure XLFD weight against font style.  Note that
             * the X weight can be "bold" or "demibold"*/
            X_attr = get_xlfd_token(mlist[i], X_TOKEN_WEIGHT, &attr_length);
            if (style & XVT_FS_BOLD) {
                if (strstr(X_attr, "bold"))
                    cur_score += 100;
            } else {
                if (strstr(X_attr, "medium"))
                    cur_score += 100;
            }
            /* Measure XLFD slant against font style.  Note that
             * the X slant can be "o" or "i" */

            X_attr = get_xlfd_token(mlist[i], X_TOKEN_SLANT, &attr_length);
            if (style & XVT_FS_ITALIC) {
                if (strstr(X_attr, "o") || strstr(X_attr, "i"))
                    cur_score += 100;
            } else {
                if (strstr(X_attr, "r"))
                    cur_score += 100;
            }
            /* Measure XLFD size against font size. If size match 
             * is exact, add 100, else add less than 100. */
            X_attr = get_xlfd_token(mlist[i], X_TOKEN_POINTSIZE, &attr_length);
            ptsize = (strtol(X_attr, NULL, 10)) / 10;
            if (ptsize == 0 || ptsize == size)
                  cur_score += 100;
            else
                  cur_score += (min((int)size, (int)ptsize) * 100 /
                                                  max((int)size, (int)ptsize));
            /* Subtract points if this is not an iso8859 font (helps avoid
             * jis & other fonts). Use 150 to weight matching ISO fonts higher
             * than, say, an exact size match. */
            X_attr = get_xlfd_token(mlist[i], X_TOKEN_REGISTRY, &attr_length);
            if (!strstr(X_attr, "iso8859"))
                cur_score -= 150;
            /* Is this the best font fit we have seen? */
            if (cur_score > best_score) {
                best_score = cur_score;
                best_fit = i;
                if (ptsize == 0)
                    scalable = TRUE;
               else 
                    scalable = FALSE;
            }
            /* If we have found the perfect match, quit looking */
            if (best_score == 500)
                break;
        }
        /* Slip the correct point size in if font is scalable */
        if (scalable) {
            make_font_name(mlist[best_fit], (int)size, result);
            native_desc = xvtk_font_XLFD_cvt(result);
        } else
            native_desc = xvtk_font_XLFD_cvt(mlist[best_fit]);
        XFreeFontNames(mlist);
    } else {
        /* There was not even a native font match against the font 
         * family. Use the system font */
        if (scratch_font_id == NULL_FNTID) {
            scratch_font_id = xvtv_font_create();
            xvtv_font_set_family(scratch_font_id, "system");
            font_database_map(scratch_font_id);
        }
        native_desc=xvtv_font_get_native_desc(scratch_font_id, FALSE);
    }
    xvtv_font_set_native_desc(font_id, native_desc);
    return;
}


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.