Commit 799d4ba18e
799d4ba18ece2bd259bd4760cca46faeae0fd1e1
parent: 49b729cc96
Verified · cmc
cmc <hello@cleberg.net> · 2026-10-16 02:12 UTC
Tell the math hook the text style around each expression
OrgMathRenderer is now (OrgMath, Font.TextStyle) -> Text?: the inline
styler records the style each math run is set in, so a renderer can set
math in a heading at the heading's size. Display blocks pass .body.
Ref krz/gitbay-ios#41
Layout: unified · split
README.md
+5 −4
| @@ -121,12 +121,13 @@ OrgView(orgSource, options: options, textTransform: { text in |
| 121 | 121 | LaTeX fragments (`$…$`, `$$…$$`, `\(…\)`, `\[…\]`, `\begin{…}…\end{…}`) and |
| 122 | 122 | `\begin{…}` blocks parse to `.math` objects and `.latexBlock` elements, found by |
| 123 | 123 | go-org's rules with org's border rule for `$…$`. The HTML and `AttributedString` |
| 124 | | renderers show their source. `OrgView` draws them through `math`, which returns a |
| 125 | | `Text` (an image from a typesetter, say) or nil to keep the source: |
| 124 | renderers show their source. `OrgView` draws them through `math`, which is given the |
| 125 | text style of the run the expression sits in and returns a `Text` (an image from a |
| 126 | typesetter, say) or nil to keep the source: |
| 126 | 127 | |
| 127 | 128 | ```swift |
| 128 | | OrgView(orgSource, options: options, math: { math in |
| 129 | | typeset(math.tex, display: math.display).map { Text($0) } |
| 129 | OrgView(orgSource, options: options, math: { math, style in |
| 130 | typeset(math.tex, display: math.display, style: style).map { Text($0) } |
| 130 | 131 | }) |
| 131 | 132 | ``` |
| 132 | 133 | |
Sources/OrgSwiftUI/OrgInlineStyler.swift
+4
| @@ -62,6 +62,10 @@ struct OrgInlineStyler { |
| 62 | 62 | break |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | if string[range][OrgMathAttribute.self] != nil { |
| 66 | string[range][OrgMathStyleAttribute.self] = textStyle |
| 67 | } |
| 68 | |
| 65 | 69 | // Always set the font, never only on modified runs: a run with no emphasis |
| 66 | 70 | // would otherwise inherit the surrounding view's font and a heading, table cell |
| 67 | 71 | // or footnote would render at body size. |
Sources/OrgSwiftUI/OrgMathText.swift
+12 −4
| @@ -2,8 +2,15 @@ import OrgSwift |
| 2 | 2 | import SwiftUI |
| 3 | 3 | |
| 4 | 4 | /// Draws one TeX expression for ``OrgView``: a `Text` holding it, typically an image set on |
| 5 | | /// the baseline, or nil to show the source as written. |
| 6 | | public typealias OrgMathRenderer = @MainActor (OrgMath) -> Text? |
| 5 | /// the baseline, or nil to show the source as written. The style is that of the text the |
| 6 | /// expression sits in, so math in a heading can be set at the heading's size. |
| 7 | public typealias OrgMathRenderer = @MainActor (OrgMath, Font.TextStyle) -> Text? |
| 8 | |
| 9 | /// The text style a math run is set in, recorded by ``OrgInlineStyler`` for the renderer. |
| 10 | enum OrgMathStyleAttribute: AttributedStringKey { |
| 11 | typealias Value = Font.TextStyle |
| 12 | static let name = "OrgSwiftUI.mathStyle" |
| 13 | } |
| 7 | 14 | |
| 8 | 15 | private struct OrgMathKey: EnvironmentKey { |
| 9 | 16 | static let defaultValue: OrgMathRenderer? = nil |
| @@ -28,7 +35,8 @@ func orgText(_ string: AttributedString, math: OrgMathRenderer?) -> Text { |
| 28 | 35 | var result = Text(verbatim: "") |
| 29 | 36 | for (expression, range) in string.runs[OrgMathAttribute.self] { |
| 30 | 37 | let piece = AttributedString(string[range]) |
| 31 | | guard let expression, let drawn = math(expression) else { |
| 38 | let style = piece.runs.first?[OrgMathStyleAttribute.self] ?? .body |
| 39 | guard let expression, let drawn = math(expression, style) else { |
| 32 | 40 | result = Text("\(result)\(Text(piece))") |
| 33 | 41 | continue |
| 34 | 42 | } |
| @@ -45,7 +53,7 @@ struct OrgMathBlockView: View { |
| 45 | 53 | @Environment(\.orgMath) private var renderer |
| 46 | 54 | |
| 47 | 55 | var body: some View { |
| 48 | | if let drawn = renderer?(math) { |
| 56 | if let drawn = renderer?(math, .body) { |
| 49 | 57 | ViewThatFits(in: .horizontal) { |
| 50 | 58 | drawn.frame(maxWidth: .infinity) |
| 51 | 59 | ScrollView(.horizontal, showsIndicators: false) { drawn } |
Tests/OrgSwiftUITests/OrgMathLayoutTests.swift
+20 −2
| @@ -48,19 +48,37 @@ struct OrgMathLayoutTests { |
| 48 | 48 | Issue.record("expected a paragraph"); return |
| 49 | 49 | } |
| 50 | 50 | var drawn = 0 |
| 51 | | _ = orgText(text) { _ in |
| 51 | _ = orgText(text) { _, _ in |
| 52 | 52 | drawn += 1 |
| 53 | 53 | return Text(verbatim: "·") |
| 54 | 54 | } |
| 55 | 55 | #expect(drawn == 1) |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | @Test func theHookIsToldTheStyleOfTheTextAroundIt() { |
| 59 | let blocks = layout("* Heading $x$\n\nbody $y$").blocks |
| 60 | var styles: [String: Font.TextStyle] = [:] |
| 61 | for block in blocks { |
| 62 | let text: AttributedString |
| 63 | switch block { |
| 64 | case .heading(let heading, _): text = heading |
| 65 | case .paragraph(let paragraph): text = paragraph |
| 66 | default: continue |
| 67 | } |
| 68 | _ = orgText(text) { math, style in |
| 69 | styles[math.tex] = style |
| 70 | return Text(verbatim: "·") |
| 71 | } |
| 72 | } |
| 73 | #expect(styles == ["x": .title, "y": .body]) |
| 74 | } |
| 75 | |
| 58 | 76 | @Test func orgTextDrawsEachExpression() { |
| 59 | 77 | guard case .paragraph(let text) = layout("$x$$x$ and $y$").blocks.first else { |
| 60 | 78 | Issue.record("expected a paragraph"); return |
| 61 | 79 | } |
| 62 | 80 | var drawn: [String] = [] |
| 63 | | _ = orgText(text) { math in |
| 81 | _ = orgText(text) { math, _ in |
| 64 | 82 | drawn.append(math.tex) |
| 65 | 83 | return Text(verbatim: "·") |
| 66 | 84 | } |