Tell the math hook the text style around each expression !12

merged merged by cmc on 2026-10-16 02:12 UTC · krz/org-swift:math-text-style into main

4 files changed, +41 −10

Layout: unified · split

README.md +5 −4
@@ -121,12 +121,13 @@ OrgView(orgSource, options: options, textTransform: { text in
121121LaTeX fragments (`$…$`, `$$…$$`, `\(…\)`, `\[…\]`, `\begin{…}…\end{…}`) and
122122`\begin{…}` blocks parse to `.math` objects and `.latexBlock` elements, found by
123123go-org's rules with org's border rule for `$…$`. The HTML and `AttributedString`
124renderers 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:
124renderers show their source. `OrgView` draws them through `math`, which is given the
125text style of the run the expression sits in and returns a `Text` (an image from a
126typesetter, say) or nil to keep the source:
126127
127128```swift
128OrgView(orgSource, options: options, math: { math in
129 typeset(math.tex, display: math.display).map { Text($0) }
129OrgView(orgSource, options: options, math: { math, style in
130 typeset(math.tex, display: math.display, style: style).map { Text($0) }
130131})
131132```
132133
Sources/OrgSwiftUI/OrgInlineStyler.swift +4
@@ -62,6 +62,10 @@ struct OrgInlineStyler {
6262 break
6363 }
6464
65 if string[range][OrgMathAttribute.self] != nil {
66 string[range][OrgMathStyleAttribute.self] = textStyle
67 }
68
6569 // Always set the font, never only on modified runs: a run with no emphasis
6670 // would otherwise inherit the surrounding view's font and a heading, table cell
6771 // or footnote would render at body size.
Sources/OrgSwiftUI/OrgMathText.swift +12 −4
@@ -2,8 +2,15 @@ import OrgSwift
22import SwiftUI
33
44/// 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.
6public 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.
7public typealias OrgMathRenderer = @MainActor (OrgMath, Font.TextStyle) -> Text?
8
9/// The text style a math run is set in, recorded by ``OrgInlineStyler`` for the renderer.
10enum OrgMathStyleAttribute: AttributedStringKey {
11 typealias Value = Font.TextStyle
12 static let name = "OrgSwiftUI.mathStyle"
13}
714
815private struct OrgMathKey: EnvironmentKey {
916 static let defaultValue: OrgMathRenderer? = nil
@@ -28,7 +35,8 @@ func orgText(_ string: AttributedString, math: OrgMathRenderer?) -> Text {
2835 var result = Text(verbatim: "")
2936 for (expression, range) in string.runs[OrgMathAttribute.self] {
3037 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 {
3240 result = Text("\(result)\(Text(piece))")
3341 continue
3442 }
@@ -45,7 +53,7 @@ struct OrgMathBlockView: View {
4553 @Environment(\.orgMath) private var renderer
4654
4755 var body: some View {
48 if let drawn = renderer?(math) {
56 if let drawn = renderer?(math, .body) {
4957 ViewThatFits(in: .horizontal) {
5058 drawn.frame(maxWidth: .infinity)
5159 ScrollView(.horizontal, showsIndicators: false) { drawn }
Tests/OrgSwiftUITests/OrgMathLayoutTests.swift +20 −2
@@ -48,19 +48,37 @@ struct OrgMathLayoutTests {
4848 Issue.record("expected a paragraph"); return
4949 }
5050 var drawn = 0
51 _ = orgText(text) { _ in
51 _ = orgText(text) { _, _ in
5252 drawn += 1
5353 return Text(verbatim: "·")
5454 }
5555 #expect(drawn == 1)
5656 }
5757
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
5876 @Test func orgTextDrawsEachExpression() {
5977 guard case .paragraph(let text) = layout("$x$$x$ and $y$").blocks.first else {
6078 Issue.record("expected a paragraph"); return
6179 }
6280 var drawn: [String] = []
63 _ = orgText(text) { math in
81 _ = orgText(text) { math, _ in
6482 drawn.append(math.tex)
6583 return Text(verbatim: "·")
6684 }