Tim Specht
Tim Specht

Reputation: 3218

Get CGPath from Text

I'm currently trying to convert a letter and/or multiple of them to a CGPathRef for manually drawing them into a custom UIView. I tried the way over CoreText and Framesetters including this little snippet but it doesn't seem to work.

NSAttributedString *stringToDraw = [[NSAttributedString alloc] initWithString:content
                                                                       attributes:nil];
    
    // now for the actual drawing
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // flip the coordinate system
    
    // draw
    CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)stringToDraw);
    CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), NULL, NULL);
    
    return CTFrameGetPath(frame);

Upvotes: 4

Views: 3844

Answers (2)

HeWhoRemains
HeWhoRemains

Reputation: 41

this is the working function which create

This function is transforming a string of text into a set of vector paths, each representing a single glyph. The paths are stored in a CGMutablePath, which can later be used for drawing or rendering the text in a custom way

func textToPath(text: String, font: UIFont) -> CGPath? {
        let attributedString = NSAttributedString(string: text, attributes: [.font: font])
        let line = CTLineCreateWithAttributedString(attributedString)
        let runArray = CTLineGetGlyphRuns(line) as NSArray
        
        let path = CGMutablePath()
        
        for run in runArray {
            let run = run as! CTRun
            let count = CTRunGetGlyphCount(run)
            
            for index in 0..<count {
                let range = CFRangeMake(index, 1)
                var glyph: CGGlyph = 0
                var position: CGPoint = .zero
                CTRunGetGlyphs(run, range, &glyph)
                CTRunGetPositions(run, range, &position)
                
                if let glyphPath = CTFontCreatePathForGlyph(font, glyph, nil) {
                    var transform = CGAffineTransform(translationX: position.x, y: position.y)
                    transform = transform.scaledBy(x: 1, y: -1)
                    
                    path.addPath(glyphPath, transform: transform)
                }
            }
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
            cgPath = path
        })
        return path
    }

Upvotes: 0

Rob Napier
Rob Napier

Reputation: 299565

CTFrameGetPath returns the path the circumscribes the CTFrame, not the path generated by drawing the frame. I assume that you're doing the above because you are caching the paths to improve later drawing performance? (Since CTFrame can easily draw itself into a custom view).

If you really want to get CGPath, you need to drill all the way down to the CGGlyph and then use CTFontCreatePathForGlyph. If you just want to quickly redraw text, I'd probably draw this into a CGLayer (not CALayer) with CTFrameDraw for later reuse.

If you still really want the CGPath for the glyphs, take a look at Low-level text rendering. The code you need is in there.

Upvotes: 8

Related Questions