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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
#if canImport(UIKit)
import UIKit
public typealias PlatformFont = UIFont
public typealias PlatformColor = UIColor
#elseif canImport(AppKit)
import AppKit
public typealias PlatformFont = NSFont
public typealias PlatformColor = NSColor
#endif
#if canImport(UIKit) || canImport(AppKit)
import NorgKit
/// Typography and colours used when highlighting a Norg document.
public struct EditorTheme {
/// Base font for source text. Inline `bold`/`italic` runs derive bold and
/// italic variants from this font's descriptor.
public var font: PlatformFont
/// Base foreground colour for plain, unstyled source.
public var textColor: PlatformColor
/// Color for a heading marker run (`*`…) at the given 1-based level.
public var headingColor: @Sendable (Int) -> PlatformColor
/// Color for structural markers and delimiters: list/quote/definition/
/// footnote/table-cell runs, delimiting lines, rules, inline-modifier
/// delimiters, link brackets and escapes.
public var markerColor: PlatformColor
/// Color for a task status marker, e.g. `(x)`.
public var taskStatusColor: @Sendable (TaskStatus) -> PlatformColor
/// Color for a ranged-tag header: the `@` and the tag name/parameters.
public var tagColor: PlatformColor
/// Color for verbatim content: inline `` `verbatim` `` and ranged-tag
/// (`@code` … `@end`) body lines.
public var verbatimColor: PlatformColor
/// Color for inline comments (`%…%`).
public var commentColor: PlatformColor
/// Color for link/anchor locations and descriptions.
public var linkColor: PlatformColor
/// Color for `spoiler` inline runs.
public var spoilerColor: PlatformColor
public init(
font: PlatformFont,
textColor: PlatformColor,
headingColor: @escaping @Sendable (Int) -> PlatformColor,
markerColor: PlatformColor,
taskStatusColor: @escaping @Sendable (TaskStatus) -> PlatformColor,
tagColor: PlatformColor,
verbatimColor: PlatformColor,
commentColor: PlatformColor,
linkColor: PlatformColor,
spoilerColor: PlatformColor
) {
self.font = font
self.textColor = textColor
self.headingColor = headingColor
self.markerColor = markerColor
self.taskStatusColor = taskStatusColor
self.tagColor = tagColor
self.verbatimColor = verbatimColor
self.commentColor = commentColor
self.linkColor = linkColor
self.spoilerColor = spoilerColor
}
public static let `default` = EditorTheme(
font: .monospacedSystemFont(ofSize: 16, weight: .regular),
textColor: .norgLabel,
headingColor: { _ in .systemBlue },
markerColor: .norgSecondaryLabel,
taskStatusColor: { status in
switch status {
case .done, .recurring: return .systemGreen
case .urgent: return .systemRed
case .pending, .onHold: return .systemBlue
case .cancelled: return .norgTertiaryLabel
case .undone, .needsInput: return .norgSecondaryLabel
}
},
tagColor: .systemPurple,
verbatimColor: .systemPink,
commentColor: .norgTertiaryLabel,
linkColor: .norgLink,
spoilerColor: .norgSecondaryLabel
)
}
#if canImport(UIKit)
extension EditorTheme: Sendable {}
#elseif canImport(AppKit)
extension EditorTheme: @unchecked Sendable {}
#endif
extension EditorTheme {
public func attributes(for kind: NorgToken.Kind) -> [NSAttributedString.Key: Any] {
switch kind {
case .heading(let level):
return [.foregroundColor: headingColor(level)]
case .unorderedList, .orderedList, .quote, .definition, .footnote,
.tableCell, .weakDelimiter, .strongDelimiter, .horizontalRule,
.modifierDelimiter, .linkDelimiter, .escape:
return [.foregroundColor: markerColor]
case .taskStatus(let status):
return [.foregroundColor: taskStatusColor(status)]
case .tagDelimiter, .tagName:
return [.foregroundColor: tagColor]
case .verbatimBlock:
return [.foregroundColor: verbatimColor]
case .comment:
return [.foregroundColor: commentColor]
case .linkTarget:
return [
.foregroundColor: linkColor,
.underlineStyle: NSUnderlineStyle.single.rawValue,
]
case .linkDescription:
return [.foregroundColor: linkColor]
case .styledText(let style):
return styledAttributes(style)
}
}
private func styledAttributes(_ style: InlineStyle) -> [NSAttributedString.Key: Any] {
var attributes: [NSAttributedString.Key: Any] = [:]
if let traited = traitedFont(bold: style.contains(.bold), italic: style.contains(.italic)) {
attributes[.font] = traited
}
if style.contains(.underline) {
attributes[.underlineStyle] = NSUnderlineStyle.single.rawValue
}
if style.contains(.strikethrough) {
attributes[.strikethroughStyle] = NSUnderlineStyle.single.rawValue
}
if style.contains(.verbatim) || style.contains(.math) {
attributes[.foregroundColor] = verbatimColor
} else if style.contains(.spoiler) {
attributes[.foregroundColor] = spoilerColor
}
return attributes
}
private func traitedFont(bold: Bool, italic: Bool) -> PlatformFont? {
guard bold || italic else { return nil }
#if canImport(UIKit)
var traits: UIFontDescriptor.SymbolicTraits = []
if bold { traits.insert(.traitBold) }
if italic { traits.insert(.traitItalic) }
guard let descriptor = font.fontDescriptor.withSymbolicTraits(traits) else { return nil }
return UIFont(descriptor: descriptor, size: font.pointSize)
#elseif canImport(AppKit)
var traits: NSFontDescriptor.SymbolicTraits = []
if bold { traits.insert(.bold) }
if italic { traits.insert(.italic) }
let descriptor = font.fontDescriptor.withSymbolicTraits(traits)
return NSFont(descriptor: descriptor, size: font.pointSize)
#endif
}
public func applyHighlighting(to storage: NSTextStorage) {
let text = storage.string
let full = NSRange(location: 0, length: (text as NSString).length)
storage.setAttributes([.font: font, .foregroundColor: textColor], range: full)
for highlight in NorgHighlighter.highlights(in: text) {
storage.addAttributes(attributes(for: highlight.kind), range: highlight.range)
}
}
}
extension PlatformColor {
#if canImport(UIKit)
fileprivate static var norgLabel: PlatformColor { .label }
fileprivate static var norgSecondaryLabel: PlatformColor { .secondaryLabel }
fileprivate static var norgTertiaryLabel: PlatformColor { .tertiaryLabel }
fileprivate static var norgLink: PlatformColor { .link }
#elseif canImport(AppKit)
fileprivate static var norgLabel: PlatformColor { .labelColor }
fileprivate static var norgSecondaryLabel: PlatformColor { .secondaryLabelColor }
fileprivate static var norgTertiaryLabel: PlatformColor { .tertiaryLabelColor }
fileprivate static var norgLink: PlatformColor { .linkColor }
#endif
}
#endif
|