Skip to content

Instantly share code, notes, and snippets.

@odrobnik
Last active February 21, 2023 12:06
Show Gist options
  • Save odrobnik/2751fb3ce32792b8a85d to your computer and use it in GitHub Desktop.
Save odrobnik/2751fb3ce32792b8a85d to your computer and use it in GitHub Desktop.
Swift: create CGPath from attributed string
func appendToPath(path: CGMutablePath)
{
let textPath = CGPathCreateMutable()
let attributedString = NSAttributedString(string: string)
let line = CTLineCreateWithAttributedString(attributedString)
// direct cast to typed array fails for some reason
let runs = (CTLineGetGlyphRuns(line) as [AnyObject]) as! [CTRun]
for run in runs
{
let attributes: NSDictionary = CTRunGetAttributes(run)
let font = attributes[kCTFontAttributeName as String] as! CTFont
let count = CTRunGetGlyphCount(run)
for index in 0..<count
{
let range = CFRangeMake(index, 1)
var glyph = CGGlyph()
CTRunGetGlyphs(run, range, &glyph)
var position = CGPoint()
CTRunGetPositions(run, range, &position)
let letterPath = CTFontCreatePathForGlyph(font, glyph, nil)
var transform = CGAffineTransformMake(1, 0, 0, -1, position.x, position.y)
CGPathAddPath(textPath, &transform, letterPath)
}
}
var transform = CGAffineTransformMakeTranslation(location.x, location.y)
CGPathAddPath(path, &transform, textPath)
}
@manmal
Copy link

manmal commented Dec 11, 2015

I'd change
let runs = CTLineGetGlyphRuns(line) as! Array<CTRun>
to
guard let runs = CTLineGetGlyphRuns(line) as? [CTRun] else { return }

and

    let attributes = CTRunGetAttributes(run) as! Dictionary<String, AnyObject>
    let font = attributes[kCTFontAttributeName as String] as! CTFont

to

    guard let attributes = CTRunGetAttributes(run) as? [String:AnyObject],
      let font = attributes[kCTFontAttributeName as String] as? CTFont else { 
        continue 
    }

The rest looks great for me 👍

@SuperWomble
Copy link

Can you please elaborate on how this is supposed to be used? Is it supposed to added to an extension on NSAttributedString? (If so, the use of "CGAffineTransformMakeTranslation(location.x, location.y)" generates an error (as location is not a property of NSAttributedString.) Thanks.

@SuperWomble
Copy link

SuperWomble commented Sep 24, 2016

@manmal, those changes won't work. As @odrobnik points out, his conversions are required (in Swift 2.3 at any rate).

However, checks ARE needed. It turn out that non-drawing glyphs such as spaces will cause CTFontCreatePathForGlyph to return NULL. You need to check for this, and only call CGPathAddPath if the path was successfully created. Otherwise, crash.

e.g. feeding the string "This string will crash" to the method on iOS 9 will indeed cause it to crash, because of the spaces.

The quick-help documentation on this is in error, or at least misleading. But the header file says this about the returned value:

"A retained CGPath reference containing the glyph outlines or NULL if there is no such glyph or it has no outline"

i.e. it's entirely possibly for NULL to be returned for some glyphs.

@ljs19923
Copy link

ljs19923 commented Nov 19, 2018

What's location.x and location.y ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment