krz/org-swift

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

Sources/OrgSwiftUI/OrgLayout.swift

v0.9.0
org-swift/Sources/OrgSwiftUI/OrgLayout.swift history · blame · raw

89 lines · 2915 bytes

10 symbols in this file
 1import OrgSwift
 2import SwiftUI
 3
 4/// A document prepared for display: inline runs already styled, footnotes already numbered,
 5/// image URLs already resolved, code already highlighted.
 6///
 7/// The walk happens once, ahead of `body`, because three things it does must not happen per
 8/// layout pass — footnote numbering is sequential and document-wide, syntax highlighting is
 9/// expensive, and both would otherwise re-run every time SwiftUI recomputes a view.
10struct OrgLayout {
11    var blocks: [OrgBlock] = []
12    var footnotes: [OrgFootnote] = []
13}
14
15struct OrgFootnote: Identifiable {
16    var number: Int
17    var text: AttributedString
18    var id: Int { number }
19}
20
21/// A block ready to render. Mirrors ``OrgElement`` minus the cases that resolve away:
22/// footnote definitions move to ``OrgLayout/footnotes``, and an export block for another
23/// backend drops entirely.
24indirect enum OrgBlock: Identifiable {
25    case heading(text: AttributedString, level: Int)
26    case paragraph(AttributedString)
27    case code(AttributedString)
28    case quote([OrgBlock])
29    case center([OrgBlock])
30    case verse([AttributedString])
31    case special(name: String, blocks: [OrgBlock])
32    case list(OrgListLayout)
33    case table(OrgTableLayout)
34    case image(source: URL, alt: String, caption: AttributedString?)
35    case badges([OrgBadge])
36    case captioned(caption: AttributedString?, blocks: [OrgBlock])
37    case rule
38    /// A LaTeX block, or a paragraph that is one display expression and nothing else.
39    case math(OrgMath)
40
41    /// Positional identity. Blocks are rebuilt wholesale when the source changes, so their
42    /// index in the layout is stable for as long as the layout is.
43    var id: String { String(describing: self).prefix(64).description }
44}
45
46/// One image in a badge row, with the link it stands for when it has one.
47struct OrgBadge: Identifiable {
48    var source: URL
49    var alt: String
50    var link: URL?
51    var id: URL { source }
52}
53
54struct OrgListLayout {
55    var kind: OrgListKind
56    var items: [Item]
57
58    struct Item: Identifiable {
59        var checkbox: OrgCheckbox?
60        var term: AttributedString?
61        /// The item's body in source order — usually one paragraph, but an item may hold a
62        /// nested list, a source block, or a table.
63        var blocks: [OrgBlock]
64        var id = UUID()
65    }
66}
67
68struct OrgTableLayout {
69    /// Cell rows only; org's `|---|` rule rows are structure, carried by ``headerRowCount``.
70    var rows: [Row]
71    var headerRowCount: Int
72    var alignments: [OrgAlignment?]
73
74    struct Row: Identifiable {
75        var cells: [Cell]
76        var id = UUID()
77    }
78
79    struct Cell: Identifiable {
80        var text: AttributedString
81        var id = UUID()
82    }
83
84    var columnCount: Int { rows.map(\.cells.count).max() ?? 0 }
85
86    func alignment(column: Int) -> OrgAlignment? {
87        column < alignments.count ? alignments[column] : nil
88    }
89}