krz/org-swift

A dependency-free Swift library that renders org-mode to sanitized HTML. html library org-mode swift

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, &notes) 26 var string = renderer.inline(objects, &notes)
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, &notes, style: style) 34 inline.attributed(objects, &notes, 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, &notes, style: Self.headingStyle(level)) 54 title += text(heading.title, &notes, 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() {