I've done some testing of LogicalFontInstance::GetGlyphBoundRect(). Each platform seems to give different values!
The commit in gerrit is: https://gerrit.libreoffice.org/c/core/+/141234
(many thanks to Hossein for some suggestions in a different gerrit patch about some things around unit tests I didn't know - see comments at https://gerrit.libreoffice.org/c/core/+/141103/)
Ultimately, the differences boil down to the pure function LogicalFontInstance::ImplGetGlyphBoundRect(), which each platform must implement to get the glyph's bounding rect.
To try to understand the differences, I've looked at each platform's implementation with some notes, if this is at all helpful. Any comments would be appreciated! I'd love to standardize this function :-)
Chris
---
Comparion of LogicalFontInstance::ImplGetGlyphBoundRect() between platforms
============================================================================
WIN32
-----
ImplGetGlyphBoundRect() is implemented in WinFontInstance, derived by
from LogicalFontInstance
Located in vcl/win/gdi/salfont.cxx
Process:
Step 1: select the font
-----------------------
1. Get the HDC of the current graphics
2. Get the current GDI font's HFONT
3. Get the the HFONT of the font referenced by the WinFontInstance
4. If the current GDI HFONT is not the WinFontInstance's HFONT then
explicitly select the WinFontInstance's HFONT
5. Setup a guard to restore the original font after
ImplGetGlyphBoundRect() finishes
Step 2: Setup transformation matrix
-----------------------------------
MAT2 is a 3x3 transformation matrix
If using horizontal text, setup an identity matrix (means that nothing
happens when applying the matrix)
If using vertical writing then matrix appropriately rotates the glyph
Step 3: Setup to get the glyph's bounding rect
----------------------------------------------
1. Set the flag for GetGlyphOutlineW to use GGO_METRICS and
GGO_GLYPH_INDEX
- GGO_METRICS indicates to retrieve the GLYPHMETRICS structure
- GGO_GLYPH_INDEX indicates that we use the TrueType glyph index
instead of the character code
2. Zero initialize the GLYPHMETRICS fields
3. Call on GetGlyphOutlineW using the transformation matrix to
populate the glyph metrics
Step 4: Get the bounding rect of the glyph
------------------------------------------
The next bit takes the glyph metrics from the previous step.
1. Populate the glyph rectangle with the origin being the x- and y-
coords of the upper left corner of the smallest rectangle that
completely encloses the glyph, and the width and height of the
glyph's "black box", which is the smallest rectangle that
encloses the glyph
2. Scale the bounding rectangle, adding a point to the right and
bottom coords of the rectangle
SUMMARY:
Basically, we call on Win32's GetGlyphOutlineW() to get the GLYPHMETRICS.
It is important to quote Microsoft on this structure:
The GLYPHMETRICS structure specifies the width of the character cell
and the location of a glyph within the character cell. The origin of
the character cell is located at the left side of the cell at the
baseline of the font. The location of the glyph origin is relative to
the character cell origin. The height of a character cell, the
baseline, and other metrics global to the font are given by the
OUTLINETEXTMETRIC structure.
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-getglyphoutlinew
MAC
---
ImplGetGlyphBoundRect() is implemented in CoreTextStyle, derived from
LogicalFontInstance
Located in vcl/quartz/ctfont.cxx
Process:
Step 1: Get the glyph rectangle
-------------------------------
1. Set the CGGlyph variable nCGGlyph the glyph index
2. Get the font by looking up the mpStyleDict dictionary for the
kCTFontAttributeName, which gives the font of the text to
which this attribute applies
Note: Currently does not handle vertical text
3. Get the glyph rectangle in a CGRect by calling on
CTFontGetBoundingRectsForGlyphs()
4. Apply any font rotation for horizontal text
Step 2: Return the tools::Rectangle bounding rect
-------------------------------------------------
1. std::floor() the origin x, y (i.e. the top left)
2. std::ceil() the bottom right of the rectangle
i.e. to get this, aCGRect.origin.x + aCGRect.size.width
aCGRect.origin.y + aCGRect.size.height
3. so the rectangle gets the positive x origin and a negative y origin (???)
and for the bottom left a positive x and a negative y (???)
SUMMARY:
Use CTFontGetBoundRectsForGlyphs() to get the bounding rect of
the glyph.
Note that the Core Text documentation says the following:
The bounding rectangles of the individual glyphs are returned through
the boundingRects parameter. These are the design metrics from the
font transformed in font space.
https://developer.apple.com/documentation/coretext/1509419-ctfontgetboundingrectsforglyphs
UNIX
====
Note there are two variants: Qt and Freetype
Qt
==
ImplGetGlyphBoundRect() is implemented in QtFont, derived from
LogicalFontInstance
Located in vcl/qt5/QtFont.cxx
Process:
Literally gets the font, then gets the glyph bounding rect, which it
converts to a tools::Rectangle
SUMMARY:
Kind of opaque - not sure how Qt derives the bounding rect. Qt's
documentation has one line to describe it, which is:
Returns the smallest rectangle containing the glyph with the given
glyphIndex.
https://doc.qt.io/qt-5/qrawfont.html#boundingRect
FreeType
========
ImplGetGlyphBoundRect() is implmeneted in FreetypeFontInstance, derived
from LogicalFontInstance. This is, however, merely a wrapper
around FreeTypeFont::GetGlyphBoundRect() via member mxFreeTypeFont.
FreeTypeFont::GetGlyphBoundRect() is defined in
vcl/unx/generic/glyphs/freetype_glyphcache.cxx
Process:
Step 1: Load the font
---------------------
1. activates the size of the font face via FT_Activate_Size() for
FT_Load_Glyph()
2. loads the glyph (see below)
Step 2: Get the glyph
---------------------
1. embolden the glyph if needed
2. loads the glyph
3. applies a tranform to the glyph is needed
Step 3: Get the bounds box of the glyph
---------------------------------------
1. Load the glyph's control box, which encloses the outlines
points, via FT_Glyph_Get_CBox(), using the flag
FT_GLYPH_BBOX_PIXELS
2. destroys the glyph via FT_Done_Glyph()
3. Creates a tools::Rectangle via:
tools::Rectangle aRect(aBbox.xMin, -aBbox.yMax, aBbox.xMax, -aBbox.yMin);
4. Calculates bounding rectangle of rotated glyph if necessary
NOTES:
A few things to note - firstly the font is loaded via the flags FT_LOAD_DEFAULT
and FT_LOAD_IGNORE_TRANSFORM. (see below)
The second thing is: a FT_BBox has a very specific format in turns of
negative yMin, yMax, xMin and xMax values. We seem to ignore these! (see below)
From https://freetype.org/freetype2/docs/reference/ft2-base_interface.html#ft_load_xxx
FT_LOAD_DEFAULT
Corresponding to 0, this value is used as the default glyph load
operation. In this case, the following happens:
1. FreeType looks for a bitmap for the glyph corresponding to the
face's current size. If one is found, the function returns. The
bitmap data can be accessed from the glyph slot (see note
below).
2. If no embedded bitmap is searched for or found, FreeType looks for
a scalable outline. If one is found, it is loaded from the font
file, scaled to device pixels, then ‘hinted’ to the pixel grid in
order to optimize it. The outline data can be accessed from the
glyph slot (see note below).
Note that by default the glyph loader doesn't render outlines into
bitmaps.
FT_LOAD_IGNORE_TRANSFORM
Ignore the transform matrix set by FT_Set_Transform.
From https://freetype.org/freetype2/docs/reference/ft2-basic_types.html#ft_bbox
FT_BBox
Defined in FT_IMAGE_H (freetype/ftimage.h).
typedef struct FT_BBox_
{
FT_Pos xMin, yMin;
FT_Pos xMax, yMax;
} FT_BBox;
A structure used to hold an outline's bounding box, i.e., the
coordinates of its extrema in the horizontal and vertical
directions.
fields
xMin: The horizontal minimum (left-most).
yMin: The vertical minimum (bottom-most).
xMax: The horizontal maximum (right-most).
yMax: The vertical maximum (top-most).
note
The bounding box is specified with the coordinates of the lower left
and the upper right corner. In PostScript, those values are often
called (llx,lly) and (urx,ury), respectively.
If yMin is negative, this value gives the glyph's descender. Otherwise,
the glyph doesn't descend below the baseline. Similarly, if yMax is
positive, this value gives the glyph's ascender.
xMin gives the horizontal distance from the glyph's origin to the left
edge of the glyph's bounding box. If xMin is negative, the glyph
extends to the left of the origin.