Skip to content

Commit cae6f69

Browse files
authored
Refactor MarkdownStyle
* Refactor MarkdownStyle with a protocol and a default implementation * Update documentation
1 parent 079e085 commit cae6f69

File tree

11 files changed

+298
-269
lines changed

11 files changed

+298
-269
lines changed

Examples/MarkdownUIDemo/Shared/Example.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ struct Example: Identifiable, Hashable {
99

1010
var style: MarkdownStyle {
1111
useDefaultStyle
12-
? MarkdownStyle(font: .system(.body))
13-
: MarkdownStyle(
12+
? DefaultMarkdownStyle(font: .system(.body))
13+
: DefaultMarkdownStyle(
1414
font: .system(.body, design: .serif),
1515
codeFontName: "Menlo",
16-
codeFontSize: .em(0.88)
16+
codeFontSizeMultiple: 0.88
1717
)
1818
}
1919
}
@@ -117,7 +117,7 @@ extension Example {
117117
118118
```
119119
.markdownStyle(
120-
MarkdownStyle(
120+
DefaultMarkdownStyle(
121121
font: .system(
122122
.body,
123123
design: .serif

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Markdown {
6868

6969
A Markdown view renders text using a `body` font appropriate for the current platform.
7070
You can choose a different font or customize other properties like the foreground color,
71-
paragraph spacing, or heading styles using the `markdownStyle(_:)` view modifier.
71+
code font, or heading font sizes using the `markdownStyle(_:)` view modifier.
7272

7373
```swift
7474
Markdown(
@@ -78,10 +78,10 @@ Markdown(
7878
"""#
7979
)
8080
.markdownStyle(
81-
MarkdownStyle(
81+
DefaultMarkdownStyle(
8282
font: .system(.body, design: .serif),
8383
codeFontName: "Menlo",
84-
codeFontSize: .em(0.88)
84+
codeFontSizeMultiple: 0.88
8585
)
8686
)
8787
```
@@ -128,7 +128,7 @@ let attributedString = NSAttributedString(
128128
document: #"""
129129
It's very easy to make some words **bold** and other words *italic* with Markdown.
130130
"""#,
131-
style: MarkdownStyle(font: .system(.body))
131+
style: DefaultMarkdownStyle(font: .system(.body))
132132
)
133133
```
134134

Sources/MarkdownUI/Shared/AttributedStringRenderer.swift

Lines changed: 43 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,7 @@ import Foundation
1010
final 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

Comments
 (0)