Search code examples
iosfontsuilabelglyph

How to manage text glyph boundary


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


Solution

  • 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