Sunset MathTable, created MTFontV2, MTFontMathTableV2 and MathImage, about to sunset MTMathImage

This commit is contained in:
Peter Tang
2023-09-15 09:53:54 +08:00
parent 2762cbee85
commit cea2188310
10 changed files with 338 additions and 65 deletions

View File

@@ -0,0 +1,51 @@
//
// MTFontMathTableV2.swift
//
//
// Created by Peter Tang on 15/9/2023.
//
import Foundation
// extension MathTable {
// public func fontMathTableV2() -> MTFontMathTableV2 {
// MTFontMathTableV2(mathFont: font, size: fontSize)
// }
// }
internal class MTFontMathTableV2: MTFontMathTable {
private let mathFont: MathFont
private let fontSize: CGFloat
private let unitsPerEm: UInt
private let mTable: NSDictionary
init(mathFont: MathFont, size: CGFloat) {
self.mathFont = mathFont
self.fontSize = size
mTable = mathFont.mathTable()
unitsPerEm = mathFont.ctFont(withSize: fontSize).unitsPerEm
super.init(withFont: mathFont.mtfont(size: fontSize), mathTable: mTable)
super._mathTable = nil
// disable all possible access to _mathTable in superclass!
}
override var _mathTable: NSDictionary? {
set { fatalError("\(#function) change to _mathTable \(mathFont.rawValue) not allowed.") }
get { mTable }
}
override var muUnit: CGFloat { fontSize/18 }
override func fontUnitsToPt(_ fontUnits:Int) -> CGFloat {
CGFloat(fontUnits) * fontSize / CGFloat(unitsPerEm)
}
override func constantFromTable(_ constName: String) -> CGFloat {
guard let consts = mTable[kConstants] as? NSDictionary, let val = consts[constName] as? NSNumber else {
return .zero
}
return fontUnitsToPt(val.intValue)
}
override func percentFromTable(_ percentName: String) -> CGFloat {
guard let consts = mTable[kConstants] as? NSDictionary, let val = consts[percentName] as? NSNumber else {
return .zero
}
return CGFloat(val.floatValue) / 100
}
}

View File

@@ -0,0 +1,57 @@
//
// File.swift
//
//
// Created by Peter Tang on 15/9/2023.
//
import Foundation
import CoreGraphics
import CoreText
extension MathFont {
public func mtfont(size: CGFloat) -> MTFontV2 {
MTFontV2(font: self, size: size)
}
}
public final class MTFontV2: MTFont {
let font: MathFont
let size: CGFloat
private lazy var _cgFont: CGFont = {
font.cgFont()
}()
private lazy var _ctFont: CTFont = {
font.ctFont(withSize: size)
}()
private lazy var _mathTab = MTFontMathTableV2(mathFont: font, size: size)
init(font: MathFont = .latinModernFont, size: CGFloat) {
self.font = font
self.size = size
super.init()
super.defaultCGFont = nil
super.ctFont = nil
super.mathTable = nil
super.rawMathTable = nil
}
override var defaultCGFont: CGFont! {
set { fatalError("\(#function): change to \(font.fontName) not allowed.") }
get { _cgFont }
}
override var ctFont: CTFont! {
set { fatalError("\(#function): change to \(font.fontName) not allowed.") }
get { _ctFont }
}
override var mathTable: MTFontMathTable? {
set { fatalError("\(#function): change to \(font.rawValue) not allowed.") }
get { _mathTab }
}
override var rawMathTable: NSDictionary? {
set { fatalError("\(#function): change to \(font.rawValue) not allowed.") }
get { fatalError("\(#function): access to \(font.rawValue) not allowed.") }
}
public override func copy(withSize size: CGFloat) -> MTFont {
MTFontV2(font: font, size: size)
}
}

View File

