@@ -10,19 +10,7 @@ import Foundation
10
10
final class AttributedStringRenderer {
11
11
private struct State {
12
12
var attributes : [ NSAttributedString . Key : Any ] = [ : ]
13
- var tightSpacing = false
14
- var hangingParagraph = false
15
- var indentLevel = 0
16
-
17
- var font : MarkdownStyle . Font ? {
18
- get { attributes [ . font] as? MarkdownStyle . Font }
19
- set { attributes [ . font] = newValue }
20
- }
21
-
22
- var paragraphStyle : NSParagraphStyle ? {
23
- get { attributes [ . paragraphStyle] as? NSParagraphStyle }
24
- set { attributes [ . paragraphStyle] = newValue }
25
- }
13
+ var paragraph = ParagraphState ( )
26
14
}
27
15
28
16
private let writingDirection : NSWritingDirection
@@ -47,21 +35,35 @@ final class AttributedStringRenderer {
47
35
self . attachments = attachments
48
36
49
37
states. removeAll ( )
50
- state = State ( attributes: [
51
- . font: style. font,
52
- . foregroundColor: style. foregroundColor,
53
- ] )
38
+ state = State (
39
+ attributes: [
40
+ . font: style. font,
41
+ . foregroundColor: style. foregroundColor,
42
+ ] ,
43
+ paragraph: ParagraphState (
44
+ baseWritingDirection: writingDirection,
45
+ alignment: alignment
46
+ )
47
+ )
54
48
49
+ style. documentAttributes ( & state. attributes)
55
50
return attributedString ( for: document. blocks)
56
51
}
57
52
#else
58
53
func attributedString( for document: Document ) -> NSAttributedString {
59
54
states. removeAll ( )
60
- state = State ( attributes: [
61
- . font: style. font,
62
- . foregroundColor: style. foregroundColor,
63
- ] )
55
+ state = State (
56
+ attributes: [
57
+ . font: style. font,
58
+ . foregroundColor: style. foregroundColor,
59
+ ] ,
60
+ paragraph: ParagraphState (
61
+ baseWritingDirection: writingDirection,
62
+ alignment: alignment
63
+ )
64
+ )
64
65
66
+ style. documentAttributes ( & state. attributes)
65
67
return attributedString ( for: document. blocks)
66
68
}
67
69
#endif
@@ -85,31 +87,30 @@ private extension AttributedStringRenderer {
85
87
saveState ( )
86
88
defer { restoreState ( ) }
87
89
88
- state. font = state . font ? . italic ( )
89
- state. indentLevel += 1
90
+ state. paragraph . indentLevel += 1
91
+ style . blockQuoteAttributes ( & state. attributes )
90
92
91
93
return attributedString ( for: blocks)
92
94
93
95
case let . list( value) :
94
96
saveState ( )
95
97
defer { restoreState ( ) }
96
98
97
- state. indentLevel += 1
99
+ state. paragraph . indentLevel += 1
98
100
99
101
return attributedString ( for: value)
100
102
101
103
case let . code( value, _) :
102
104
saveState ( )
103
105
defer { restoreState ( ) }
104
106
107
+ state. paragraph. indentLevel += 1
108
+ style. codeBlockAttributes ( & state. attributes, paragraphState: state. paragraph)
109
+
105
110
let cleanCode = value. trimmingCharacters ( in: CharacterSet . newlines)
106
111
. components ( separatedBy: CharacterSet . newlines)
107
112
. joined ( separator: Constants . lineSeparator)
108
113
109
- state. font = makeCodeFont ( )
110
- state. indentLevel += 1
111
- state. paragraphStyle = makeParagraphStyle ( )
112
-
113
114
return NSAttributedString ( string: String ( cleanCode) , attributes: state. attributes)
114
115
115
116
case let . html( value) :
@@ -133,28 +134,27 @@ private extension AttributedStringRenderer {
133
134
saveState ( )
134
135
defer { restoreState ( ) }
135
136
136
- state. paragraphStyle = makeParagraphStyle ( )
137
-
137
+ style. htmlBlockAttributes ( & state. attributes, paragraphState: state. paragraph)
138
138
result. addAttributes ( state. attributes, range: NSRange ( location: 0 , length: result. length) )
139
+
139
140
return result
141
+ } else {
142
+ return NSAttributedString ( )
140
143
}
141
144
142
- return NSAttributedString ( )
143
-
144
145
case let . paragraph( inlines) :
145
146
saveState ( )
146
147
defer { restoreState ( ) }
147
148
148
- state. paragraphStyle = makeParagraphStyle ( )
149
+ style . paragraphAttributes ( & state. attributes , paragraphState : state . paragraph )
149
150
150
151
return attributedString ( for: inlines)
151
152
152
153
case let . heading( inlines, level) :
153
154
saveState ( )
154
155
defer { restoreState ( ) }
155
156
156
- state. font = makeHeadingFont ( level)
157
- state. paragraphStyle = makeHeadingParagraphStyle ( level)
157
+ style. headingAttributes ( & state. attributes, level: level, paragraphState: state. paragraph)
158
158
159
159
return attributedString ( for: inlines)
160
160
@@ -184,11 +184,7 @@ private extension AttributedStringRenderer {
184
184
saveState ( )
185
185
defer { restoreState ( ) }
186
186
187
- if let symbolicTraits = state. font? . fontDescriptor. symbolicTraits {
188
- state. font = makeCodeFont ( ) ? . addingSymbolicTraits ( symbolicTraits)
189
- } else {
190
- state. font = makeCodeFont ( )
191
- }
187
+ style. codeAttributes ( & state. attributes)
192
188
193
189
return NSAttributedString ( string: value, attributes: state. attributes)
194
190
@@ -199,29 +195,23 @@ private extension AttributedStringRenderer {
199
195
saveState ( )
200
196
defer { restoreState ( ) }
201
197
202
- state . font = state. font ? . italic ( )
198
+ style . emphasisAttributes ( & state. attributes )
203
199
204
200
return attributedString ( for: inlines)
205
201
206
202
case let . strong( inlines) :
207
203
saveState ( )
208
204
defer { restoreState ( ) }
209
205
210
- state . font = state. font ? . bold ( )
206
+ style . strongAttributes ( & state. attributes )
211
207
212
208
return attributedString ( for: inlines)
213
209
214
210
case let . link( inlines, url, title) :
215
211
saveState ( )
216
212
defer { restoreState ( ) }
217
213
218
- state. attributes [ . link] = URL ( string: url)
219
-
220
- #if os(macOS)
221
- if !title. isEmpty {
222
- state. attributes [ . toolTip] = title
223
- }
224
- #endif
214
+ style. linkAttributes ( & state. attributes, url: url, title: title)
225
215
226
216
return attributedString ( for: inlines)
227
217
@@ -249,7 +239,7 @@ private extension AttributedStringRenderer {
249
239
saveState ( )
250
240
defer { restoreState ( ) }
251
241
252
- state. tightSpacing = ( list . spacing == . tight )
242
+ state. paragraph . spacing = list . spacing
253
243
254
244
return list. items. enumerated ( ) . map { offset, item in
255
245
attributedString (
@@ -272,100 +262,23 @@ private extension AttributedStringRenderer {
272
262
defer { restoreState ( ) }
273
263
274
264
if isLastItem, offset == item. blocks. count - 1 {
275
- state. tightSpacing = false
265
+ state. paragraph . spacing = . loose
276
266
}
277
267
278
268
if offset == 0 {
279
- state. hangingParagraph = true
269
+ state. paragraph . isHanging = true
280
270
281
271
return [
282
272
attributedString ( for: delimiterBlock) ,
283
273
attributedString ( for: block) ,
284
274
] . joined ( )
285
275
} else {
286
- state. indentLevel += 1
276
+ state. paragraph . indentLevel += 1
287
277
return attributedString ( for: block)
288
278
}
289
279
} . joined ( separator: NSAttributedString ( string: Constants . paragraphSeparator) )
290
280
}
291
281
292
- func makeCodeFont( ) -> MarkdownStyle . Font ? {
293
- let codeFontSize = round ( style. codeFontSize. resolve ( style. font. pointSize) )
294
- if let codeFontName = style. codeFontName {
295
- return MarkdownStyle . Font ( name: codeFontName, size: codeFontSize) ?? . monospaced( size: codeFontSize)
296
- } else {
297
- return . monospaced( size: codeFontSize)
298
- }
299
- }
300
-
301
- func makeParagraphStyle( ) -> NSParagraphStyle {
302
- let paragraphStyle = NSMutableParagraphStyle ( )
303
-
304
- paragraphStyle. baseWritingDirection = writingDirection
305
- paragraphStyle. alignment = alignment
306
-
307
- let indentSize = round ( style. indentSize. resolve ( style. font. pointSize) )
308
- let indent = CGFloat ( state. indentLevel) * indentSize
309
-
310
- paragraphStyle. firstLineHeadIndent = indent
311
-
312
- if state. hangingParagraph {
313
- paragraphStyle. headIndent = indent + indentSize
314
- paragraphStyle. tabStops = [
315
- NSTextTab ( textAlignment: alignment, location: indent + indentSize, options: [ : ] ) ,
316
- ]
317
- } else {
318
- paragraphStyle. headIndent = indent
319
- }
320
-
321
- if !state. tightSpacing {
322
- paragraphStyle. paragraphSpacing = round ( style. paragraphSpacing. resolve ( style. font. pointSize) )
323
- }
324
-
325
- return paragraphStyle
326
- }
327
-
328
- func makeHeadingFont( _ level: Int ) -> MarkdownStyle . Font ? {
329
- let headingStyle = style. headingStyles [ min ( level, style. headingStyles. count) - 1 ]
330
- let fontSize = round ( headingStyle. fontSize. resolve ( style. font. pointSize) )
331
- let font = MarkdownStyle . Font ( descriptor: style. font. fontDescriptor, size: fontSize)
332
-
333
- #if canImport(UIKit)
334
- return font. bold ( )
335
- #elseif os(macOS)
336
- return font? . bold ( )
337
- #endif
338
- }
339
-
340
- func makeHeadingParagraphStyle( _ level: Int ) -> NSParagraphStyle {
341
- let headingStyle = style. headingStyles [ min ( level, style. headingStyles. count) - 1 ]
342
-
343
- let paragraphStyle = NSMutableParagraphStyle ( )
344
-
345
- paragraphStyle. baseWritingDirection = writingDirection
346
- paragraphStyle. alignment = alignment
347
-
348
- let indentSize = round ( style. indentSize. resolve ( style. font. pointSize) )
349
- let indent = CGFloat ( state. indentLevel) * indentSize
350
-
351
- paragraphStyle. firstLineHeadIndent = indent
352
-
353
- if state. hangingParagraph {
354
- paragraphStyle. headIndent = indent + indentSize
355
- paragraphStyle. tabStops = [
356
- NSTextTab ( textAlignment: alignment, location: indent + indentSize, options: [ : ] ) ,
357
- ]
358
- } else {
359
- paragraphStyle. headIndent = indent
360
- }
361
-
362
- if !state. tightSpacing {
363
- paragraphStyle. paragraphSpacing = round ( headingStyle. spacing. resolve ( style. font. pointSize) )
364
- }
365
-
366
- return paragraphStyle
367
- }
368
-
369
282
func saveState( ) {
370
283
states. append ( state)
371
284
}
0 commit comments