@@ -10,19 +10,7 @@ import Foundation
1010final class AttributedStringRenderer {
1111 private struct State {
1212 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 ( )
2614 }
2715
2816 private let writingDirection : NSWritingDirection
@@ -47,21 +35,35 @@ final class AttributedStringRenderer {
4735 self . attachments = attachments
4836
4937 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+ )
5448
49+ style. documentAttributes ( & state. attributes)
5550 return attributedString ( for: document. blocks)
5651 }
5752 #else
5853 func attributedString( for document: Document ) -> NSAttributedString {
5954 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+ )
6465
66+ style. documentAttributes ( & state. attributes)
6567 return attributedString ( for: document. blocks)
6668 }
6769 #endif
@@ -85,31 +87,30 @@ private extension AttributedStringRenderer {
8587 saveState ( )
8688 defer { restoreState ( ) }
8789
88- state. font = state . font ? . italic ( )
89- state. indentLevel += 1
90+ state. paragraph . indentLevel += 1
91+ style . blockQuoteAttributes ( & state. attributes )
9092
9193 return attributedString ( for: blocks)
9294
9395 case let . list( value) :
9496 saveState ( )
9597 defer { restoreState ( ) }
9698
97- state. indentLevel += 1
99+ state. paragraph . indentLevel += 1
98100
99101 return attributedString ( for: value)
100102
101103 case let . code( value, _) :
102104 saveState ( )
103105 defer { restoreState ( ) }
104106
107+ state. paragraph. indentLevel += 1
108+ style. codeBlockAttributes ( & state. attributes, paragraphState: state. paragraph)
109+
105110 let cleanCode = value. trimmingCharacters ( in: CharacterSet . newlines)
106111 . components ( separatedBy: CharacterSet . newlines)
107112 . joined ( separator: Constants . lineSeparator)
108113
109- state. font = makeCodeFont ( )
110- state. indentLevel += 1
111- state. paragraphStyle = makeParagraphStyle ( )
112-
113114 return NSAttributedString ( string: String ( cleanCode) , attributes: state. attributes)
114115
115116 case let . html( value) :
@@ -133,28 +134,27 @@ private extension AttributedStringRenderer {
133134 saveState ( )
134135 defer { restoreState ( ) }
135136
136- state. paragraphStyle = makeParagraphStyle ( )
137-
137+ style. htmlBlockAttributes ( & state. attributes, paragraphState: state. paragraph)
138138 result. addAttributes ( state. attributes, range: NSRange ( location: 0 , length: result. length) )
139+
139140 return result
141+ } else {
142+ return NSAttributedString ( )
140143 }
141144
142- return NSAttributedString ( )
143-
144145 case let . paragraph( inlines) :
145146 saveState ( )
146147 defer { restoreState ( ) }
147148
148- state. paragraphStyle = makeParagraphStyle ( )
149+ style . paragraphAttributes ( & state. attributes , paragraphState : state . paragraph )
149150
150151 return attributedString ( for: inlines)
151152
152153 case let . heading( inlines, level) :
153154 saveState ( )
154155 defer { restoreState ( ) }
155156
156- state. font = makeHeadingFont ( level)
157- state. paragraphStyle = makeHeadingParagraphStyle ( level)
157+ style. headingAttributes ( & state. attributes, level: level, paragraphState: state. paragraph)
158158
159159 return attributedString ( for: inlines)
160160
@@ -184,11 +184,7 @@ private extension AttributedStringRenderer {
184184 saveState ( )
185185 defer { restoreState ( ) }
186186
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)
192188
193189 return NSAttributedString ( string: value, attributes: state. attributes)
194190
@@ -199,29 +195,23 @@ private extension AttributedStringRenderer {
199195 saveState ( )
200196 defer { restoreState ( ) }
201197
202- state . font = state. font ? . italic ( )
198+ style . emphasisAttributes ( & state. attributes )
203199
204200 return attributedString ( for: inlines)
205201
206202 case let . strong( inlines) :
207203 saveState ( )
208204 defer { restoreState ( ) }
209205
210- state . font = state. font ? . bold ( )
206+ style . strongAttributes ( & state. attributes )
211207
212208 return attributedString ( for: inlines)
213209
214210 case let . link( inlines, url, title) :
215211 saveState ( )
216212 defer { restoreState ( ) }
217213
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)
225215
226216 return attributedString ( for: inlines)
227217
@@ -249,7 +239,7 @@ private extension AttributedStringRenderer {
249239 saveState ( )
250240 defer { restoreState ( ) }
251241
252- state. tightSpacing = ( list . spacing == . tight )
242+ state. paragraph . spacing = list . spacing
253243
254244 return list. items. enumerated ( ) . map { offset, item in
255245 attributedString (
@@ -272,100 +262,23 @@ private extension AttributedStringRenderer {
272262 defer { restoreState ( ) }
273263
274264 if isLastItem, offset == item. blocks. count - 1 {
275- state. tightSpacing = false
265+ state. paragraph . spacing = . loose
276266 }
277267
278268 if offset == 0 {
279- state. hangingParagraph = true
269+ state. paragraph . isHanging = true
280270
281271 return [
282272 attributedString ( for: delimiterBlock) ,
283273 attributedString ( for: block) ,
284274 ] . joined ( )
285275 } else {
286- state. indentLevel += 1
276+ state. paragraph . indentLevel += 1
287277 return attributedString ( for: block)
288278 }
289279 } . joined ( separator: NSAttributedString ( string: Constants . paragraphSeparator) )
290280 }
291281
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-
369282 func saveState( ) {
370283 states. append ( state)
371284 }
0 commit comments