Telegram-iOS/third-party/SwiftMath/Sources/MathBundle/MTFontMathTableV2.swift
2026-04-27 18:45:30 +02:00

194 lines
8.8 KiB
Swift
Executable file

//
// MTFontMathTableV2.swift
//
//
// Created by Peter Tang on 15/9/2023.
//
import Foundation
import CoreGraphics
import CoreText
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, unitsPerEm: UInt) {
self.mathFont = mathFont
self.fontSize = size
self.unitsPerEm = unitsPerEm
mTable = mathFont.rawMathTable()
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
}
/** 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. */
override func getVerticalVariantsForGlyph(_ glyph: CGGlyph) -> [NSNumber?] {
guard let variants = mTable[kVertVariants] as? NSDictionary else { return [] }
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. */
override func getHorizontalVariantsForGlyph(_ glyph: CGGlyph) -> [NSNumber?] {
guard let variants = mTable[kHorizVariants] as? NSDictionary else { return [] }
return self.getVariantsForGlyph(glyph, inDictionary:variants)
}
override func getVariantsForGlyph(_ glyph: CGGlyph, inDictionary variants: NSDictionary) -> [NSNumber?] {
let font = mathFont.mtfont(size: fontSize)
let glyphName = font.get(nameForGlyph: glyph)
var glyphArray = [NSNumber]()
let variantGlyphs = variants[glyphName] as? NSArray
guard let variantGlyphs = variantGlyphs, variantGlyphs.count != .zero else {
// There are no extra variants, so just add the current glyph to it.
let glyph = font.get(glyphWithName: glyphName)
glyphArray.append(NSNumber(value:glyph))
return glyphArray
}
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.
- Parameter glyph: The glyph to find a larger variant for
- Parameter forDisplayStyle: If true, selects the largest appropriate variant for display style.
If false, selects the next larger variant (incremental sizing).
- Returns: A larger glyph variant, or the original glyph if no variants exist
*/
override func getLargerGlyph(_ glyph: CGGlyph, forDisplayStyle: Bool = false) -> CGGlyph {
let font = mathFont.mtfont(size: fontSize)
let glyphName = font.get(nameForGlyph: glyph)
guard let variants = mTable[kVertVariants] as? NSDictionary,
let variantGlyphs = variants[glyphName] as? NSArray, variantGlyphs.count != .zero else {
// There are no extra variants, so just returnt the current glyph.
return glyph
}
if forDisplayStyle {
// For display style, select a large variant suitable for mathematical display mode
// Display integrals should be significantly larger (~2.2em) for visual prominence
let count = variantGlyphs.count
// Strategy: Use the largest variant, but avoid extreme sizes for fonts with many variants
let targetIndex: Int
if count <= 2 {
// Small variant list: use the last one (e.g., integral.size1 at ~2.2em)
targetIndex = count - 1
} else if count <= 4 {
// Medium variant list: use second-to-last to avoid extremes
targetIndex = count - 2
} else {
// Large variant list (like texgyretermes with 6 variants):
// Use variant at ~60% position to get appropriate display size (~2.0em)
// For 7 variants (0-6), this gives index 4
targetIndex = min(count - 2, Int(Double(count) * 0.6))
}
if let glyphVariantName = variantGlyphs[targetIndex] as? String {
let variantGlyph = font.get(glyphWithName: glyphVariantName)
return variantGlyph
}
} else {
// Text/inline style: use incremental sizing for moderate enlargement
// 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
}
/** Returns the italic correction for the given glyph if any. If there
isn't any this returns 0. */
override func getItalicCorrection(_ glyph: CGGlyph) -> CGFloat {
let font = mathFont.mtfont(size: fontSize)
let glyphName = font.get(nameForGlyph: glyph)
guard let italics = mTable[kItalic] as? NSDictionary, let val = italics[glyphName] as? NSNumber else {
return .zero
}
// if val is nil, this returns 0.
return fontUnitsToPt(val.intValue)
}
override func getTopAccentAdjustment(_ glyph: CGGlyph) -> CGFloat {
let font = mathFont.mtfont(size: fontSize)
let glyphName = font.get(nameForGlyph: glyph)
guard let accents = mTable[kAccents] as? NSDictionary, let val = accents[glyphName] as? NSNumber else {
// If no top accent is defined then it is the center of the advance width.
var glyph = glyph
var advances = CGSize.zero
CTFontGetAdvancesForGlyphs(font.ctFont, .horizontal, &glyph, &advances, 1)
return advances.width/2
}
return fontUnitsToPt(val.intValue)
}
override func getVerticalGlyphAssembly(forGlyph glyph: CGGlyph) -> [GlyphPart] {
let font = mathFont.mtfont(size: fontSize)
let glyphName = font.get(nameForGlyph: glyph)
guard let assemblyTable = mTable[kVertAssembly] as? NSDictionary,
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 adv = partInfo["advance"] as? NSNumber,
let end = partInfo["endConnector"] as? NSNumber,
let start = partInfo["startConnector"] as? NSNumber,
let ext = partInfo["extender"] as? NSNumber,
let glyphName = partInfo["glyph"] as? String else { continue }
let fullAdvance = fontUnitsToPt(adv.intValue)
let endConnectorLength = fontUnitsToPt(end.intValue)
let startConnectorLength = fontUnitsToPt(start.intValue)
let isExtender = ext.boolValue
let glyph = font.get(glyphWithName: glyphName)
let part = GlyphPart(glyph: glyph, fullAdvance: fullAdvance,
startConnectorLength: startConnectorLength,
endConnectorLength: endConnectorLength,
isExtender: isExtender)
rv.append(part)
}
return rv
}
}