@@ -39,10 +39,10 @@ public enum MathFont: String, CaseIterable {
case .termesFont: return "TeXGyreTermesMath-Regular"
}
}
public func cgFont() -> CGFont? {
public func cgFont() -> CGFont {
BundleManager.manager.obtainCGFont(font: self)
}
public func ctFont(withSize size: CGFloat) -> CTFont? {
public func ctFont(withSize size: CGFloat) -> CTFont {
BundleManager.manager.obtainCTFont(font: self, withSize: size)
}
#if os(iOS)
@@ -55,16 +55,16 @@ public enum MathFont: String, CaseIterable {
NSFont(name: fontName, size: size)
}
#endif
internal func mathTable() -> NSDictionary? {
internal func mathTable() -> NSDictionary {
BundleManager.manager.obtainMathTable(font: self)
}
internal func get(nameForGlyph glyph: CGGlyph) -> String {
let name = cgFont()?.name(for: glyph) as? String
return name ?? ""
}
internal func get(glyphWithName name: String) -> CGGlyph? {
cgFont()?.getGlyphWithGlyphName(name: name as CFString)
}
// internal func get(nameForGlyph glyph: CGGlyph) -> String {
// let name = cgFont().name(for: glyph) as? String
// return name ?? ""
// }
// internal func get(glyphWithName name: String) -> CGGlyph {
// cgFont().getGlyphWithGlyphName(name: name as CFString)
// }
}
internal extension CTFont {
/** The size of this font in points. */
@@ -117,7 +117,6 @@ private class BundleManager {
version == "1.3" else {
throw FontError.invalidMathTable
}
//FIXME: mathTable = MTFontMathTable(withFont:self, mathTable:rawMathTable)
mathTables[mathFont] = rawMathTable
print("mathFonts bundle resource: \(mathFont.rawValue).plist registered.")
}
@@ -135,12 +134,15 @@ private class BundleManager {
initializedOnceAlready.toggle()
}
fileprivate func obtainCGFont(font: MathFont) -> CGFont? {
fileprivate func obtainCGFont(font: MathFont) -> CGFont {
if !initializedOnceAlready { registerAllBundleResources() }
return cgFonts[font]
guard let cfFont = cgFonts[font] else {
fatalError("\(#function) unable to locate CTFont \(font.fontName)")
}
return cfFont
}
fileprivate func obtainCTFont(font: MathFont, withSize size: CGFloat) -> CTFont? {
fileprivate func obtainCTFont(font: MathFont, withSize size: CGFloat) -> CTFont {
if !initializedOnceAlready { registerAllBundleResources() }
let fontPair = CTFontPair(font: font, size: size)
guard let ctFont = ctFonts[fontPair] else {
@@ -149,13 +151,16 @@ private class BundleManager {
ctFonts[fontPair] = ctFont
return ctFont
}
return nil
fatalError("\(#function) unable to locate CTFont \(font.fontName)")
}
return ctFont
}
fileprivate func obtainMathTable(font: MathFont) -> NSDictionary? {
fileprivate func obtainMathTable(font: MathFont) -> NSDictionary {
if !initializedOnceAlready { registerAllBundleResources() }
return mathTables[font]
guard let mathTable = mathTables[font] else {
fatalError("\(#function) unable to locate mathTable: \(font.rawValue).plist")
}
return mathTable
}
deinit {
ctFonts.removeAll()

View File

@@ -0,0 +1,112 @@
//
// MTMathImageV2.swift
//
//
// Created by Peter Tang on 15/9/2023.
//
import Foundation
import Foundation
#if os(iOS)
import UIKit
#endif
#if os(macOS)
import AppKit
#endif
public struct MathImage {
public var font: MathFont = .latinModernFont
public var fontSize: CGFloat
public var textColor: MTColor
public var labelMode: MTMathUILabelMode
public var textAlignment: MTTextAlignment
public var contentInsets: MTEdgeInsets = MTEdgeInsetsZero
public let latex: String
private(set) var intrinsicContentSize = CGSize.zero
public init(latex: String, fontSize: CGFloat, textColor: MTColor, labelMode: MTMathUILabelMode = .display, textAlignment: MTTextAlignment = .center) {
self.latex = latex
self.fontSize = fontSize
self.textColor = textColor
self.labelMode = labelMode
self.textAlignment = textAlignment
}
}
extension MathImage {
public var currentStyle: MTLineStyle {
switch labelMode {
case .display: return .display
case .text: return .text
}
}
private func intrinsicContentSize(_ displayList: MTMathListDisplay) -> CGSize {
CGSize(width: displayList.width + contentInsets.left + contentInsets.right,
height: displayList.ascent + displayList.descent + contentInsets.top + contentInsets.bottom)
}
public mutating func asImage() -> (NSError?, MTImage?) {
func layoutImage(size: CGSize, displayList: MTMathListDisplay) {
var textX = CGFloat(0)
switch self.textAlignment {
case .left: textX = contentInsets.left
case .center: textX = (size.width - contentInsets.left - contentInsets.right - displayList.width) / 2 + contentInsets.left
case .right: textX = size.width - displayList.width - contentInsets.right
}
let availableHeight = size.height - contentInsets.bottom - contentInsets.top
// center things vertically
var height = displayList.ascent + displayList.descent
if height < fontSize/2 {
height = fontSize/2 // set height to half the font size
}
let textY = (availableHeight - height) / 2 + displayList.descent + contentInsets.bottom
displayList.position = CGPoint(x: textX, y: textY)
}
var error: NSError?
let mtfont: MTFont? = font.mtfont(size: fontSize)
guard let mathList = MTMathListBuilder.build(fromString: latex, error: &error), error == nil,
let displayList = MTTypesetter.createLineForMathList(mathList, font: mtfont, style: currentStyle) else {
return (error, nil)
}
intrinsicContentSize = intrinsicContentSize(displayList)
displayList.textColor = textColor
let size = intrinsicContentSize
layoutImage(size: size, displayList: displayList)
#if os(iOS)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { rendererContext in
rendererContext.cgContext.saveGState()
rendererContext.cgContext.concatenate(.flippedVertically(size.height))
displayList.draw(rendererContext.cgContext)
rendererContext.cgContext.restoreGState()
}
return (nil, image)
#endif
#if os(macOS)
let image = NSImage(size: size, flipped: false) { bounds in
guard let context = NSGraphicsContext.current?.cgContext else { return false }
context.saveGState()
displayList.draw(context)
context.restoreGState()
return true
}
return (nil, image)
#endif
}
}
private extension CGAffineTransform {
static func flippedVertically(_ height: CGFloat) -> CGAffineTransform {
var transform = CGAffineTransform(scaleX: 1, y: -1)
transform = transform.translatedBy(x: 0, y: -height)
return transform
}
}

View File

@@ -1,310 +0,0 @@
//
// MathTable.swift
//
//
// Created by Peter Tang on 11/9/2023.
//
import Foundation
import CoreText
/** This class represents the Math table of an open type font.
The math table is documented here: https://www.microsoft.com/typography/otspec/math.htm
How the constants in this class affect the display is documented here:
http://www.tug.org/TUGboat/tb30-1/tb94vieth.pdf
Note: We don't parse the math table from the open type font. Rather we parse it
in python and convert it to a .plist file which is easily consumed by this class.
This approach is preferable to spending an inordinate amount of time figuring out
how to parse the returned NSData object using the open type rules.
Remark: This class is not meant to be used outside of this library.
*/
internal struct MathTable {
let kConstants = "constants"
let font: MathFont
private let unitsPerEm: UInt
private let fontSize: CGFloat
weak var fontMathTable: NSDictionary?
init(withFont font: MathFont, fontSize: CGFloat, unitsPerEm: UInt) {
self.font = font
self.unitsPerEm = unitsPerEm
self.fontSize = fontSize
self.fontMathTable = font.mathTable()
}
func fontUnitsToPt(_ fontUnits: Int) -> CGFloat {
CGFloat(fontUnits) * fontSize / CGFloat(unitsPerEm)
}
func constantFromTable(_ constName: String) -> CGFloat {
guard let consts = fontMathTable?[kConstants] as? NSDictionary, let val = consts[constName] as? NSNumber else {
fatalError("\(#function) unable to extract \(constName) from plist")
}
return fontUnitsToPt(val.intValue)
}
func percentFromTable(_ percentName: String) -> CGFloat {
guard let consts = fontMathTable?[kConstants] as? NSDictionary, let val = consts[percentName] as? NSNumber else {
fatalError("\(#function) unable to extract \(percentName) from plist")
}
return CGFloat(val.floatValue) / 100
}
/// Math Font Metrics from the opentype specification
// MARK: - Fractions
var fractionNumeratorDisplayStyleShiftUp:CGFloat { constantFromTable("FractionNumeratorDisplayStyleShiftUp") } // \sigma_8 in TeX
var fractionNumeratorShiftUp:CGFloat { constantFromTable("FractionNumeratorShiftUp") } // \sigma_9 in TeX
var fractionDenominatorDisplayStyleShiftDown:CGFloat { constantFromTable("FractionDenominatorDisplayStyleShiftDown") } // \sigma_11 in TeX
var fractionDenominatorShiftDown:CGFloat { constantFromTable("FractionDenominatorShiftDown") } // \sigma_12 in TeX
var fractionNumeratorDisplayStyleGapMin:CGFloat { constantFromTable("FractionNumDisplayStyleGapMin") } // 3 * \xi_8 in TeX
var fractionNumeratorGapMin:CGFloat { constantFromTable("FractionNumeratorGapMin") } // \xi_8 in TeX
var fractionDenominatorDisplayStyleGapMin:CGFloat { constantFromTable("FractionDenomDisplayStyleGapMin") } // 3 * \xi_8 in TeX
var fractionDenominatorGapMin:CGFloat { constantFromTable("FractionDenominatorGapMin") } // \xi_8 in TeX
var fractionRuleThickness:CGFloat { constantFromTable("FractionRuleThickness") } // \xi_8 in TeX
var skewedFractionHorizonalGap:CGFloat { constantFromTable("SkewedFractionHorizontalGap") } // \sigma_20 in TeX
var skewedFractionVerticalGap:CGFloat { constantFromTable("SkewedFractionVerticalGap") } // \sigma_21 in TeX
// MARK: - Non-standard
/// FractionDelimiterSize and FractionDelimiterDisplayStyleSize are not constants
/// specified in the OpenType Math specification. Rather these are proposed LuaTeX extensions
/// for the TeX parameters \sigma_20 (delim1) and \sigma_21 (delim2). Since these do not
/// exist in the fonts that we have, we use the same approach as LuaTeX and use the fontSize
/// to determine these values. The constants used are the same as LuaTeX and KaTeX and match the
/// metrics values of the original TeX fonts.
/// Note: An alternative approach is to use DelimitedSubFormulaMinHeight for \sigma21 and use a factor
/// of 2 to get \sigma 20 as proposed in Vieth paper.
/// The XeTeX implementation sets \sigma21 = fontSize and \sigma20 = DelimitedSubFormulaMinHeight which
/// will produce smaller delimiters.
/// Of all the approaches we've implemented LuaTeX's approach since it mimics LaTeX most accurately.
var fractionDelimiterSize: CGFloat { 1.01 * fontSize }
/// Modified constant from 2.4 to 2.39, it matches KaTeX and looks better.
var fractionDelimiterDisplayStyleSize: CGFloat { 2.39 * fontSize }
// MARK: - Stacks
var stackTopDisplayStyleShiftUp:CGFloat { constantFromTable("StackTopDisplayStyleShiftUp") } // \sigma_8 in TeX
var stackTopShiftUp:CGFloat { constantFromTable("StackTopShiftUp") } // \sigma_10 in TeX
var stackDisplayStyleGapMin:CGFloat { constantFromTable("StackDisplayStyleGapMin") } // 7 \xi_8 in TeX
var stackGapMin:CGFloat { constantFromTable("StackGapMin") } // 3 \xi_8 in TeX
var stackBottomDisplayStyleShiftDown:CGFloat { constantFromTable("StackBottomDisplayStyleShiftDown") } // \sigma_11 in TeX
var stackBottomShiftDown:CGFloat { constantFromTable("StackBottomShiftDown") } // \sigma_12 in TeX
var stretchStackBottomShiftDown:CGFloat { constantFromTable("StretchStackBottomShiftDown") }
var stretchStackGapAboveMin:CGFloat { constantFromTable("StretchStackGapAboveMin") }
var stretchStackGapBelowMin:CGFloat { constantFromTable("StretchStackGapBelowMin") }
var stretchStackTopShiftUp:CGFloat { constantFromTable("StretchStackTopShiftUp") }
// MARK: - super/sub scripts
var superscriptShiftUp:CGFloat { constantFromTable("SuperscriptShiftUp") } // \sigma_13, \sigma_14 in TeX
var superscriptShiftUpCramped:CGFloat { constantFromTable("SuperscriptShiftUpCramped") } // \sigma_15 in TeX
var subscriptShiftDown:CGFloat { constantFromTable("SubscriptShiftDown") } // \sigma_16, \sigma_17 in TeX
var superscriptBaselineDropMax:CGFloat { constantFromTable("SuperscriptBaselineDropMax") } // \sigma_18 in TeX
var subscriptBaselineDropMin:CGFloat { constantFromTable("SubscriptBaselineDropMin") } // \sigma_19 in TeX
var superscriptBottomMin:CGFloat { constantFromTable("SuperscriptBottomMin") } // 1/4 \sigma_5 in TeX
var subscriptTopMax:CGFloat { constantFromTable("SubscriptTopMax") } // 4/5 \sigma_5 in TeX
var subSuperscriptGapMin:CGFloat { constantFromTable("SubSuperscriptGapMin") } // 4 \xi_8 in TeX
var superscriptBottomMaxWithSubscript:CGFloat { constantFromTable("SuperscriptBottomMaxWithSubscript") } // 4/5 \sigma_5 in TeX
var spaceAfterScript:CGFloat { constantFromTable("SpaceAfterScript") }
// MARK: - radicals
var radicalExtraAscender:CGFloat { constantFromTable("RadicalExtraAscender") } // \xi_8 in Tex
var radicalRuleThickness:CGFloat { constantFromTable("RadicalRuleThickness") } // \xi_8 in Tex
var radicalDisplayStyleVerticalGap:CGFloat { constantFromTable("RadicalDisplayStyleVerticalGap") } // \xi_8 + 1/4 \sigma_5 in Tex
var radicalVerticalGap:CGFloat { constantFromTable("RadicalVerticalGap") } // 5/4 \xi_8 in Tex
var radicalKernBeforeDegree:CGFloat { constantFromTable("RadicalKernBeforeDegree") } // 5 mu in Tex
var radicalKernAfterDegree:CGFloat { constantFromTable("RadicalKernAfterDegree") } // -10 mu in Tex
var radicalDegreeBottomRaisePercent:CGFloat { percentFromTable("RadicalDegreeBottomRaisePercent") } // 60% in Tex
// MARK: - Limits
var upperLimitBaselineRiseMin:CGFloat { constantFromTable("UpperLimitBaselineRiseMin") } // \xi_11 in TeX
var upperLimitGapMin:CGFloat { constantFromTable("UpperLimitGapMin") } // \xi_9 in TeX
var lowerLimitGapMin:CGFloat { constantFromTable("LowerLimitGapMin") } // \xi_10 in TeX
var lowerLimitBaselineDropMin:CGFloat { constantFromTable("LowerLimitBaselineDropMin") } // \xi_12 in TeX
var limitExtraAscenderDescender:CGFloat { 0 } // \xi_13 in TeX, not present in OpenType so we always set it to 0.
// MARK: - Underline
var underbarVerticalGap:CGFloat { constantFromTable("UnderbarVerticalGap") } // 3 \xi_8 in TeX
var underbarRuleThickness:CGFloat { constantFromTable("UnderbarRuleThickness") } // \xi_8 in TeX
var underbarExtraDescender:CGFloat { constantFromTable("UnderbarExtraDescender") } // \xi_8 in TeX
// MARK: - Overline
var overbarVerticalGap:CGFloat { constantFromTable("OverbarVerticalGap") } // 3 \xi_8 in TeX
var overbarRuleThickness:CGFloat { constantFromTable("OverbarRuleThickness") } // \xi_8 in TeX
var overbarExtraAscender:CGFloat { constantFromTable("OverbarExtraAscender") } // \xi_8 in TeX
// MARK: - Constants
var axisHeight:CGFloat { constantFromTable("AxisHeight") } // \sigma_22 in TeX
var scriptScaleDown:CGFloat { percentFromTable("ScriptPercentScaleDown") }
var scriptScriptScaleDown:CGFloat { percentFromTable("ScriptScriptPercentScaleDown") }
var mathLeading:CGFloat { constantFromTable("MathLeading") }
var delimitedSubFormulaMinHeight:CGFloat { constantFromTable("DelimitedSubFormulaMinHeight") }
// MARK: - Accent
var accentBaseHeight:CGFloat { constantFromTable("AccentBaseHeight") } // \fontdimen5 in TeX (x-height)
var flattenedAccentBaseHeight:CGFloat { constantFromTable("FlattenedAccentBaseHeight") }
// MARK: - Variants
let kVertVariants = "v_variants"
let kHorizVariants = "h_variants"
/** Returns an Array of all the vertical variants of the glyph if any. If
there are no variants for the glyph, the array contains the given glyph. */
func getVerticalVariantsForGlyph( _ glyph: CGGlyph) -> [NSNumber?] {
guard let variants = fontMathTable?[kVertVariants] as? NSDictionary else {
fatalError("\(#function) unable to extract \(glyph) from plist")
}
return self.getVariantsForGlyph(glyph, inDictionary: variants)
}
/** Returns an Array of all the horizontal variants of the glyph if any. If
there are no variants for the glyph, the array contains the given glyph. */
func getHorizontalVariantsForGlyph( _ glyph: CGGlyph) -> [NSNumber?] {
guard let variants = fontMathTable?[kHorizVariants] as? NSDictionary else {
fatalError("\(#function) unable to extract \(glyph) from plist")
}
return self.getVariantsForGlyph(glyph, inDictionary:variants)
}
func getVariantsForGlyph(_ glyph: CGGlyph, inDictionary variants: NSDictionary) -> [NSNumber?] {
let glyphName = font.get(nameForGlyph: glyph)
let variantGlyphs = variants[glyphName] as? NSArray
var glyphArray = [NSNumber]()
if variantGlyphs == nil || variantGlyphs?.count == 0, let glyph = font.get(glyphWithName: glyphName) {
// There are no extra variants, so just add the current glyph to it.
glyphArray.append(NSNumber(value:glyph))
return glyphArray
} else if let variantGlyphs = variantGlyphs {
for gvn in variantGlyphs {
if let glyphVariantName = gvn as? String, let variantGlyph = font.get(glyphWithName: glyphVariantName) {
glyphArray.append(NSNumber(value:variantGlyph))
}
}
}
return glyphArray
}
/** Returns a larger vertical variant of the given glyph if any.
If there is no larger version, this returns the current glyph.
*/
func getLargerGlyph(_ glyph:CGGlyph) -> CGGlyph {
let variants = fontMathTable?[kVertVariants] as? NSDictionary
let glyphName = font.get(nameForGlyph: glyph)
let variantGlyphs = variants![glyphName] as? NSArray
if variantGlyphs == nil || variantGlyphs?.count == 0 {
// There are no extra variants, so just returnt the current glyph.
return glyph
}
// Find the first variant with a different name.
for gvn in variantGlyphs! {
if let glyphVariantName = gvn as? String,
glyphVariantName != glyphName,
let variantGlyph = font.get(glyphWithName: glyphVariantName) {
return variantGlyph
}
}
// We did not find any variants of this glyph so return it.
return glyph;
}
// MARK: - Italic Correction
let kItalic = "italic"
/** Returns the italic correction for the given glyph if any. If there
isn't any this returns 0. */
func getItalicCorrection(_ glyph: CGGlyph) -> CGFloat {
let italics = fontMathTable?[kItalic] as? NSDictionary
let glyphName = font.get(nameForGlyph: glyph)
let val = italics![glyphName] as? NSNumber
// if val is nil, this returns 0.
return fontUnitsToPt(val?.intValue ?? 0)
}
// MARK: - Accents
let kAccents = "accents"
/** Returns the adjustment to the top accent for the given glyph if any.
If there isn't any this returns -1. */
func getTopAccentAdjustment(_ glyph: CGGlyph) -> CGFloat {
var glyph = glyph
let accents = fontMathTable?[kAccents] as? NSDictionary
let glyphName = font.get(nameForGlyph: glyph)
let val = accents![glyphName] as? NSNumber
if let val = val {
return self.fontUnitsToPt(val.intValue)
} else {
// If no top accent is defined then it is the center of the advance width.
var advances = CGSize.zero
guard let ctFont = font.ctFont(withSize: fontSize) else {
fatalError("\(#function) unable to obtain ctFont resource name: \(font.rawValue) with size \(fontSize)")
}
CTFontGetAdvancesForGlyphs(ctFont, .horizontal, &glyph, &advances, 1)
return advances.width/2
}
}
// MARK: - Glyph Construction
/** Minimum overlap of connecting glyphs during glyph construction */
var minConnectorOverlap:CGFloat { constantFromTable("MinConnectorOverlap") }
let kVertAssembly = "v_assembly"
let kAssemblyParts = "parts"
/** Returns an array of the glyph parts to be used for constructing vertical variants
of this glyph. If there is no glyph assembly defined, returns an empty array. */
func getVerticalGlyphAssembly(forGlyph glyph:CGGlyph) -> [GlyphPart] {
let assemblyTable = fontMathTable?[kVertAssembly] as? NSDictionary
let glyphName = font.get(nameForGlyph: glyph)
guard let assemblyInfo = assemblyTable?[glyphName] as? NSDictionary,
let parts = assemblyInfo[kAssemblyParts] as? NSArray else {
// No vertical assembly defined for glyph
// parts should always have been defined, but if it isn't return nil
return []
}
var rv = [GlyphPart]()
for part in parts {
guard let partInfo = part as? NSDictionary, let glyph = font.get(glyphWithName: glyphName) else { continue }
var part = GlyphPart(glyph: glyph)
if let adv = partInfo["advance"] as? NSNumber,
let end = partInfo["endConnector"] as? NSNumber,
let start = partInfo["startConnector"] as? NSNumber,
let ext = partInfo["extender"] as? NSNumber,
let partInfoGlyphName = partInfo["glyph"] as? String, partInfoGlyphName == glyphName {
part.fullAdvance = fontUnitsToPt(adv.intValue)
part.endConnectorLength = fontUnitsToPt(end.intValue)
part.startConnectorLength = fontUnitsToPt(start.intValue)
part.isExtender = ext.boolValue
rv.append(part)
}
}
return rv
}
}
extension MathTable {
struct GlyphPart {
/// The glyph that represents this part
var glyph: CGGlyph
/// Full advance width/height for this part, in the direction of the extension in points.
var fullAdvance: CGFloat = 0
/// Advance width/ height of the straight bar connector material at the beginning of the glyph in points.
var startConnectorLength: CGFloat = 0
/// Advance width/ height of the straight bar connector material at the end of the glyph in points.
var endConnectorLength: CGFloat = 0
/// If this part is an extender. If set, the part can be skipped or repeated.
var isExtender: Bool = false
}
}