1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
import NorgKit
import SwiftUI
/// Extensions to AttributedString to generate from NorgKit models.
extension AttributedString {
/// Builds a styled attributed string from parsed Norg inline spans.
public init(norg spans: [InlineSpan], baseFont: Font = .body, theme: NorgTheme = .default) {
var result = AttributedString()
for span in spans {
result.append(Self.render(span, baseFont: baseFont, theme: theme))
}
self = result
}
private static func render(_ span: InlineSpan, baseFont: Font, theme: NorgTheme)
-> AttributedString {
var run = AttributedString(span.text)
run.font = font(for: span.styles, baseFont: baseFont)
applyDecorations(to: &run, styles: span.styles, theme: theme)
if let link = span.link {
applyLink(link, to: &run, theme: theme)
}
return run
}
private static func font(for styles: InlineStyle, baseFont: Font) -> Font {
var font = baseFont
if styles.contains(.verbatim) || styles.contains(.math) { font = font.monospaced() }
if styles.contains(.bold) { font = font.bold() }
if styles.contains(.italic) { font = font.italic() }
return font
}
private static func applyDecorations(
to run: inout AttributedString, styles: InlineStyle, theme: NorgTheme
) {
if styles.contains(.underline) { run.underlineStyle = .single }
if styles.contains(.strikethrough) { run.strikethroughStyle = .single }
if styles.contains(.superscript) { run.baselineOffset = theme.superscriptOffset }
if styles.contains(.subscript) { run.baselineOffset = theme.subscriptOffset }
if styles.contains(.spoiler) {
run.foregroundColor = theme.spoilerForeground
run.backgroundColor = theme.spoilerBackground
}
if styles.contains(.verbatim) {
run.foregroundColor = theme.verbatimColor
}
}
private static func applyLink(
_ link: InlineLink, to run: inout AttributedString, theme: NorgTheme
) {
run.foregroundColor = theme.linkColor
run.underlineStyle = .single
run.link = url(for: link)
}
private static func url(for link: InlineLink) -> URL? {
if link.kind == .link, let target = link.target, let external = externalURL(for: target) {
return external
}
return NorgLinkURL.encode(link)
}
private static func externalURL(for target: String) -> URL? {
let lower = target.lowercased()
guard lower.hasPrefix("http://") || lower.hasPrefix("https://") || lower.hasPrefix("file://")
else {
return nil
}
return URL(string: target)
}
}
|