@@ -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