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