Firemonkey’s abstract TCanvas class has been providing the dimensions of the bounding rectangle of some text on itself. On recent updates it has deprecated providing text rectangle directly in the canvas, in stead encouraged users to use TTextLayout abstract class which handles several text-based operations on the native context of the platforms. So it is possible to get the bounding rectangle of a string on any canvas. Is this enough? Though I am sure this class will improve to involve many new textual features, at current time it lacks some major needs of a developer who designs text drawing applications.
For the first time, I have faced a feature lack when I saw even TMS’s HTML Text was unable to align words in an HTML notation that have got different sizes. Because having text rectangle is not enough to locate words in different sizes because of the missing baseline information. If you align the words from the top the smaller word’s baseline will be upper (as in the above image), if you align the bottoms the baseline will be lower, so to make a correct match you should know where are the baselines of each word exactly located. So we need to know the Ascent value of the text. If have got the ascent value, we can easily align the words on the baseline as seen on following image. For a complete information on typography see this article on Wikipedia.
As seen on the above image, having the information of ascent values for each word we can align them properly on the baseline of the sentence. However FMX TTextLayout doesn’t provide this information. So I decided to add a new function to my PlatformExtensions unit to provide metrics information that are gathered from the drawing of that specific font in a specific size. The new function is GetTextMetrics and hast this following structure.
Class Procedure GetTextMetrics(Text:String; Font:TFont; var TextRect:TRectF; var Ascent,Descent:Single; var CapHeight,XHeight:Single);virtual;abstract;
Getting Text Metrics in Windows
Firemonkey uses two different context types on Windows platform that are Windows GDIP and Direct2D. The default one is Windows GDI plus context which is a set of device independent drawing API based on the old GDI. Because of its device independency GDIP lacks of some important drawing features such as XOR brush, but it is always possible to handle these extra operations by locking the context to device dependent HDC and doing things on the device bitmap. What I mean is; getting font metrics is not as easy as it was old GDI based on HDC, so I used the limited information of the GDIP and estimated some others like (x height of text) by using statistical multipliers. However someone other can collapse the context to HDC level and calculate more detailed font metrics on that level. I didn’t prefer it to be consistent with the design logic of GDIP, and used TGPFontFamily class of GDIP to get the information about the font. Note that the metrics values gathered from this class is in logical units so should be converted to pixels using the proportions.
Here are my calculations:
fSize := FGPFont.GetHeight(FGraphics); cAscent := FGpFamily.GetCellAscent(FontStyle); cDescent := FGpFamily.GetCellDescent(FontStyle); emHeight := FGpFamily.GetEmHeight(FontStyle); lSpacing := FGPFamily.GetLineSpacing(FontStyle); iLeading := cAscent+cDescent - emHeight; eLeading := lSpacing - cAscent-cDescent; Ascent := fSize * (cAscent)/lSpacing; Descent := fSize * (descent)/lSpacing; CapHeight := fSize * (cAscent-iLeading)/lSpacing; XHeight := fsize * (cAscent-iLeading) *(7/10) /lSpacing;
Note that the calculations use TGPFont, TGPFontFamily and TGPGraphics classes to deal with font metrics. The FGPFont and FGPGraphics objects are not accessible through standard class hierarchy, however we can use RTTI functions to access them.
RttiField := RttiContext.GetType(Layout.ClassType).GetField('FGPFont'); if assigned(RttiField) then begin FGPFont := TGpFont(RttiField.GetValue(Layout).AsObject); RttiField := RttiContext.GetType(Layout.ClassType).GetField('FGraphics'); FGraphics := TGpGraphics(RttiField.GetValue(Layout).AsObject); FGPFamily := TGPFontFamily.Create; FGPFont.GetFamily(FGPFamily); // Get Metrics // ... end;
Another point to be mentioned here is that, above calculations work only if the DefaultCanvas is a GDIP canvas. If the developer selects to use the D2D canvas, because of the absence of the FGPFont object in RTTI the text metrics will not be updated. Since I don’t have any experience on Direct2D I haven’t worked on it, but I will be happy if someone does it and send me so that I can put it in the class code by giving credits to his/her name.
Getting Text Metrics in Mac OS X
Things are always easier for programmers in Mac side. You can see below how it is easy getting font metrics using CoreText API of Cocoa.
LFontRef := CTFontCreateWithName(CFSTR(Layout.Font.Family), Layout.Font.Size, nil); Ascent := CTFontGetAscent(LFontRef); Descent := CTFontGetDescent(LFontRef); CapHeight := CTFontGetCapHeight(LFontRef); XHeight := CTFontGetXHeight(LFontRef); CFRelease(LFontRef);
You can see all the details of using TTextLayout class and how to extend it to get Text/Font Metrics in my PlatformExtensions units. You can get the source code of the PlatformExtensions classes with the demo application from this SVN link. For non-programmers the compiled Win32, Win64, MacOSX (Thanks to Firemonkey) applications are also available to download.