Cache fonts in attributed string converter
This commit is contained in:
parent
134803b72d
commit
1c461041c1
|
@ -11,26 +11,40 @@ import UIKit
|
|||
import AppKit
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
private typealias PlatformFont = UIFont
|
||||
#elseif os(macOS)
|
||||
private typealias PlatformFont = NSFont
|
||||
#endif
|
||||
|
||||
public struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
|
||||
private let configuration: AttributedStringConverterConfiguration
|
||||
private var tokenizer: Tokenizer<String.Iterator>
|
||||
private let str = NSMutableAttributedString()
|
||||
private var fontCache: [FontTrait: PlatformFont] = [:]
|
||||
|
||||
private var tokenizer: Tokenizer<String.Iterator>!
|
||||
private var str: NSMutableAttributedString!
|
||||
|
||||
private var actionStack: InlineArray3<ElementAction> = []
|
||||
private var styleStack: InlineArray3<Style> = []
|
||||
// The current run of text w/o styles changing
|
||||
private var currentRun: String = ""
|
||||
|
||||
public init(html: String, configuration: AttributedStringConverterConfiguration) where Callbacks == DefaultCallbacks {
|
||||
self.init(html: html, configuration: configuration, callbacks: DefaultCallbacks.self)
|
||||
public init(configuration: AttributedStringConverterConfiguration) where Callbacks == DefaultCallbacks {
|
||||
self.init(configuration: configuration, callbacks: DefaultCallbacks.self)
|
||||
}
|
||||
|
||||
public init(html: String, configuration: AttributedStringConverterConfiguration, callbacks _: Callbacks.Type = Callbacks.self) {
|
||||
public init(configuration: AttributedStringConverterConfiguration, callbacks _: Callbacks.Type = Callbacks.self) {
|
||||
self.configuration = configuration
|
||||
self.tokenizer = Tokenizer(chars: html.makeIterator())
|
||||
}
|
||||
|
||||
public mutating func convert() -> NSAttributedString {
|
||||
public mutating func convert(html: String) -> NSAttributedString {
|
||||
tokenizer = Tokenizer(chars: html.makeIterator())
|
||||
str = NSMutableAttributedString()
|
||||
|
||||
actionStack = []
|
||||
styleStack = []
|
||||
currentRun = ""
|
||||
|
||||
while let token = tokenizer.next() {
|
||||
switch token {
|
||||
case .character(let c):
|
||||
|
@ -215,7 +229,7 @@ public struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
|
|||
}
|
||||
|
||||
var attributes = [NSAttributedString.Key: Any]()
|
||||
var currentFontTraits = Set<FontTrait>()
|
||||
var currentFontTraits: FontTrait = []
|
||||
for style in styleStack {
|
||||
switch style {
|
||||
case .bold:
|
||||
|
@ -238,23 +252,7 @@ public struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
|
|||
}
|
||||
}
|
||||
|
||||
let baseFont = currentFontTraits.contains(.monospace) ? configuration.monospaceFont : configuration.font
|
||||
var descriptor = baseFont.fontDescriptor
|
||||
if currentFontTraits.contains(.bold) && currentFontTraits.contains(.italic),
|
||||
let boldItalic = descriptor.withSymbolicTraits([.traitBold, .traitItalic]) {
|
||||
descriptor = boldItalic
|
||||
} else if currentFontTraits.contains(.bold),
|
||||
let bold = descriptor.withSymbolicTraits(.traitBold) {
|
||||
descriptor = bold
|
||||
} else if currentFontTraits.contains(.italic),
|
||||
let italic = descriptor.withSymbolicTraits(.traitItalic) {
|
||||
descriptor = italic
|
||||
}
|
||||
#if os(iOS)
|
||||
attributes[.font] = UIFont(descriptor: descriptor, size: 0)
|
||||
#elseif os(macOS)
|
||||
attributes[.font] = NSFont(descriptor: descriptor, size: 0)
|
||||
#endif
|
||||
attributes[.font] = getFont(traits: currentFontTraits)
|
||||
|
||||
if !attributes.keys.contains(.paragraphStyle) {
|
||||
attributes[.paragraphStyle] = configuration.paragraphStyle
|
||||
|
@ -263,6 +261,28 @@ public struct AttributedStringConverter<Callbacks: AttributedStringCallbacks> {
|
|||
str.append(NSAttributedString(string: currentRun, attributes: attributes))
|
||||
currentRun = ""
|
||||
}
|
||||
|
||||
private mutating func getFont(traits: FontTrait) -> PlatformFont? {
|
||||
if let cached = fontCache[traits] {
|
||||
return cached
|
||||
}
|
||||
|
||||
let baseFont = traits.contains(.monospace) ? configuration.monospaceFont : configuration.font
|
||||
var descriptor = baseFont.fontDescriptor
|
||||
if traits.contains(.bold) && traits.contains(.italic),
|
||||
let boldItalic = descriptor.withSymbolicTraits([.traitBold, .traitItalic]) {
|
||||
descriptor = boldItalic
|
||||
} else if traits.contains(.bold),
|
||||
let bold = descriptor.withSymbolicTraits(.traitBold) {
|
||||
descriptor = bold
|
||||
} else if traits.contains(.italic),
|
||||
let italic = descriptor.withSymbolicTraits(.traitItalic) {
|
||||
descriptor = italic
|
||||
}
|
||||
let font = PlatformFont(descriptor: descriptor, size: 0)
|
||||
fontCache[traits] = font
|
||||
return font
|
||||
}
|
||||
}
|
||||
|
||||
public protocol AttributedStringCallbacks {
|
||||
|
@ -338,10 +358,16 @@ private extension NSFontDescriptor.SymbolicTraits {
|
|||
}
|
||||
#endif
|
||||
|
||||
private enum FontTrait {
|
||||
case bold
|
||||
case italic
|
||||
case monospace
|
||||
private struct FontTrait: OptionSet, Hashable {
|
||||
static let bold = FontTrait(rawValue: 1 << 0)
|
||||
static let italic = FontTrait(rawValue: 1 << 1)
|
||||
static let monospace = FontTrait(rawValue: 1 << 2)
|
||||
|
||||
let rawValue: Int
|
||||
|
||||
init(rawValue: Int) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
}
|
||||
|
||||
private enum Style {
|
||||
|
|
|
@ -45,8 +45,8 @@ final class AttributedStringConverterTests: XCTestCase {
|
|||
color: .black,
|
||||
paragraphStyle: .default
|
||||
)
|
||||
var converter = AttributedStringConverter<Callbacks>(html: html, configuration: config)
|
||||
return converter.convert()
|
||||
var converter = AttributedStringConverter<Callbacks>(configuration: config)
|
||||
return converter.convert(html: html)
|
||||
}
|
||||
|
||||
func testConvertBR() {
|
||||
|
|
Loading…
Reference in New Issue