Commit d1d98c0638
d1d98c06381f4baa32d06f47e21e1f491771c259
parent: 032cfe34d5
Unsigned
cmc <hello@cleberg.net> · 2026-08-31 18:10 UTC
Give every inline run its block's font
The styler set a font only on runs it modified, so a run with no emphasis
carried none and inherited whatever font the surrounding view had. Headings
rendered at body size and weight, and so did table cells, footnotes and
captions. Every run now carries the font of the block it sits in, and
headings are bold.
Found rendering krz/gitbay's README in the app.
Layout: unified · split
Sources/OrgSwiftUI/OrgInlineStyler.swift
+14 −8
| @@ -15,8 +15,13 @@ struct OrgInlineStyler { |
| 15 | |
15 | |
| 16 | /// - Parameter style: the text style of the block this run sits in, so emphasis inside a |
16 | /// - Parameter style: the text style of the block this run sits in, so emphasis inside a |
| 17 | /// heading bolds the heading's font rather than the body font. |
17 | /// heading bolds the heading's font rather than the body font. |
| |
18 | /// - Parameters: |
| |
19 | /// - style: the text style of the block this run sits in, so emphasis inside a heading |
| |
20 | /// bolds the heading's font rather than the body font. |
| |
21 | /// - bold: forces every run bold, for headings. |
| 18 | func attributed( |
22 | func attributed( |
| 19 | _ objects: [OrgObject], _ notes: inout OrgFootnotes, style: Font.TextStyle = .body |
23 | _ objects: [OrgObject], _ notes: inout OrgFootnotes, |
| |
24 | style: Font.TextStyle = .body, bold forceBold: Bool = false |
| 20 | ) -> AttributedString { |
25 | ) -> AttributedString { |
| 21 | var string = renderer.inline(objects, ¬es) |
26 | var string = renderer.inline(objects, ¬es) |
| 22 | |
27 | |
| @@ -26,7 +31,7 @@ struct OrgInlineStyler { |
| 26 | |
31 | |
| 27 | var textStyle = style |
32 | var textStyle = style |
| 28 | var monospaced = false |
33 | var monospaced = false |
| 29 | var bold = false |
34 | var bold = forceBold |
| 30 | var italic = false |
35 | var italic = false |
| 31 | |
36 | |
| 32 | if intents.contains(.stronglyEmphasized) { bold = true } |
37 | if intents.contains(.stronglyEmphasized) { bold = true } |
| @@ -57,12 +62,13 @@ struct OrgInlineStyler { |
| 57 | break |
62 | break |
| 58 | } |
63 | } |
| 59 | |
64 | |
| 60 | if bold || italic || monospaced || textStyle != style { |
65 | // Always set the font, never only on modified runs: a run with no emphasis |
| 61 | var font = Font.system(textStyle, design: monospaced ? .monospaced : .default) |
66 | // would otherwise inherit the surrounding view's font and a heading, table cell |
| 62 | if bold { font = font.bold() } |
67 | // or footnote would render at body size. |
| 63 | if italic { font = font.italic() } |
68 | var font = Font.system(textStyle, design: monospaced ? .monospaced : .default) |
| 64 | string[range].font = font |
69 | if bold { font = font.bold() } |
| 65 | } |
70 | if italic { font = font.italic() } |
| |
71 | string[range].font = font |
| 66 | } |
72 | } |
| 67 | return string |
73 | return string |
| 68 | } |
74 | } |
Sources/OrgSwiftUI/OrgLayoutBuilder.swift
+4 −3
| @@ -28,9 +28,10 @@ struct OrgLayoutBuilder { |
| 28 | } |
28 | } |
| 29 | |
29 | |
| 30 | private func text( |
30 | private func text( |
| 31 | _ objects: [OrgObject], _ notes: inout OrgFootnotes, style: Font.TextStyle = .body |
31 | _ objects: [OrgObject], _ notes: inout OrgFootnotes, |
| |
32 | style: Font.TextStyle = .body, bold: Bool = false |
| 32 | ) -> AttributedString { |
33 | ) -> AttributedString { |
| 33 | inline.attributed(objects, ¬es, style: style) |
34 | inline.attributed(objects, ¬es, style: style, bold: bold) |
| 34 | } |
35 | } |
| 35 | |
36 | |
| 36 | /// One element to zero or more blocks. Zero for a footnote definition (it moves to the |
37 | /// One element to zero or more blocks. Zero for a footnote definition (it moves to the |
| @@ -50,7 +51,7 @@ struct OrgLayoutBuilder { |
| 50 | title += cookie |
51 | title += cookie |
| 51 | } |
52 | } |
| 52 | let level = min(6, max(1, heading.level + options.headingLevelOffset)) |
53 | let level = min(6, max(1, heading.level + options.headingLevelOffset)) |
| 53 | title += text(heading.title, ¬es, style: Self.headingStyle(level)) |
54 | title += text(heading.title, ¬es, style: Self.headingStyle(level), bold: true) |
| 54 | for tag in heading.tags { |
55 | for tag in heading.tags { |
| 55 | var chip = AttributedString(" \(tag)") |
56 | var chip = AttributedString(" \(tag)") |
| 56 | chip.foregroundColor = .secondary |
57 | chip.foregroundColor = .secondary |
Tests/OrgSwiftUITests/OrgLayoutTests.swift
+22
| @@ -179,6 +179,28 @@ struct OrgLayoutTests { |
| 179 | #expect(layout("#+begin_export latex\n\\emph{hi}\n#+end_export").blocks.isEmpty) |
179 | #expect(layout("#+begin_export latex\n\\emph{hi}\n#+end_export").blocks.isEmpty) |
| 180 | } |
180 | } |
| 181 | |
181 | |
| |
182 | /// Every run carries a font, not only the emphasised ones. A plain heading run with no |
| |
183 | /// font would inherit the surrounding view's and render at body size. |
| |
184 | @Test |
| |
185 | func everyRunCarriesItsBlockFont() { |
| |
186 | guard case .heading(let title, _) = layout("* Plain heading").blocks.first else { |
| |
187 | Issue.record("expected a heading"); return |
| |
188 | } |
| |
189 | #expect(title.runs.allSatisfy { $0.font != nil }) |
| |
190 | #expect(title.runs.contains { $0.font == Font.system(.title, design: .default).bold() }) |
| |
191 | |
| |
192 | // Same for the blocks that render at a non-body style. |
| |
193 | guard case .table(let table) = layout("| a |\n|---|\n| b |").blocks.first else { |
| |
194 | Issue.record("expected a table"); return |
| |
195 | } |
| |
196 | #expect(table.rows.allSatisfy { $0.cells.allSatisfy { cell in |
| |
197 | cell.text.runs.allSatisfy { $0.font != nil } |
| |
198 | } }) |
| |
199 | |
| |
200 | let notes = layout("Claim[fn:a]\n\n[fn:a] note").footnotes |
| |
201 | #expect(notes.first?.text.runs.allSatisfy { $0.font != nil } == true) |
| |
202 | } |
| |
203 | |
| 182 | /// Emphasis nests, and the styler turns intents into fonts rather than dropping them. |
204 | /// Emphasis nests, and the styler turns intents into fonts rather than dropping them. |
| 183 | @Test |
205 | @Test |
| 184 | func inlineStylingCarriesNestedEmphasis() { |
206 | func inlineStylingCarriesNestedEmphasis() { |