From 0948371f838cb0bba494381cdfae9c42bd2e0710 Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Wed, 27 Sep 2023 17:35:36 -0400 Subject: [PATCH] Improve appearance of lists when converting from HTML Closes #434 --- Tusker/HTMLConverter.swift | 62 ++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/Tusker/HTMLConverter.swift b/Tusker/HTMLConverter.swift index d4911408..22504752 100644 --- a/Tusker/HTMLConverter.swift +++ b/Tusker/HTMLConverter.swift @@ -37,7 +37,12 @@ struct HTMLConverter { mutAttrString.trimTrailingCharactersInSet(.whitespacesAndNewlines) mutAttrString.collapseWhitespace() - mutAttrString.addAttribute(.paragraphStyle, value: paragraphStyle, range: mutAttrString.fullRange) + // Wait until the end and then fill in the unset paragraph styles, to avoid clobbering the list style. + mutAttrString.enumerateAttribute(.paragraphStyle, in: mutAttrString.fullRange, options: .longestEffectiveRangeNotRequired) { value, range, stop in + if value == nil { + mutAttrString.addAttribute(.paragraphStyle, value: paragraphStyle, range: range) + } + } return mutAttrString } else { @@ -56,6 +61,10 @@ struct HTMLConverter { } return NSAttributedString(string: text, attributes: [.font: font, .foregroundColor: color]) case let node as Element: + if node.tagName() == "ol" || node.tagName() == "ul" { + return attributedTextForList(node, usePreformattedText: usePreformattedText) + } + let attributed = NSMutableAttributedString(string: "", attributes: [.font: font, .foregroundColor: color]) for child in node.getChildNodes() { var appendEllipsis = false @@ -115,25 +124,6 @@ struct HTMLConverter { case "pre": attributed.append(NSAttributedString(string: "\n\n")) attributed.addAttribute(.font, value: monospaceFont, range: attributed.fullRange) - case "ol", "ul": - attributed.append(NSAttributedString(string: "\n\n")) - attributed.trimLeadingCharactersInSet(.whitespacesAndNewlines) - case "li": - let parentEl = node.parent()! - let parentTag = parentEl.tagName() - let bullet: NSAttributedString - if parentTag == "ol" { - let index = (try? node.elementSiblingIndex()) ?? 0 - // we use the monospace digit font so that the periods of all the list items line up - // TODO: this probably breaks with dynamic type - bullet = NSAttributedString(string: "\(index + 1).\t", attributes: [.font: monospaceFont, .foregroundColor: color]) - } else if parentTag == "ul" { - bullet = NSAttributedString(string: "\u{2022}\t", attributes: [.font: font, .foregroundColor: color]) - } else { - bullet = NSAttributedString() - } - attributed.insert(bullet, at: 0) - attributed.append(NSAttributedString(string: "\n", attributes: [.font: font])) default: break } @@ -144,5 +134,37 @@ struct HTMLConverter { } } + private func attributedTextForList(_ element: Element, usePreformattedText: Bool) -> NSAttributedString { + let list = element.tagName() == "ol" ? OrderedNumberTextList(markerFormat: .decimal, options: 0) : NSTextList(markerFormat: .disc, options: 0) + let paragraphStyle = paragraphStyle.mutableCopy() as! NSMutableParagraphStyle + // I don't like that I can't just use paragraphStyle.textLists, because it makes the list markers + // not use the monospace digit font (it seems to just use whatever font attribute is set for the whole thing), + // and it doesn't right align the list markers. + // Unfortunately, doing it manually means the list markers are incldued in the selectable text. + paragraphStyle.headIndent = 32 + paragraphStyle.firstLineHeadIndent = 0 + // Use 2 tab stops, one for the list marker, the second for the content. + paragraphStyle.tabStops = [NSTextTab(textAlignment: .right, location: 28), NSTextTab(textAlignment: .natural, location: 32)] + let str = NSMutableAttributedString(string: "") + var item = 1 + for child in element.children() where child.tagName() == "li" { + if let childStr = attributedTextForHTMLNode(child, usePreformattedText: usePreformattedText) { + str.append(NSAttributedString(string: "\t\(list.marker(forItemNumber: item))\t", attributes: [ + .font: UIFontMetrics(forTextStyle: .body).scaledFont(for: .monospacedDigitSystemFont(ofSize: 17, weight: .regular)), + ])) + str.append(childStr) + str.append(NSAttributedString(string: "\n")) + item += 1 + } + } + str.addAttribute(.paragraphStyle, value: paragraphStyle, range: str.fullRange) + return str + } } + +private class OrderedNumberTextList: NSTextList { + override func marker(forItemNumber itemNumber: Int) -> String { + "\(super.marker(forItemNumber: itemNumber))." + } +}