How to manage text glyph boundary
Asked Answered
V

1

1

Suppose I have a clock app, and I want to label the hours with characters that could be in an arbitrary character set/font. I'd like to frame the character in a frame and fill it as much as possible. How can I do that? If I use a UILabel, space gets inserted around the glyph. Here are examples showing two labels, both with the same font but with different characters. The Hebrew numerals all seem shorter than the Arabic numerals. Is there a way to determine the size of the glyphs or somehow push the glyphs out to fit the frame?

enter image description here enter image description here

As you can see, the Hebrew numeral is shorter even though it's being added to a taller UILabel.

Edit: Since posting, I made a first pass of using core text to see if that could resolve my problem. It seems that it will not. See these illustrations comparing a Hebrew glyph to an Arabic numeral glyph. They yellow is the label bounds. The green is the glyph rectangle reported by core text. I was hoping to get a rectangle flush to the glyph, but it looks like the Hebrew glyphs include space above and beside what I expected to be the glyph. Is there something else I should be checking? Hebrew numeral Arabic numeral

Vyky answered 14/11, 2017 at 16:37 Comment(4)
Take a look at CTFont - specifically the Getting Glyph Data section: developer.apple.com/documentation/coretext/ctfont-q6rBoyish
I looked at that, but that gets data for the font, not specific glyphs.Vyky
I take it back. It looks like there is boundary data available for arrays of glyphs. I'll look at that....Vyky
The green rectangles come from CTFontGetBoundingRectsForGlyphs. They are all as expected for Arabic numerals and all have extra space above for Hebrew numerals.Vyky
B
1

I don't know if you use Obj-C or Swift, but you can paste this into a Swift Playground page to see the result:

Minor Edits

import UIKit
import CoreText
import PlaygroundSupport


class PathView: UIView {

    var myPath: UIBezierPath?

    override func draw(_ rect: CGRect) {

        if let pth = myPath {

            UIColor.red.setStroke()

            // glyph path is inverted, so flip vertically
            let flipY = CGAffineTransform(scaleX: 1, y: -1.0)

            // glyph path may be offset on the x coord, and by the height (because it's flipped)
            let translate = CGAffineTransform(translationX: -pth.bounds.origin.x, y: pth.bounds.size.height + pth.bounds.origin.y)

            // apply the transforms
            pth.apply(flipY)
            pth.apply(translate)

            // stroke the path
            pth.stroke()

            // print the modified path for debug / reference
            print(pth)

        }

    }

}

class TestViewController : UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // blue background so we can see framing
        view.backgroundColor = UIColor(red: 0.25, green: 0.5, blue: 01.0, alpha: 1.0)

        // use a large font so we can see it easily
        let font = UIFont(name: "Times", size: 160)!

        // Hebrew character for 8
        var unichars = [UniChar]("ח".utf16)
        unichars = [UniChar]("י".utf16)

        // init glyphs array
        var glyphs = [CGGlyph](repeatElement(0, count: unichars.count))

        let gotGlyphs = CTFontGetGlyphsForCharacters(font, &unichars, &glyphs, unichars.count)

        if gotGlyphs {
            // get the cgPath for the character
            let cgpath = CTFontCreatePathForGlyph(font, glyphs[0], nil)!

            // convert it to a UIBezierPath
            let path = UIBezierPath(cgPath: cgpath)

            var r = path.bounds

            // let's show it at 40,40
            r = r.offsetBy(dx: 40.0, dy: 40.0)

            let pView = PathView(frame: r)
            pView.backgroundColor = .white
            pView.myPath = path

            view.addSubview(pView)

            // print bounds and path data for debug / reference
            print("bounds of path:", path.bounds)
            print()
            print(path)
            print()
        }

    }

}

let vc = TestViewController()
PlaygroundPage.current.liveView = vc

Lots of error checking / handling needed, but this may give you a good start on finding and using the bounds of the actual character glyphs (instead of the label frames).

Result:

enter image description here enter image description here

Boyish answered 22/11, 2017 at 20:17 Comment(6)
So essentially, instead of asking for the bounding box, you're asking for the path of the glyph and getting a bound for it. CTFontGetBoundingRectsForGlyphs has nothing to do with the label frame or bounds.Vyky
Essentially, yes. It's a little complicated, though... If you run that code for example, and look at the path's bounds, you'll see (6.875, -1.953125, 62.4247159090909, 89.921875) - which is a little odd on the surface. Negative Y? I don't know enough about font / glyph characteristics to explain... but hopefully this gives you enough to get to your destination.Boyish
Yes, it looks like it does. I'll review your code in detail when I get some time. But it appears to be able to get exactly what I need. Thanks. I'll likely be accepting it as the answer in a couple days. In addition to getting a solution, though, I'm trying to understand the framework. It's possible that the glyph boxes include space for other marks that can sometimes be included for Hebrew, but it seems like I should be able to ask for the boundaries of those areas. P.S. I'm currently using Objective-C, but I code in both, so this is fine.Vyky
BTW, there seems to be an issue with c : unichar = 0x05d9. I think. I haven't bothered yet to see why it seems to be truncated.Vyky
As I mentioned, there's a lot about font / glyph characteristics I don't know... I made a couple small edits... if you grab this updated code, it should work (or at least come close to working) with both "ח" and "י" (just comment / uncomment the unichars assignment).Boyish
I just did those same edits on my side before I saw your message. :) BTW, I don't like using string literals for characters with opposite directionality because selection is weird and sometimes there are display artifacts resulting from direction reversing.Vyky

© 2022 - 2024 — McMap. All rights reserved.