From f786e73eff00402fe44d2dd0218c8d22fa593d53 Mon Sep 17 00:00:00 2001 From: kean Date: Sat, 14 Jan 2023 12:36:00 -0500 Subject: [PATCH] Optimize HTML export --- Sources/PulseUI/Helpers/TextUtilities.swift | 42 ++++++++++++------- .../TextRendererPerformanceTests.swift | 34 ++++++++++----- 2 files changed, 51 insertions(+), 25 deletions(-) diff --git a/Sources/PulseUI/Helpers/TextUtilities.swift b/Sources/PulseUI/Helpers/TextUtilities.swift index 7e7ff1e4..716c7f5b 100644 --- a/Sources/PulseUI/Helpers/TextUtilities.swift +++ b/Sources/PulseUI/Helpers/TextUtilities.swift @@ -18,32 +18,44 @@ enum TextUtilities { let data = try string.data(from: range, documentAttributes: [ .documentType: NSAttributedString.DocumentType.html ]) - guard var html = String(data: data, encoding: .utf8) else { + guard let html = NSMutableString(data: data, encoding: NSUTF8StringEncoding) else { return data } - func insert(_ string: String, at index: String.Index) { - html.insert(contentsOf: "\n\(string)", at: index) + func getRange(of string: String) -> NSRange? { + let range = html.range(of: string) + return range.location == NSNotFound ? nil : range } - // TODO: Use JavaScriptCore and DOM to perform these manipulations + func insert(_ string: String, at index: Int) { + html.insert("\n\(string)", at: index) + } - if let range = html.firstRange(of: "") { + if let range = getRange(of: "") { insert(#""#, at: range.upperBound) insert(#""#, at: range.upperBound) } - if let range = html.firstRange(of: "") { + let regular = #"font-family: 'SF Pro Text', -apple-system, sans-serif"# + let mono = #"font-family: 'SF Mono', SFMono-Regular, ui-monospace, Menlo, monospace;"# + do { + let regex = try NSRegularExpression(pattern: "font-family: '.SFUI-\\w*'", options: []) + regex.replaceMatches(in: html, range: NSRange(location: 0, length: range.upperBound), withTemplate: regular) + } catch { + // Should never happen + } + do { + let regex = try NSRegularExpression(pattern: "font-family: '.AppleSystemUIFontMonospaced-\\w*'", options: []) + regex.replaceMatches(in: html, range: NSRange(location: 0, length: range.upperBound), withTemplate: mono) + } catch { + // Should never happen + } + } + return html.data(using: NSUTF8StringEncoding) ?? data } /// Renders the given attributed string as PDF diff --git a/Tests/PulsePerformanceTests/TextRendererPerformanceTests.swift b/Tests/PulsePerformanceTests/TextRendererPerformanceTests.swift index aed8efcd..c689859a 100644 --- a/Tests/PulsePerformanceTests/TextRendererPerformanceTests.swift +++ b/Tests/PulsePerformanceTests/TextRendererPerformanceTests.swift @@ -16,16 +16,6 @@ final class TextRendererTestsTests: XCTestCase { } } - func testAttributedStringBigStore() throws { - let url = try XCTUnwrap(Bundle(for: TextRendererTestsTests.self).url(forResource: "bigstore", withExtension: "pulse")) - let store = try LoggerStore(storeURL: url) - let entities = try store.allMessages() - - benchmark(title: "Entities -> NSAttributedString") { - let _ = TextRendererTests.share(entities) - } - } - func testPlainTextConversion() throws { let entities = try LoggerStore.mock.allMessages() let string = TextRendererTests.share(entities) @@ -57,6 +47,30 @@ final class TextRendererTestsTests: XCTestCase { } } #endif + + // MARK: Big Store + + func testBigStoreAttributedString() throws { + let url = try XCTUnwrap(Bundle(for: TextRendererTestsTests.self).url(forResource: "bigstore", withExtension: "pulse")) + let store = try LoggerStore(storeURL: url) + let entities = try store.allMessages() + + benchmark(title: "Entities -> NSAttributedString") { + let _ = TextRendererTests.share(entities) + } + } + + func testBigStoreHTML() throws { + let url = try XCTUnwrap(Bundle(for: TextRendererTestsTests.self).url(forResource: "bigstore", withExtension: "pulse")) + let store = try LoggerStore(storeURL: url) + let entities = try store.allMessages() + + let string = TextRendererTests.share(entities) + + benchmark(title: "NSAttributedString -> HTML") { + let _ = try! TextRendererTests.html(from: string) + } + } } @discardableResult