SWT Step 5. Font handling

Objective

Implement font handling (including implementation of o.e.swt.graphics.Font, FontData and FontMetrics classes). The test example of this step should draw some text strings with various fonts on the SWT top window client area using corresponding methods of o.e.swt.graphics.GC class.

Task notes

Font matching

The GpiQueryFaceString() function is intended to compose a font facename (that includes textual representation of the font style) given a font family name and flags describing the desired style (bold, italic etc), by searching the installed fonts for the exact match (the font that truely has the requested style, without any emulation). But this function is buggy and sometimes returns NULL even if the desired font actually exists in the system. The example is the Lucida Sans Typewriter font family shipped with Java. If we have all 4 styles of this family installed (Regular, Bold, Oblique and Bold Oblique) and try to call this function for it with FWEIGHT_DONT_CARE, FWIDTH_DONT_CARE and FTYPE_ITALIC flags set ((i.e. we request any Lucida Sans Typewriter font that has the FM_SEL_ITALIC bit set in the FONTMETRICS.fsSelection field, ignoring usWeightClass and usWidthClass) we get nothing although the Lucida Sans Typewriter Oblique exists and completely matches the given criteria.

Due to this bug, the only way to find a font given its family name and style is the full scan of the list of all fonts installed in the system. So we decided to cache the complete font list in some useful way upon the first font request from the SWT user to speed up further font searches. Hopefully, there is a function GpiQueryFontAction() that can tell us whether the font list has been changed since the last call or not, which can be used to check the validity of the cache.

The font matching algorithm is close enough to the original algorithm used by OS/2 to ensure that any font matched by our algorithm can be set as the font for standard OS/2 window classes, but has some differences (in particular, when there is no bitmap font of the requested size installed in the system we select one with the closest size from the font family, while OS/2 always selects the default font in that cases). See the Device.matchFont() methods for more detailed info about the matching algorithm.

Font metrics

There are numerous bugs with calculating font metrics in GPI. For example, various text drawing functions start using and returning wrong character box and string extent coordinates when we set the text alignment (GpiSetTextAlignment()) other than default (TA_NORMAL_HORIZ, TA_NORMAL_VERT). So we always have to use this default alignment (instead of TA_TOP for example, which would be more useful in SWT).

Also GPI wrongly fills the background of the string for true type fonts -- it makes it one pixel higher than the real font height (lMaxBaselineExt) and leaves unfilled gaps at the beginning and at the end of the string. For this reason we always fill the background ourselves instructing GPI not to fill it when drawing strings.

Another problem is that lMaxBaselineExt (as returned by GpiQueryFontMetrics()) is not always the sum of lMaxAscender and lMaxDescender, sometimes this sum is one pixel larger -- the example is the Times New Roman TTF font drawn at 24 points (40 pixels). As a solution we calculate the font descent value as lMaxBaselineExt - lMaxAscender instead of taking lMaxDescender from returned font metrics.

FATTRS and FONTMETRICS sz* fields

Originally FATTRS and FONTMETRICS structures have fields, szFamilyname and szFacename (FONTMETRICS only) to store the font family and face name as arrays of chars (not as pointers to arrays). In Java we can only define poiners (references) to arrays as field members. As opposed to Windows version of SWT, where they defined similar fields as a sequence of separate N byte fields (where N is the length of the original array), in OS/2 we defined them as references to java byte arrays. It is much easier to work with these arrays but is potentially dangerous because one can accidentally replace these references with references to arrays of different size -- it must not happen because the code assumes that these arrays always have the length of OS.FACENAME (32) bytes. When the structure is instantiated these arrays are allocated automatically and should not be reallocated later.

Locales

There are two ways to support unicode when working in OS/2 PM/GPI.

The first way is to convert unicode strings to single-byte strings using Uni* API (availabel since Warp 4 Fixpak 5) before passing them to PM/GPI calls and provide the corresponding single-byte codepage numbers when creating logical fonts and setting the message queue codepage.

The second way is to work in true unicode mode -- this means to use the codepage IBM-1200 for font creation and the message queue and pass unicode strings to PM/GPI as is, i.e. as two-byte unicode strings.

It's obvious that the second way is more convenient than the first because it allows to draw characters from different languages simultaneously, using the same logical font (of course, provided that the font physically contains glyphs for all used character groups). It is also the only way to display such different characters in the window's titlebar. Unfortunately, the current implementation of this approach is quite buggy in OS/2. The following bugs are known:

When we use the first approach (single-byte strings with convertion) bugs pointed above do not appear. But this approach doesn't allow to draw characters from different languages using the same font (a separate font for every character group is required).

In SWT, the only way to deal with locales is to use the FontData.setLocale() method to create fonts from different locales. As we understand this method makes sense only on systems that lack the true unicode support (Win95?) and it corresponds to the first way of working with unicode described above. For systems that support true unicode it is useless and even produces worse results (WinXP).

Since OS/2 provides the true unicode support it seems to be more logical to use it in SWT. But at the present time we decided not to do it because of listed bugs. Currently the only alternative way is to use single-byte approach and implement the setLocale() method. But we decided not to go this way also because of its conceptual limitations and because we believe that bugs of true unicode mode will be fixed in the near future by switching to the usage of the FreeType library to work with fonts which is free of many-many font-related bugs seen in OS/2. So, currently, only unicode characters that correspond to the system locale are displayed correctly (unicode is converted to single-byte using String.toBytes() method) and there is no way to switch locales (from the font usage point of view) dynamically in runtime.

Step checklist

Operation Status Remarks
Add FATTRS and FONTMETRICS classes and their native getters/setters, FATTRS_* constants, FM_TYPE_*, FM_DEFN_*, FM_SEL_*, FM_CAP_* constants Done [eli, dmik]
Implement FontMetrics class Done [eli, dmik]
Implement several missing methods in the GC class Done [dmik] See the updated SWT006 Step for more info
Add OS.WinSetPresParam (...byte[]), WinQueryPresParam() Done [dmik]
Add OS.GpiQueryFaceString(), FACENAMEDESC and FWEIGHT_*, FWIDTH_*, FTYPE_* constants... remove!!! Done [dmik]
Add OS.GpiQueryFontAction() and QFA_* constants; GpiQueryFonts() and QF_* constants, GpiQueryFontMetrics() Done [dmik]
Add OS.GpiCreateLogFont() and FONT_* constants, GpiSetCharSet(), GpiSetCharBox() Done [dmik]
Add OS.GpiCharStringAt(), GpiSetTextAlignment(), GpiQueryTextBox() & TXTBOX_* constants Done [dmik]
Add OS.PrfQueryProfileSize(), PrfQueryProfileString() and HINI_* constants Done [dmik] to read the system font value from OS2.INI
Add OS.WinEnableWindow() and implement Control.setEnabled() Done [dmik]
Implement FontData and Font classes Done [dmik]
Imlement string methods in the GC class Done [dmik]
Add testcases SWT005_01 and SWT005_02 Done [dmik]