Let's take a simple example of what I'm talking about. Let's say you want to display all of the fonts installed on the user's system to the user. Not only do you want the user to see the name of the font, you want them to see an example of it as well. For instance, like the font drop-down menu you see in MS Word.
If you go with the simple approach, you'd write some code that looks like this:[rbcode] dim c as Integer = FontCount
dim y as Integer = Canvas1.Top
dim fontName as String
for i as Integer = 0 to c - 1
fontName = Font( i )
Canvas1.Graphics.TextFont = fontName
Canvas1.Graphics.DrawString( fontName, 0, y )
y = y + Canvas1.Graphics.StringHeight( fontName, Canvas1.Width )
next i[/rbcode]
As you can see, this code loops over all of the installed fonts, and uses DrawString to display the font name to the user in the given font. This produces output like this:

However, if you look carefully at the picture, you'll notice that a number of the fonts aren't being displayed properly. For instance, look at the eight fonts preceeding Sylfaen in the list -- they all are displayed as a series of boxes. What's happening here is that the GDI rendering engine is unable to find any glyphs to display the text in. So the font exists, but it doesn't contain glyphs for the characters in the string. This is ok, however, since the font supports other glyphs which the user may wish to use. So we don't want to exclude it from the list, but we do want to show the user some meaningful information.
So we want to find out whether all of the characters in the string can be displayed in the given font or not. This way, we can fallback on another font to display the text in (such as Tahoma). So we want our code to look something like this:[rbcode] dim c as Integer = FontCount
dim y as Integer = Canvas1.Top
dim fontName as String
for i as Integer = 0 to c - 1
fontName = Font( i )
Canvas1.Graphics.TextFont = fontName
if not CanBeDisplayed( Canvas1.Graphics, fontName ) then
Canvas1.Graphics.TextFont = "Tahoma"
end if
Canvas1.Graphics.DrawString( fontName, 0, y )
y = y + Canvas1.Graphics.StringHeight( fontName, Canvas1.Width )
next i[/rbcode]
By doing this, we fallback on the Tahoma font, which produces output like this:

You'll notice that the previous "boxed" fonts are now displayed in text you can read, so we've accomplished our goals. How do we define the CanBeDisplayed function? We use the GetGlyphIndices API to tell us whether the glyphs are defined or not. The API allows you to specify the GGI_MARK_NONEXISTING_GLYPHS flag, which marks any non-existent glyphs with a special marker (&hFFFF). So we pass in the string to be displayed and check to see whether all of the glyphs for it exist or not. If any glyph doesn't exist, then we can fall back on Tahoma. The code will look like:[rbcode]
Function CanBeDisplayed(g as Graphics, text as String) As Boolean
Soft Declare Function GetGlyphIndicesW Lib "Gdi32" ( hdc as Integer, lpstr as WString, cch as Integer, retData as Ptr, flags as Integer ) as Integer
Dim glyphs as new MemoryBlock( Len( text ) * 2 )
Const GGI_MARK_NONEXISTING_GLYPHS = 1
Dim numConverted as Integer
numConverted = GetGlyphIndicesW( g.Handle( Graphics.HandleTypeHDC ), _
text, Len( text ), glyphs, GGI_MARK_NONEXISTING_GLYPHS )
// Now loop over all of the glyphs
for i as Integer = 0 to numConverted - 1
if glyphs.UInt16Value( i * 2 ) = &hFFFF then
return false
end if
next i
return true
End Function[/rbcode]
It's pretty straightforward as it loops over the glyph indicies returned to see if any are the marked value. If we find any that are marked, the string cannot be displayed in its entirety, and we return false. The astute will notice that this is a possible performance-killer since we have to loop over every character in the string (twice -- the API call must do this to get the glyphs as well), so you don't want to call this on very large strings.
There's only one thing left to do -- The API call relies on the HDC being setup with the font information. But REALbasic doesn't select the font into device context until it's actually needed. So if you call GetGlyphIndicies before using the Graphics object, then the wrong font will be selected into the context. So we need to make a harmless call which forces REALbasic to select the font into the context. Getting the StringWidth is harmless and quick. So before calling CanBeDisplayed, put in a call to Graphics.StringWidth. Once you've done this, your code will look like:[rbcode] dim c as Integer = FontCount
dim y as Integer = Canvas1.Top
dim fontName as String
for i as Integer = 0 to c - 1
fontName = Font( i )
Canvas1.Graphics.TextFont = fontName
// This is a hack to select the font into the device context
call Canvas1.Graphics.StringWidth( "Foo" )
if not CanBeDisplayed( Canvas1.Graphics, fontName ) then
Canvas1.Graphics.TextFont = "Tahoma"
end if
Canvas1.Graphics.DrawString( fontName, 0, y )
y = y + Canvas1.Graphics.StringHeight( fontName, Canvas1.Width )
next i[/rbcode]
The next challenge is figuring out whether the font is a symbol font or not. Symbol fonts (such as Webdings) may have glyphs to display for the characters, but they aren't going to be in any human-readable form (generally speaking). So you'd want to display the font name in Tahoma, but an example of the font behind it. But I'll leave this problem for another time -- you've got enough to work with now to get a good start on making a human-readable font picker list. Enjoy!
Brilliant! This is very, very useful for my project. I can't wait to implement it.
Glad you're finding it useful!
Possibly something I may look at adding to GraffitiButton in the future...only by request, though. I wouldn't want a user getting frustrated because they could use *ANY* font they wanted to.
Hrm...
Make that "couldn't" in my last comment. :x
Very useful - thanks!
Aaron, how about showing the equivalent code in all 3 REALbasic-supported operating systems?
Well, aside from the fact that I don't know the API for all three platforms.. ;-)
I don't think it's needed on the Mac because the Mac does automatic font linking. So those glyphs would get substituted out before being displayed improperly. Of course, you can use font linking on Windows as well.
@Aaron: Of course, *you* can use font linking on Windows as well ;-(
Well, it turns out that GetGlyphIndices isn't always reliable. It's possible for it to not flag some code points even though they can't be displayed.
The alternative is a much more involved process which I'm taking a look at now.
[...] Last time, we talked about how to tell whether text could be displayed using a specific font. And I left you with a teaser dealing with symbol fonts. You see, a symbol font makes it impossible to read the text. [...]