// // HTMLConverter.swift // Tusker // // Created by Shadowfacts on 12/3/22. // Copyright © 2022 Shadowfacts. All rights reserved. // import UIKit import HTMLStreamer import WebURL import WebURLFoundationExtras class HTMLConverter { static let defaultFont = UIFont.systemFont(ofSize: 17) static let defaultMonospaceFont = UIFont.monospacedSystemFont(ofSize: 17, weight: .regular) static let defaultColor = UIColor.label static let defaultParagraphStyle: NSParagraphStyle = { let style = NSMutableParagraphStyle() // 2 points is enough that it doesn't make things look weirdly spaced out, but leaves a slight gap when there are multiple lines of emojis style.lineSpacing = 2 return style }() private let converter: AttributedStringConverter init(font: UIFont, monospaceFont: UIFont, color: UIColor, paragraphStyle: NSParagraphStyle) { let config = AttributedStringConverterConfiguration(font: font, monospaceFont: monospaceFont, color: color, paragraphStyle: paragraphStyle) self.converter = AttributedStringConverter(configuration: config) } func convert(_ html: String) -> NSAttributedString { converter.convert(html: html) } } extension HTMLConverter { // note: this is duplicated in NotificationExtension struct Callbacks: HTMLConversionCallbacks { static func makeURL(string: String) -> URL? { // Converting WebURL to URL is a small but non-trivial expense (since it works by // serializing the WebURL as a string and then having Foundation parse it again), // so, if available, use the system parser which doesn't require another round trip. if #available(iOS 16.0, macOS 13.0, *), let url = try? URL.ParseStrategy().parse(string) { url } else if let web = WebURL(string), let url = URL(web) { url } else { URL(string: string) } } static func elementAction(name: String, attributes: [Attribute]) -> ElementAction { guard name == "span" else { return .default } let clazz = attributes.attributeValue(for: "class") if clazz == "invisible" { return .skip } else if clazz == "ellipsis" { return .append("…") } else { return .default } } } }