5

This snippet can be used for drawing CGGlyphs with a CGContext:

//drawing
let coreGraphicsFont = CTFontCopyGraphicsFont(coreTextFont, nil)
CGContextSetFont(context, coreGraphicsFont);
CGContextSetFontSize(context, CTFontGetSize(coreTextFont))
CGContextSetFillColorWithColor(context, Color.blueColor().CGColor)          
CGContextShowGlyphsAtPositions(context, glyphs, positions, length)

But how do I obtain the CGGlyphs from a Swift string which contains emoji symbols like flags or accented characters?

let string = "swift: \u{1F496} \u{65}\u{301} \u{E9}\u{20DD} \u{1F1FA}\u{1F1F8}"

Neither of the below approaches shows the special characters, even though they are correctly printed to the console. Note that this first approach returns NSGlyph but CGGlyphs are required for drawing:

var progress = CGPointZero
for character in string.characters
{
    let glyph = font.glyphWithName(String(character))
    glyphs.append(CGGlyph(glyph))
    let advancement = font.advancementForGlyph(glyph)
    positions.append(progress)
    progress.x += advancement.width
}

or this second approach which requires casting to NSString:

var buffer = Array<unichar>(count: length, repeatedValue: 0)
let range = NSRange(location: 0, length: length)
(string as NSString).getCharacters(&buffer, range: range)

glyphs = Array<CGGlyph>(count: length, repeatedValue: 0)
CTFontGetGlyphsForCharacters(coreTextFont, &buffer, &glyphs, length)

//glyph positions
advances = Array<CGSize>(count: length, repeatedValue: CGSize.zero)
CTFontGetAdvancesForGlyphs(ctFont, CTFontOrientation.Default, glyphs, &advances, length)
positions = []
var progress = CGPointZero
for advance in advances
{
    positions.append(progress)
    progress.x += advance.width
}

Some of the characters are drawn as empty boxes with either approach. Kinda stuck here, hoping you can help.

Edit:

Using CTFontDrawGlyphs renders the glyphs correctly, but setting the font, size and text matrix directly before calling CGContextShowGlyphsAtPositions draws nothing. I find that rather odd.

McKinley
  • 1,123
  • 1
  • 8
  • 18
user965972
  • 2,489
  • 2
  • 23
  • 39
  • "Note that this first approach returns NSGlyph but CGGlyph's are required for drawing." Yes, if you're going to use Core Text. But why are you using it? You can do all of this with Text Kit and it's so much easier... – matt Oct 29 '15 at 21:18
  • 2
    Textkit can do a lot but not everything. – user965972 Oct 29 '15 at 21:21
  • Lets say I need as much control over the drawing and positioning of individual glyphs as possible. Best route for that is coretext. – user965972 Oct 29 '15 at 21:21

1 Answers1

5

If you generate glyphs yourself, you also need to perform font substitution yourself. When you use Core Text or TextKit to lay out and draw the text, they perform font substitution for you. For example:

let richText = NSAttributedString(string: "Hello→")
let line = CTLineCreateWithAttributedString(richText)
print(line)

Output:

<CTLine: 0x7fa349505f00>{run count = 3, string range = (0, 8), width = 55.3457, A/D/L = 15/4.6875/0, glyph count = 7, runs = (

<CTRun: 0x7fa34969f600>{string range = (0, 5), string = "Hello", attributes = <CFBasicHash 0x7fa3496902d0 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0x1153bb720 [0x10e85a7b0]>{contents = "NSFont"} = <CTFont: 0x7fa3496182f0>{name = Helvetica, size = 12.000000, matrix = 0x0, descriptor = <CTFontDescriptor: 0x7fa34968f860>{attributes = <CFBasicHash 0x7fa34968f8b0 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0x1153c16c0 [0x10e85a7b0]>{contents = "NSFontNameAttribute"} = <CFString 0x1153b4700 [0x10e85a7b0]>{contents = "Helvetica"}
}
>}}
}
}


<CTRun: 0x7fa3496cde40>{string range = (5, 2), string = "\U0001F600", attributes = <CFBasicHash 0x7fa34b11a150 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0x1153bb720 [0x10e85a7b0]>{contents = "NSFont"} = <CTFont: 0x7fa3496c3eb0>{name = AppleColorEmoji, size = 12.000000, matrix = 0x0, descriptor = <CTFontDescriptor: 0x7fa3496a3c30>{attributes = <CFBasicHash 0x7fa3496a3420 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0x1153c16c0 [0x10e85a7b0]>{contents = "NSFontNameAttribute"} = <CFString 0x11cf63bb0 [0x10e85a7b0]>{contents = "AppleColorEmoji"}
}
>}}
}
}


<CTRun: 0x7fa3496cf3e0>{string range = (7, 1), string = "\u2192", attributes = <CFBasicHash 0x7fa34b10ed00 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0x1153bb720 [0x10e85a7b0]>{contents = "NSFont"} = <CTFont: 0x7fa3496cf2c0>{name = PingFangSC-Regular, size = 12.000000, matrix = 0x0, descriptor = <CTFontDescriptor: 0x7fa3496a45a0>{attributes = <CFBasicHash 0x7fa3496a5660 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
    2 : <CFString 0x1153c16c0 [0x10e85a7b0]>{contents = "NSFontNameAttribute"} = <CFString 0x11cf63230 [0x10e85a7b0]>{contents = "PingFangSC-Regular"}
}
>}}
}
}

)
}

We can see here that Core Text recognized that the default font (Helvetica) doesn't have glyphs for the emoji or the arrow, so it split the line into three runs, each with the needed font.

The Core Text Programming Guide says this:

Most of the time you should just use a CTLine object to get this information because one font may not encode the entire string. In addition, simple character-to-glyph mapping will not get the correct appearance for complex scripts. This simple glyph mapping may be appropriate if you are trying to display specific Unicode characters for a font.

Your best bet is to use CTLineCreateWithAttributedString to generate glyphs and choose fonts. Then, if you want to adjust the position of the glyphs, use CTLineGetGlyphRuns to get the runs out of the line, and then ask the run for the glyphs, the font, and whatever else you need.

If you want to handle font substitution yourself, I think you're going to want to look into “font cascading”.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • CTLineCreateWithAttributedString or CTTypesetterCreateLine will determine glyph positions. Since I want to do my own positioning of the glyphs, that seems like unnecessary work(?). So perhaps I should first look into font cascading to avoid this(?). (or am I falling for a premature optimisation trap?) The swift string is enumerating grapheme by grapheme, so by using the proper font I should get valid glyphs if I understand correctly. I will certainly try your suggestions. Thanks. – user965972 Oct 29 '15 at 22:22
  • update: Extracting the glyph information from the runs and adjusting the positions is doable. However CGContextShowGlyphsAtPositions is not drawing the emoji from the AppleColorEmoji font. – user965972 Oct 30 '15 at 11:23