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.