aboutsummaryrefslogtreecommitdiff
path: root/Sources/NorgUI/Theme/NorgTheme.swift
blob: fa8ab251fd02f78cbc1e0a1734abfc2a595e84b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import NorgKit
import SwiftUI

/// Typography and colours used when rendering a Norg document.
public struct NorgTheme: Sendable {

  /// Font for a heading at the given (1-based) level.
  public var heading: @Sendable (Int) -> Font
  /// Base font for body text, list items and paragraphs.
  public var body: Font
  /// Font for the title of a definition, footnote or table cell.
  public var termFont: Font
  /// Foreground colour for `verbatim` inline spans.
  public var verbatimColor: Color
  /// Foreground colour for `spoiler` inline spans.
  public var spoilerForeground: Color
  /// Background colour for `spoiler` inline spans.
  public var spoilerBackground: Color
  /// Foreground colour applied to link spans.
  public var linkColor: Color
  /// Baseline offset applied to `superscript` spans.
  public var superscriptOffset: CGFloat
  /// Baseline offset applied to `subscript` spans.
  public var subscriptOffset: CGFloat
  /// Foreground colour for block quotes.
  public var quoteColor: Color
  /// Fill colour for the vertical bar drawn alongside a quote.
  public var quoteBarColor: Color
  /// Background fill for fenced code blocks and ranged tags.
  public var codeBackground: AnyShapeStyle
  /// Font for code block and ranged-tag contents.
  public var codeFont: Font
  /// Horizontal indentation applied per nesting level.
  public var indentWidth: CGFloat
  /// Vertical spacing between the label and contents of a verbatim block.
  public var verbatimLeadingSpace: CGFloat
  /// Padding applied around the contents of a verbatim block.
  public var verbatimPadding: CGFloat
  /// Vertical spacing between sibling blocks and nodes.
  public var blockSpacing: CGFloat
  /// Padding applied above a heading.
  public var headingTopPadding: CGFloat
  /// Point size used when rendering math. Inline math is rendered at this size
  /// and display blocks slightly larger.
  public var mathFontSize: CGFloat
  /// Renderer used for inline and block math. When `nil`, math is shown as
  /// monospaced text. See ``NorgMathRenderer``.
  public var mathRenderer: (any NorgMathRenderer)?
  /// Tint for a task's status symbol.
  public var statusTint: @Sendable (TaskStatus) -> Color
  /// SF Symbol name for a task's status symbol.
  public var statusSymbol: @Sendable (TaskStatus) -> String
  /// Localized label for a task status, shown in the status menu and used
  /// for accessibility. Defaults to ``TaskStatus/displayName``; override to
  /// supply wording from your own string catalog.
  public var statusLabel: @Sendable (TaskStatus) -> LocalizedStringResource

  public init(
    heading: @escaping @Sendable (Int) -> Font,
    body: Font,
    termFont: Font,
    verbatimColor: Color,
    spoilerForeground: Color,
    spoilerBackground: Color,
    linkColor: Color,
    superscriptOffset: CGFloat,
    subscriptOffset: CGFloat,
    quoteColor: Color,
    quoteBarColor: Color,
    codeBackground: AnyShapeStyle,
    codeFont: Font,
    indentWidth: CGFloat,
    verbatimLeadingSpace: CGFloat = 4,
    verbatimPadding: CGFloat = 10,
    blockSpacing: CGFloat = 10,
    headingTopPadding: CGFloat = 4,
    mathFontSize: CGFloat = 17,
    mathRenderer: (any NorgMathRenderer)? = nil,
    statusTint: @escaping @Sendable (TaskStatus) -> Color,
    statusSymbol: @escaping @Sendable (TaskStatus) -> String,
    statusLabel: @escaping @Sendable (TaskStatus) -> LocalizedStringResource = { $0.displayName }
  ) {
    self.heading = heading
    self.body = body
    self.termFont = termFont
    self.verbatimColor = verbatimColor
    self.spoilerForeground = spoilerForeground
    self.spoilerBackground = spoilerBackground
    self.linkColor = linkColor
    self.superscriptOffset = superscriptOffset
    self.subscriptOffset = subscriptOffset
    self.quoteColor = quoteColor
    self.quoteBarColor = quoteBarColor
    self.codeBackground = codeBackground
    self.codeFont = codeFont
    self.indentWidth = indentWidth
    self.verbatimLeadingSpace = verbatimLeadingSpace
    self.verbatimPadding = verbatimPadding
    self.blockSpacing = blockSpacing
    self.headingTopPadding = headingTopPadding
    self.mathFontSize = mathFontSize
    self.mathRenderer = mathRenderer
    self.statusTint = statusTint
    self.statusSymbol = statusSymbol
    self.statusLabel = statusLabel
  }

  /// The default theme.
  public static let `default` = NorgTheme(
    heading: { level in
      switch level {
      case 1: return .title.bold()
      case 2: return .title2.bold()
      case 3: return .title3.bold()
      case 4: return .headline
      case 5: return .subheadline.bold()
      default: return .subheadline
      }
    },
    body: .body,
    termFont: .body.bold(),
    verbatimColor: .pink,
    spoilerForeground: .secondary,
    spoilerBackground: .secondary.opacity(0.25),
    linkColor: .accentColor,
    superscriptOffset: 5,
    subscriptOffset: -3,
    quoteColor: .secondary,
    quoteBarColor: .secondary,
    codeBackground: AnyShapeStyle(.quaternary),
    codeFont: .system(.callout, design: .monospaced),
    indentWidth: 18,
    mathRenderer: defaultMathRenderer,
    statusTint: { status in
      switch status {
      case .done: return .green
      case .urgent: return .red
      case .pending: return .blue
      default: return .secondary
      }
    },
    statusSymbol: { status in
      switch status {
      case .undone: return "circle"
      case .done: return "checkmark.circle.fill"
      case .needsInput: return "questionmark.circle"
      case .urgent: return "exclamationmark.circle"
      case .recurring: return "repeat.circle"
      case .pending: return "ellipsis.circle"
      case .onHold: return "pause.circle"
      case .cancelled: return "xmark.circle"
      }
    }
  )

  #if SwiftMathSupport
    static let defaultMathRenderer: (any NorgMathRenderer)? = SwiftMathRenderer()
  #else
    static let defaultMathRenderer: (any NorgMathRenderer)? = nil
  #endif
}