Skip to content

Commit 4c16430

Browse files
authored
Use the environment's line spacing (#95)
* Set the environment's line spacing value to the attributed string paragraph style * Refactor renderer environment
1 parent b6e88f1 commit 4c16430

File tree

7 files changed

+138
-119
lines changed

7 files changed

+138
-119
lines changed

Sources/MarkdownUI/Markdown.swift

Lines changed: 29 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,13 @@ public struct Markdown: View {
119119

120120
private struct ViewState {
121121
var attributedString = NSAttributedString()
122-
var environmentHash: Int?
122+
var hashValue: Int?
123123
}
124124

125125
@Environment(\.layoutDirection) private var layoutDirection: LayoutDirection
126126
@Environment(\.multilineTextAlignment) private var textAlignment: TextAlignment
127127
@Environment(\.sizeCategory) private var sizeCategory: ContentSizeCategory
128+
@Environment(\.lineSpacing) private var lineSpacing: CGFloat
128129
@Environment(\.markdownStyle) private var style: MarkdownStyle
129130
@Environment(\.openMarkdownLink) private var openMarkdownLink
130131
@State private var viewState = ViewState()
@@ -194,35 +195,32 @@ public struct Markdown: View {
194195
}
195196

196197
private var viewStatePublisher: AnyPublisher<ViewState, Never> {
197-
struct Environment: Hashable {
198-
var storage: Storage
199-
var baseURL: URL?
200-
var layoutDirection: LayoutDirection
201-
var textAlignment: TextAlignment
202-
var sizeCategory: ContentSizeCategory
203-
var style: MarkdownStyle
198+
struct Input: Hashable {
199+
let storage: Storage
200+
let environment: AttributedStringRenderer.Environment
204201
}
205202

206203
return Just(
207204
// This value helps determine if we need to render the markdown again
208-
Environment(
205+
Input(
209206
storage: self.storage,
210-
baseURL: self.baseURL,
211-
layoutDirection: self.layoutDirection,
212-
textAlignment: self.textAlignment,
213-
sizeCategory: self.sizeCategory,
214-
style: self.style
207+
environment: .init(
208+
baseURL: self.baseURL,
209+
layoutDirection: self.layoutDirection,
210+
alignment: self.textAlignment,
211+
lineSpacing: self.lineSpacing,
212+
sizeCategory: self.sizeCategory,
213+
style: self.style
214+
)
215215
).hashValue
216216
)
217-
.flatMap { environmentHash -> AnyPublisher<ViewState, Never> in
218-
if self.viewState.environmentHash == environmentHash,
219-
!viewState.attributedString.hasMarkdownImages
220-
{
217+
.flatMap { hashValue -> AnyPublisher<ViewState, Never> in
218+
if self.viewState.hashValue == hashValue, !viewState.attributedString.hasMarkdownImages {
221219
return Empty().eraseToAnyPublisher()
222-
} else if self.viewState.environmentHash == environmentHash {
223-
return self.loadMarkdownImages(environmentHash: environmentHash)
220+
} else if self.viewState.hashValue == hashValue {
221+
return self.loadMarkdownImages(hashValue)
224222
} else {
225-
return self.renderAttributedString(environmentHash: environmentHash)
223+
return self.renderAttributedString(hashValue)
226224
}
227225
}
228226
.eraseToAnyPublisher()
@@ -235,29 +233,29 @@ public struct Markdown: View {
235233
}
236234
}
237235

238-
private func loadMarkdownImages(environmentHash: Int) -> AnyPublisher<ViewState, Never> {
236+
private func loadMarkdownImages(_ hashValue: Int) -> AnyPublisher<ViewState, Never> {
239237
NSAttributedString.loadingMarkdownImages(
240238
from: self.viewState.attributedString,
241239
using: self.imageHandlers
242240
)
243-
.map { ViewState(attributedString: $0, environmentHash: environmentHash) }
241+
.map { ViewState(attributedString: $0, hashValue: hashValue) }
244242
.receive(on: UIScheduler.shared)
245243
.eraseToAnyPublisher()
246244
}
247245

248-
private func renderAttributedString(environmentHash: Int) -> AnyPublisher<ViewState, Never> {
246+
private func renderAttributedString(_ hashValue: Int) -> AnyPublisher<ViewState, Never> {
249247
self.storage.document.renderAttributedString(
250-
baseURL: self.baseURL,
251-
baseWritingDirection: .init(self.layoutDirection),
252-
alignment: .init(
248+
environment: .init(
249+
baseURL: self.baseURL,
253250
layoutDirection: self.layoutDirection,
254-
textAlignment: self.textAlignment
251+
alignment: self.textAlignment,
252+
lineSpacing: self.lineSpacing,
253+
sizeCategory: self.sizeCategory,
254+
style: self.style
255255
),
256-
sizeCategory: self.sizeCategory,
257-
style: self.style,
258256
imageHandlers: self.imageHandlers
259257
)
260-
.map { ViewState(attributedString: $0, environmentHash: environmentHash) }
258+
.map { ViewState(attributedString: $0, hashValue: hashValue) }
261259
.receive(on: UIScheduler.shared)
262260
.eraseToAnyPublisher()
263261
}
@@ -412,33 +410,3 @@ private struct OpenMarkdownLinkAction {
412410
private struct OpenMarkdownLinkKey: EnvironmentKey {
413411
static let defaultValue: OpenMarkdownLinkAction? = nil
414412
}
415-
416-
extension NSWritingDirection {
417-
fileprivate init(_ layoutDirection: LayoutDirection) {
418-
switch layoutDirection {
419-
case .leftToRight:
420-
self = .leftToRight
421-
case .rightToLeft:
422-
self = .rightToLeft
423-
@unknown default:
424-
self = .natural
425-
}
426-
}
427-
}
428-
429-
extension NSTextAlignment {
430-
fileprivate init(layoutDirection: LayoutDirection, textAlignment: TextAlignment) {
431-
switch (layoutDirection, textAlignment) {
432-
case (_, .leading):
433-
self = .natural
434-
case (_, .center):
435-
self = .center
436-
case (.leftToRight, .trailing):
437-
self = .right
438-
case (.rightToLeft, .trailing):
439-
self = .left
440-
default:
441-
self = .natural
442-
}
443-
}
444-
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import SwiftUI
2+
3+
extension AttributedStringRenderer {
4+
struct Environment: Hashable {
5+
let baseURL: URL?
6+
let baseWritingDirection: NSWritingDirection
7+
let alignment: NSTextAlignment
8+
let lineSpacing: CGFloat
9+
let sizeCategory: ContentSizeCategory
10+
let style: MarkdownStyle
11+
12+
init(
13+
baseURL: URL?,
14+
layoutDirection: LayoutDirection,
15+
alignment: TextAlignment,
16+
lineSpacing: CGFloat,
17+
sizeCategory: ContentSizeCategory,
18+
style: MarkdownStyle
19+
) {
20+
self.baseURL = baseURL
21+
self.baseWritingDirection = .init(layoutDirection)
22+
self.alignment = .init(layoutDirection, alignment)
23+
self.lineSpacing = lineSpacing
24+
self.sizeCategory = sizeCategory
25+
self.style = style
26+
}
27+
}
28+
}
29+
30+
extension NSWritingDirection {
31+
fileprivate init(_ layoutDirection: LayoutDirection) {
32+
switch layoutDirection {
33+
case .leftToRight:
34+
self = .leftToRight
35+
case .rightToLeft:
36+
self = .rightToLeft
37+
@unknown default:
38+
self = .natural
39+
}
40+
}
41+
}
42+
43+
extension NSTextAlignment {
44+
fileprivate init(_ layoutDirection: LayoutDirection, _ textAlignment: TextAlignment) {
45+
switch (layoutDirection, textAlignment) {
46+
case (_, .leading):
47+
self = .natural
48+
case (_, .center):
49+
self = .center
50+
case (.leftToRight, .trailing):
51+
self = .right
52+
case (.rightToLeft, .trailing):
53+
self = .left
54+
default:
55+
self = .natural
56+
}
57+
}
58+
}

Sources/MarkdownUI/Rendering/AttributedStringRenderer.swift

Lines changed: 33 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,15 @@ struct AttributedStringRenderer {
3636
case decimal(Int)
3737
}
3838

39-
let baseURL: URL?
40-
let baseWritingDirection: NSWritingDirection
41-
let alignment: NSTextAlignment
42-
let sizeCategory: ContentSizeCategory
43-
let style: MarkdownStyle
39+
let environment: Environment
4440

4541
func renderDocument(_ document: Document) -> NSAttributedString {
4642
return renderBlocks(
4743
document.blocks,
4844
state: .init(
49-
font: style.font,
50-
foregroundColor: style.foregroundColor,
51-
paragraphSpacing: style.measurements.paragraphSpacing
45+
font: environment.style.font,
46+
foregroundColor: environment.style.foregroundColor,
47+
paragraphSpacing: environment.style.measurements.paragraphSpacing
5248
)
5349
)
5450
}
@@ -101,8 +97,8 @@ extension AttributedStringRenderer {
10197

10298
var state = state
10399
state.font = state.font.italic()
104-
state.headIndent += style.measurements.headIndentStep
105-
state.tailIndent += style.measurements.tailIndentStep
100+
state.headIndent += environment.style.measurements.headIndentStep
101+
state.tailIndent += environment.style.measurements.tailIndentStep
106102
state.tabStops.append(
107103
.init(textAlignment: .natural, location: state.headIndent)
108104
)
@@ -129,13 +125,14 @@ extension AttributedStringRenderer {
129125
let result = NSMutableAttributedString()
130126

131127
var itemState = state
132-
itemState.paragraphSpacing = bulletList.tight ? 0 : style.measurements.paragraphSpacing
133-
itemState.headIndent += style.measurements.headIndentStep
128+
itemState.paragraphSpacing =
129+
bulletList.tight ? 0 : environment.style.measurements.paragraphSpacing
130+
itemState.headIndent += environment.style.measurements.headIndentStep
134131
itemState.tabStops.append(
135132
contentsOf: [
136133
.init(
137-
textAlignment: .trailing(baseWritingDirection),
138-
location: itemState.headIndent - style.measurements.listMarkerSpacing
134+
textAlignment: .trailing(environment.baseWritingDirection),
135+
location: itemState.headIndent - environment.style.measurements.listMarkerSpacing
139136
),
140137
.init(textAlignment: .natural, location: itemState.headIndent),
141138
]
@@ -172,21 +169,24 @@ extension AttributedStringRenderer {
172169
// as the head indent step if higher than the style's head indent step.
173170
let highestNumber = orderedList.start + orderedList.items.count - 1
174171
let headIndentStep = max(
175-
style.measurements.headIndentStep,
172+
environment.style.measurements.headIndentStep,
176173
NSAttributedString(
177174
string: "\(highestNumber).",
178-
attributes: [.font: state.font.monospacedDigit().resolve(sizeCategory: sizeCategory)]
179-
).em() + style.measurements.listMarkerSpacing
175+
attributes: [
176+
.font: state.font.monospacedDigit().resolve(sizeCategory: environment.sizeCategory)
177+
]
178+
).em() + environment.style.measurements.listMarkerSpacing
180179
)
181180

182181
var itemState = state
183-
itemState.paragraphSpacing = orderedList.tight ? 0 : style.measurements.paragraphSpacing
182+
itemState.paragraphSpacing =
183+
orderedList.tight ? 0 : environment.style.measurements.paragraphSpacing
184184
itemState.headIndent += headIndentStep
185185
itemState.tabStops.append(
186186
contentsOf: [
187187
.init(
188-
textAlignment: .trailing(baseWritingDirection),
189-
location: itemState.headIndent - style.measurements.listMarkerSpacing
188+
textAlignment: .trailing(environment.baseWritingDirection),
189+
location: itemState.headIndent - environment.style.measurements.listMarkerSpacing
190190
),
191191
.init(textAlignment: .natural, location: itemState.headIndent),
192192
]
@@ -258,8 +258,8 @@ extension AttributedStringRenderer {
258258
state: State
259259
) -> NSAttributedString {
260260
var state = state
261-
state.font = state.font.scale(style.measurements.codeFontScale).monospaced()
262-
state.headIndent += style.measurements.headIndentStep
261+
state.font = state.font.scale(environment.style.measurements.codeFontScale).monospaced()
262+
state.headIndent += environment.style.measurements.headIndentStep
263263
state.tabStops.append(
264264
.init(textAlignment: .natural, location: state.headIndent)
265265
)
@@ -313,14 +313,14 @@ extension AttributedStringRenderer {
313313

314314
var inlineState = state
315315
inlineState.font = inlineState.font.bold().scale(
316-
style.measurements.headingScales[heading.level - 1]
316+
environment.style.measurements.headingScales[heading.level - 1]
317317
)
318318

319319
result.append(renderInlines(heading.text, state: inlineState))
320320

321321
// The paragraph spacing is relative to the parent font
322322
var paragraphState = state
323-
paragraphState.paragraphSpacing = style.measurements.headingSpacing
323+
paragraphState.paragraphSpacing = environment.style.measurements.headingSpacing
324324

325325
result.addAttribute(
326326
.paragraphStyle,
@@ -342,7 +342,7 @@ extension AttributedStringRenderer {
342342
.init(
343343
string: .nbsp,
344344
attributes: [
345-
.font: state.font.resolve(sizeCategory: sizeCategory),
345+
.font: state.font.resolve(sizeCategory: environment.sizeCategory),
346346
.strikethroughStyle: NSUnderlineStyle.single.rawValue,
347347
.strikethroughColor: PlatformColor.separator,
348348
]
@@ -425,7 +425,7 @@ extension AttributedStringRenderer {
425425
NSAttributedString(
426426
string: text,
427427
attributes: [
428-
.font: state.font.resolve(sizeCategory: sizeCategory),
428+
.font: state.font.resolve(sizeCategory: environment.sizeCategory),
429429
.foregroundColor: PlatformColor(state.foregroundColor),
430430
]
431431
)
@@ -441,7 +441,7 @@ extension AttributedStringRenderer {
441441

442442
private func renderInlineCode(_ inlineCode: InlineCode, state: State) -> NSAttributedString {
443443
var state = state
444-
state.font = state.font.scale(style.measurements.codeFontScale).monospaced()
444+
state.font = state.font.scale(environment.style.measurements.codeFontScale).monospaced()
445445
return renderText(inlineCode.code, state: state)
446446
}
447447

@@ -466,7 +466,7 @@ extension AttributedStringRenderer {
466466
let absoluteURL =
467467
link.url
468468
.map(\.relativeString)
469-
.flatMap { URL(string: $0, relativeTo: baseURL) }
469+
.flatMap { URL(string: $0, relativeTo: environment.baseURL) }
470470
.map(\.absoluteURL)
471471
if let url = absoluteURL {
472472
result.addAttribute(.link, value: url, range: NSRange(0..<result.length))
@@ -483,19 +483,20 @@ extension AttributedStringRenderer {
483483
private func renderImage(_ image: CommonMark.Image, state: State) -> NSAttributedString {
484484
image.url
485485
.map(\.relativeString)
486-
.flatMap { URL(string: $0, relativeTo: baseURL) }
486+
.flatMap { URL(string: $0, relativeTo: environment.baseURL) }
487487
.map(\.absoluteURL)
488488
.map {
489489
NSAttributedString(markdownImageURL: $0)
490490
} ?? NSAttributedString()
491491
}
492492

493493
private func paragraphStyle(state: State) -> NSParagraphStyle {
494-
let pointSize = state.font.resolve(sizeCategory: sizeCategory).pointSize
494+
let pointSize = state.font.resolve(sizeCategory: environment.sizeCategory).pointSize
495495
let result = NSMutableParagraphStyle()
496496
result.setParagraphStyle(.default)
497-
result.baseWritingDirection = baseWritingDirection
498-
result.alignment = alignment
497+
result.baseWritingDirection = environment.baseWritingDirection
498+
result.alignment = environment.alignment
499+
result.lineSpacing = environment.lineSpacing
499500
result.paragraphSpacing = round(pointSize * state.paragraphSpacing)
500501
result.headIndent = round(pointSize * state.headIndent)
501502
result.tailIndent = round(pointSize * state.tailIndent)

0 commit comments

Comments
 (0)