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 {
1515
1616 /// - Parameter style: the text style of the block this run sits in, so emphasis inside a
1717 /// 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.
1822 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
2025 ) -> AttributedString {
2126 var string = renderer.inline(objects, &notes)
2227
@@ -26,7 +31,7 @@ struct OrgInlineStyler {
2631
2732 var textStyle = style
2833 var monospaced = false
29 var bold = false
34 var bold = forceBold
3035 var italic = false
3136
3237 if intents.contains(.stronglyEmphasized) { bold = true }
@@ -57,12 +62,13 @@ struct OrgInlineStyler {
5762 break
5863 }
5964
60 if bold || italic || monospaced || textStyle != style {
61 var font = Font.system(textStyle, design: monospaced ? .monospaced : .default)
62 if bold { font = font.bold() }
63 if italic { font = font.italic() }
64 string[range].font = font
65 }
65 // Always set the font, never only on modified runs: a run with no emphasis
66 // would otherwise inherit the surrounding view's font and a heading, table cell
67 // or footnote would render at body size.
68 var font = Font.system(textStyle, design: monospaced ? .monospaced : .default)
69 if bold { font = font.bold() }
70 if italic { font = font.italic() }
71 string[range].font = font
6672 }
6773 return string
6874 }
Sources/OrgSwiftUI/OrgLayoutBuilder.swift +4 −3
@@ -28,9 +28,10 @@ struct OrgLayoutBuilder {
2828 }
2929
3030 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
3233 ) -> AttributedString {
33 inline.attributed(objects, &notes, style: style)
34 inline.attributed(objects, &notes, style: style, bold: bold)
3435 }
3536
3637 /// One element to zero or more blocks. Zero for a footnote definition (it moves to the
@@ -50,7 +51,7 @@ struct OrgLayoutBuilder {
5051 title += cookie
5152 }
5253 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)
5455 for tag in heading.tags {
5556 var chip = AttributedString(" \(tag)")
5657 chip.foregroundColor = .secondary
Tests/OrgSwiftUITests/OrgLayoutTests.swift +22
@@ -179,6 +179,28 @@ struct OrgLayoutTests {
179179 #expect(layout("#+begin_export latex\n\\emph{hi}\n#+end_export").blocks.isEmpty)
180180 }
181181
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
182204 /// Emphasis nests, and the styler turns intents into fonts rather than dropping them.
183205 @Test
184206 func inlineStylingCarriesNestedEmphasis() {