CoreText Swift Academy - part 2

In Part 1, I created a label view, where I draw a text in it. Now let's see what happened there:

Font & Attibuted String

How about font? Font is important part of drawing text. CoreText relies on Attributed String. Simple NSAttributedString() instance without any attribute, defaults to System font. System font depends on the system (iOS/macOS/tvOS) and system version.

To use set font, I need to create font instance (NSFont/UIFont) and set string attribute key (NSAttributedString.Key.font). Here I set Papyrus (available on macOS) font:

let myfont = CTFontCreateWithName("Papyrus" as CFString, 24, nil)
let attributedString = NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: myfont])

CTFont is base CoreText type and is bridged to/from NSFont/UIFont. I can use UIFont directly. This code has the same effect:

let myfont = UIFont(name: "Papyrus", size: 24)!
let attributedString = NSAttributedString(string: text, attributes: [NSAttributedString.Key.font: myfont])

effect

Screenshot-2020-07-05-at-13.08.48

When it comes to Attibuted string attributes. String attributes are just a String based keys, and I can define my own attribtued, that I can process later on while drawing (look for CTRun later on)

CoreText has its own set of String Attributes I can use at any time when dealing with CoreText engine:

kCTFontAttributeName, kCTKernAttributeName, kCTLigatureAttributeName, kCTForegroundColorAttributeName, kCTForegroundColorFromContextAttributeName, kCTParagraphStyleAttributeName, kCTStrokeWidthAttributeName, kCTStrokeColorAttributeName, kCTSuperscriptAttributeName, kCTUnderlineColorAttributeName, kCTUnderlineStyleAttributeName, kCTVerticalFormsAttributeName, kCTGlyphInfoAttributeName, kCTRunDelegateAttributeName, kCTBaselineOffsetAttributeName, kCTTrackingAttributeName

Most of it is mapped to/from UIKit/AppKit eg. NSAttributedString.Key.font is kCTFontAttributeName

Font Descriptor

While CTFont is a reference to the font. CTFontDescriptor (bridged to NSFontDescriptor/UIFontDescriptor) is where fun with fonts happens. It allows me to adjust point size, and variation that can completely specify a font.

Font metrics

glyph_metrics

Each font comes with many metrics that font creator (author) specified for the font. Size is just one metric, and the most popular. Font size is a base, however when deal with text drawing (especially with text layout), I need to be aware of every other measurements needed to calculate correct (and expected) position for each letter.

Baseline

Baseline is the most imporant value. Baseline is the base of the line, and it's not the bottom of the line. Some values (most of it) relates to base line, not to the bottom of the line - something worth remembering.

Baseline marks the Origin.

Height

There's no single "height". Height of what? The height is one of Ascent, Descent, Leading (an more) or sum of it. See image above to find all the values. I can get all the values with CoreText.

Probably the most useful height is a line height

Line height

Perfect line height is a sum of Ascent + Descent + Leading. Life would be too easy if that'd be an ultimate answer. Later I'll discuss how that's not necessarily the most used line height.

For the most general metrics, here's the snippet

let fontLineHeight = CTFontGetAscent(font) + CTFontGetDescent(font) + CTFontGetLeading(font)

now, bear in mind we're dealing with a line (CTLine) that is an Attributed String and fonts or font sizes may vary for the line. To calculate the line height, I should go over all letters and calculate tallest height value. I can do that, or I can use CTLineGetTypographicBounds that will do all of that

var ascent: CGFloat = 0
var descent: CGFloat = 0
var leading: CGFloat = 0
let width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading)

where width is the width of the line.

Note: If you have an prior experience with CoreText, you may notice at this point that the line height calulated with these values is not what CoreText uses for the layout. I'll discuss that in the next chapter...


Node: this is part of series (2 of many). check here tomorrow for next episode.