mirror of
https://github.com/Ranchero-Software/NetNewsWire
synced 2025-08-12 06:26:36 +00:00
Move NSAttributedString+NetNewsWire into Shared/Extensions
This commit is contained in:
134
Shared/Extensions/NSAttributedString+NetNewsWire.swift
Normal file
134
Shared/Extensions/NSAttributedString+NetNewsWire.swift
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// NSAttributedString+NetNewsWire.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Nate Weaver on 2020-04-07.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
#if canImport(AppKit)
|
||||
import AppKit
|
||||
typealias Font = NSFont
|
||||
typealias FontDescriptor = NSFontDescriptor
|
||||
typealias Color = NSColor
|
||||
|
||||
private let boldTrait = NSFontDescriptor.SymbolicTraits.bold
|
||||
private let italicTrait = NSFontDescriptor.SymbolicTraits.italic
|
||||
private let monoSpaceTrait = NSFontDescriptor.SymbolicTraits.monoSpace
|
||||
#else
|
||||
import UIKit
|
||||
typealias Font = UIFont
|
||||
typealias FontDescriptor = UIFontDescriptor
|
||||
typealias Color = UIColor
|
||||
|
||||
private let boldTrait = UIFontDescriptor.SymbolicTraits.traitBold
|
||||
private let italicTrait = UIFontDescriptor.SymbolicTraits.traitItalic
|
||||
private let monoSpaceTrait = UIFontDescriptor.SymbolicTraits.traitMonoSpace
|
||||
#endif
|
||||
|
||||
extension NSAttributedString {
|
||||
|
||||
/// Adds a font and color to an attributed string.
|
||||
///
|
||||
/// Additionally converts super-/subscript runs to super-/subscripted font variants
|
||||
/// and converts bold text to heavy if the base font is semibold.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - baseFont: The font to add.
|
||||
/// - color: The color to add.
|
||||
func adding(font baseFont: Font, color: Color? = nil) -> NSAttributedString {
|
||||
let mutable = self.mutableCopy() as! NSMutableAttributedString
|
||||
let fullRange = NSRange(location: 0, length: mutable.length)
|
||||
|
||||
if let color = color {
|
||||
mutable.addAttribute(.foregroundColor, value: color as Any, range: fullRange)
|
||||
}
|
||||
|
||||
let size = baseFont.pointSize
|
||||
let baseDescriptor = baseFont.fontDescriptor
|
||||
let baseSymbolicTraits = baseDescriptor.symbolicTraits
|
||||
|
||||
let baseTraits = baseDescriptor.object(forKey: .traits) as! [FontDescriptor.TraitKey: Any]
|
||||
let baseWeight = baseTraits[.weight] as! Font.Weight
|
||||
|
||||
mutable.enumerateAttribute(.font, in: fullRange, options: []) { (font: Any?, range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) in
|
||||
guard let font = font as? Font else { return }
|
||||
|
||||
var newSymbolicTraits = baseSymbolicTraits
|
||||
|
||||
let symbolicTraits = font.fontDescriptor.symbolicTraits
|
||||
|
||||
if symbolicTraits.contains(italicTrait) {
|
||||
newSymbolicTraits.insert(italicTrait)
|
||||
}
|
||||
|
||||
if symbolicTraits.contains(monoSpaceTrait) {
|
||||
newSymbolicTraits.insert(monoSpaceTrait)
|
||||
}
|
||||
|
||||
#if canImport(AppKit)
|
||||
var descriptor = baseDescriptor.withSymbolicTraits(newSymbolicTraits)
|
||||
#else
|
||||
var descriptor = baseDescriptor.withSymbolicTraits(newSymbolicTraits)!
|
||||
#endif
|
||||
|
||||
if symbolicTraits.contains(boldTrait) {
|
||||
// If the base font is semibold (as timeline titles are), make the "bold"
|
||||
// text heavy for better contrast.
|
||||
|
||||
if baseWeight == .semibold {
|
||||
let traits: [FontDescriptor.TraitKey: Any] = [.weight: Font.Weight.heavy]
|
||||
let attributes: [FontDescriptor.AttributeName: Any] = [.traits: traits]
|
||||
descriptor = descriptor.addingAttributes(attributes)
|
||||
}
|
||||
}
|
||||
|
||||
let newFont = Font(descriptor: descriptor, size: size)
|
||||
|
||||
mutable.addAttribute(.font, value: newFont as Any, range: range)
|
||||
}
|
||||
|
||||
// make sup/sub smaller. `Key("NSSupeScript")` is used here because `.superscript`
|
||||
// isn't defined in UIKit.
|
||||
let superscriptAttribute = Key("NSSuperScript")
|
||||
|
||||
mutable.enumerateAttributes(in: fullRange, options: []) { (attributes: [Key : Any], range: NSRange, stop: UnsafeMutablePointer<ObjCBool>) in
|
||||
guard let superscript = attributes[superscriptAttribute] as? Int else {
|
||||
return
|
||||
}
|
||||
|
||||
if superscript != 0 {
|
||||
let font = mutable.attribute(.font, at: range.location, effectiveRange: nil) as! Font
|
||||
|
||||
// There's some discrepancy here: The raw value of AppKit's .typeIdentifier is UIKit's .featureIdentifier,
|
||||
// and AppKit's .selectorIdentifier is UIKit's .typeIdentifier
|
||||
#if canImport(AppKit)
|
||||
let features: [FontDescriptor.FeatureKey: Any] = [.typeIdentifier: kVerticalPositionType, .selectorIdentifier: superscript > 0 ? kSuperiorsSelector : kInferiorsSelector]
|
||||
#else
|
||||
let features: [FontDescriptor.FeatureKey: Any] = [.featureIdentifier: kVerticalPositionType, .typeIdentifier: superscript > 0 ? kSuperiorsSelector : kInferiorsSelector]
|
||||
#endif
|
||||
let attributes: [FontDescriptor.AttributeName: Any] = [.featureSettings: [features]]
|
||||
let descriptor = font.fontDescriptor.addingAttributes(attributes)
|
||||
|
||||
let newFont = Font(descriptor: descriptor, size: font.pointSize)
|
||||
|
||||
let newAttributes: [NSAttributedString.Key: Any] = [
|
||||
.font: newFont as Any,
|
||||
superscriptAttribute: 0,
|
||||
]
|
||||
|
||||
mutable.addAttributes(newAttributes, range: range)
|
||||
}
|
||||
}
|
||||
|
||||
return mutable.copy() as! NSAttributedString
|
||||
}
|
||||
|
||||
/// Creates an attributed string from HTML.
|
||||
convenience init(html: String) {
|
||||
let data = html.data(using: .utf8)!
|
||||
let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [.characterEncoding: String.Encoding.utf8.rawValue, .documentType: NSAttributedString.DocumentType.html]
|
||||
try! self.init(data: data, options: options, documentAttributes: nil)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user