Using NSLayoutManager to calculate frames for each glyph
Asked Answered
B

2

17

On this thread, Core Text calculate letter frame in iOS, they were able to calculate the frame of each glyph very precisely using Core Text. The final rects hug the actual drawn glyphs perfectly.

Using NSLayoutManager's boundingRectForGlyphRange:inTextContainer: doesn't seem to return the glyph bounding boxes that precisely:

glyph frames using boundingRectForGlyphRange:inTextContainer

And the returned rects don't fully enclose more complex fonts (Zapfino example):

Zapfino f glyph using boundingRectForGlyphRange:inTextContainer

Does anyone know how to replicate the results from the above mentioned discussion without going into Core Text under iOS 7 only apps?

Thanks.

Beagle answered 13/2, 2014 at 19:59 Comment(1)
that is how the font designed. what result did you get in case of SnellRoundhand font, or any other italic/oblique fonts?Porter
T
1

Thanks for posting this Q!!

I do realize that this post is from 2014 (woah 8 years). I stumbled upon this very same bug in... 2022, just leaving, below, my final solution.

Please note that performing the Width calculation based on Glyph Location doesn't work for multiple lines (and the Character Average might also be off):

 func rtlAccurateBoundingRect(forGlyphRange glyphRange: NSRange, in container: NSTextContainer) -> CGRect {
    let output = boundingRect(forGlyphRange: glyphRange, in: container)
    let characterRange = characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
            
    /// Determine the Bounds for each line, and calculate the enclusing Width / Origin
    ///
    var maxWidth: CGFloat = .zero
    var locationMin: CGFloat = .greatestFiniteMagnitude
            
    enumerateLineFragments(forGlyphRange: glyphRange) { (fragRect, usedRect, textContainer, fragGlyphRange, stop) in
        maxWidth        = max(usedRect.width, maxWidth)
        locationMin     = min(usedRect.minX, locationMin)
    }
    
    /// Finally adjust the resulting CGRect
    ///
    var updated = output
    updated.origin.x = locationMin
    updated.size.width = maxWidth

    return updated
}
Thitherto answered 15/3, 2022 at 19:16 Comment(0)
D
0

I've found a solution for this issue or at least sort of

Problem: boundingRectForGlyphRange results is off in case of RTL text.

So in case of RTL text is detected only:

Using NSLayoutManager method locationForGlyphAtIndex, for each letter in the range. These are the starting points of each glyph in the range. Using these points I adjust the boundingRect correctly.

Here is an outline of it:

CGPoint endPoint = ((NSValue *)pointsOfGlyps.firstObject).CGPointValue;
CGPoint startPoint = ((NSValue *)pointsOfGlyps.lastObject).CGPointValue;
boundingRect.origin.x =  startPoint.x;
boundingRect.size.width = endPoint.x - startPoint.x + letterWidth;

letterWidth is an approximation of a letter width calculated as the delta between two consecutive starting points.

Disaffect answered 13/2, 2014 at 20:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.