Search code examples
swiftuibezierpath

Text path created by CTFontCreatePathForGlyph is flipped


I am trying to achieve text drawn inside CAShapeLayer. For this purpose I need to create UIBezierPath from the text. Based on text path solution should all works fine, but sadly I can't find what is wrong.

The path created by CTFontCreatePathForGlyph(runFont, glyph, nil) has drawn letter from the flipped side.

enter image description here

The code converted to Swift 4

      func initializeTextLayer() {
        let fontName: CFString = "Noteworthy-Light" as CFString

        let letters     = CGMutablePath()
        let font        = CTFontCreateWithName(fontName, 50, nil)
        let attrString  = NSAttributedString(string: "TEST", attributes: [kCTFontAttributeName as NSAttributedStringKey : font])
        let line        = CTLineCreateWithAttributedString(attrString)
        let runArray    = CTLineGetGlyphRuns(line)

        for runIndex in 0..<CFArrayGetCount(runArray) {

            let run     : CTRun =  unsafeBitCast(CFArrayGetValueAtIndex(runArray, runIndex), to: CTRun.self)
            let dictRef : CFDictionary = CTRunGetAttributes(run)
            let dict    : NSDictionary = dictRef as NSDictionary
            let runFont = dict[kCTFontAttributeName as String] as! CTFont

            for runGlyphIndex in 0..<CTRunGetGlyphCount(run) {
                let thisGlyphRange  = CFRangeMake(runGlyphIndex, 1)
                var glyph           = CGGlyph()
                var position        = CGPoint.zero
                CTRunGetGlyphs(run, thisGlyphRange, &glyph)
                CTRunGetPositions(run, thisGlyphRange, &position)

                let letter          = CTFontCreatePathForGlyph(runFont, glyph, nil)
                let t               = CGAffineTransform(translationX: position.x, y: position.y)
                if let letter = letter  {
                    letters.addPath(letter, transform: t)
                }
            }
        }
        let path = UIBezierPath()
        path.move(to: CGPoint.zero)
        path.append(UIBezierPath(cgPath: letters))

        let pathLayer               = CAShapeLayer()
        pathLayer.path              = path.cgPath
        self.layer.addSublayer(pathLayer)
    }

Any ideas what can I do to mirror it ? I found something mirror path but it didn't get any results.


Solution

  • As @Grimxn said: I have to do scale and translation.

    In super view of custom layer inside draw(in rect: CGRect) added following lines of code.

     func updateTextLayerPath() {
        if let pth = popupLayer.textLayerPath {
    
            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()
        }
    }
    

    textLayerPath is a stored property in custom CAShapeLayer which gets updates inside layoutSubviews of the super view.

    Hope it will be helpful for everyone else who is going to face this problem.