Skip to content

Commit 8205469

Browse files
committed
Update caching and README.
1 parent 5b47f2d commit 8205469

File tree

4 files changed

+116
-40
lines changed

4 files changed

+116
-40
lines changed

README.md

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ LaTeX("e^{i\\pi}+1=0")
6262

6363
![Euler's Identity](./assets/images/euler.png)
6464

65+
##### Equations
66+
67+
`LaTexSwiftUI` can parse and render equations (aside from the entire input string) defined with the following terminators.
68+
69+
| Terminators |
70+
|-------------|
71+
| `$...$` |
72+
| `$$...$$` |
73+
| `\[...\]` |
74+
| `\begin{equation}...\end{equation}` |
75+
| `\begin{equation*}...\end{equation*}` |
76+
6577
#### Image Rendering Mode
6678

6779
You can specify the rendering mode of the rendered equations so that they either take on the style of the surrounding text or display the style rendered by MathJax. The default behavior is to use the `template` rendering mode so that images match surrounding text.
@@ -144,10 +156,35 @@ LaTeX("$x^2<1$")
144156
For more control over the MathJax rendering, you can pass a `TeXInputProcessorOptions` object to the view.
145157

146158
```swift
147-
// Add AMS style equation numbering
148-
LaTeX("""
149-
\\begin{equation}
150-
e^{i\\pi}+1=0
151-
\\end{equation}
152-
""").texOptions(TeXInputProcessorOptions(loadPackages: TeXInputProcessorOptions.Packages.all))
159+
LaTeX("Hello, $\\LaTeX$!")
160+
.texOptions(TeXInputProcessorOptions(loadPackages: [TeXInputProcessorOptions.Packages.base]))
161+
```
162+
163+
### Caching
164+
165+
`LaTeXSwiftUI` caches its SVG responses from MathJax and the images rendered as a result of the view's environment. If you want to control the cache, then you can access the static `cache` property.
166+
167+
The caches are managed automatically, but if, for example, you wanted to clear the cache manually you may do so.
168+
169+
```swift
170+
// Clear the SVG data cache.
171+
LaTeX.dataCache?.removeAll()
172+
173+
// Clear the rendered image cache.
174+
LaTeX.imageCache.removeAll()
175+
```
176+
177+
`LaTeXSwiftUI` uses the [caching](https://github.com/kean/Nuke/tree/master/Sources/Nuke/Caching) components of the [Nuke](https://github.com/kean/Nuke) package.
178+
179+
### Preloading
180+
181+
SVGs and images are rendered and cached on demand, but there may be situations where you want to preload the data so that there is no lag when the view appears.
182+
183+
```swift
184+
VStack {
185+
ForEach(expressions, id: \.self) { expression in
186+
LaTeX(expression)
187+
.preload()
188+
}
189+
}
153190
```

Sources/LaTeXSwiftUI/LaTeX.swift

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
//
77

88
import HTMLEntities
9+
import MathJaxSwift
10+
import Nuke
911
import SwiftUI
1012

1113
public struct LaTeX: View {
@@ -48,6 +50,16 @@ public struct LaTeX: View {
4850
case error
4951
}
5052

53+
/// The package's shared data cache.
54+
public static var dataCache: DataCache? {
55+
Renderer.shared.dataCache
56+
}
57+
58+
/// The package's shared image cache.
59+
public static var imageCache: ImageCache {
60+
Renderer.shared.imageCache
61+
}
62+
5163
// MARK: Public properties
5264

5365
/// The view's LaTeX input string.
@@ -105,24 +117,49 @@ public struct LaTeX: View {
105117
public var body: some View {
106118
switch blockMode {
107119
case .alwaysInline:
108-
asText(forceInline: true)
120+
blocksAsText(blocks, forceInline: true)
109121
case .blockText:
110-
asText(forceInline: false)
122+
blocksAsText(blocks)
111123
case .blockViews:
112-
asStack()
124+
blocksAsStack(blocks)
113125
}
114126
}
115127

116128
}
117129

130+
// MARK: Public methods
131+
132+
extension LaTeX {
133+
134+
/// Preloads the view's components.
135+
///
136+
/// - Returns: A LaTeX view whose components have been preloaded.
137+
@MainActor public func preload() -> LaTeX {
138+
switch blockMode {
139+
case .alwaysInline:
140+
blocksAsText(blocks, forceInline: true)
141+
case .blockText:
142+
blocksAsText(blocks)
143+
case .blockViews:
144+
blocksAsStack(blocks)
145+
}
146+
return self
147+
}
148+
149+
}
150+
151+
// MARK: Private methods
152+
118153
extension LaTeX {
119154

120155
/// The view's input rendered as a text view.
121156
///
122157
/// - Parameter forceInline: Whether or not block equations should be forced
123158
/// as inline.
124159
/// - Returns: A text view.
125-
@MainActor private func asText(forceInline: Bool) -> Text {
160+
@MainActor
161+
@discardableResult
162+
private func blocksAsText(_ blocks: [ComponentBlock], forceInline: Bool = false) -> Text {
126163
blocks.map { block in
127164
let text = text(for: block)
128165
return block.isEquationBlock && !forceInline ?
@@ -134,7 +171,9 @@ extension LaTeX {
134171
/// The view's input rendered as a vertical stack of views.
135172
///
136173
/// - Returns: A stack view.
137-
@MainActor private func asStack() -> some View {
174+
@MainActor
175+
@discardableResult
176+
private func blocksAsStack(_ blocks: [ComponentBlock]) -> some View {
138177
VStack(alignment: .leading, spacing: lineSpacing + 4) {
139178
ForEach(blocks, id: \.self) { block in
140179
if block.isEquationBlock,
@@ -188,28 +227,20 @@ extension LaTeX {
188227
@available(iOS 16.1, *)
189228
struct LaTeX_Previews: PreviewProvider {
190229
static var previews: some View {
191-
// VStack {
192-
// LaTeX("Hello, $\\LaTeX$!")
193-
// .font(.title)
194-
//
195-
// LaTeX("Hello, $\\LaTeX$!")
196-
// .font(.title2)
197-
// .foregroundColor(.cyan)
198-
//
199-
// LaTeX("Hello, $\\LaTeX$!")
200-
// .font(.title3)
201-
// .foregroundColor(.pink)
202-
// }
203-
// .fontDesign(.serif)
204-
// .previewLayout(.sizeThatFits)
205-
206230
VStack {
207-
LaTeX("This is a block equation with some block after it $$\\text{and this is some text on the next line}$$ with some more text that comes after it!")
208-
.lineSpacing(5)
209-
Divider()
210-
Text("This is a block equation with some block afte and it wraps and this is some text on the next line and blah blah with some more text that comes after it!")
211-
.lineSpacing(5)
231+
LaTeX("Hello, $\\LaTeX$!")
232+
.font(.title)
233+
234+
LaTeX("Hello, $\\LaTeX$!")
235+
.font(.title2)
236+
.foregroundColor(.cyan)
237+
238+
LaTeX("Hello, $\\LaTeX$!")
239+
.font(.title3)
240+
.foregroundColor(.pink)
212241
}
242+
.fontDesign(.serif)
243+
.previewLayout(.sizeThatFits)
213244
}
214245

215246
}

Sources/LaTeXSwiftUI/Models/Renderer.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,25 @@ internal class Renderer {
7878
private let mathjax: MathJax?
7979

8080
/// The renderer's data cache.
81-
private let cache: DataCache?
81+
internal let dataCache: DataCache?
82+
83+
/// The renderer's image cache.
84+
internal let imageCache: ImageCache
8285

8386
// MARK: Initializers
8487

8588
/// Initializes a renderer with a MathJax instance.
8689
init() {
8790
do {
88-
cache = try DataCache(name: "mathJaxRenderCache")
91+
dataCache = try DataCache(name: "mathJaxRenderDataCache")
8992
}
9093
catch {
9194
logError("Error creating DataCache instance: \(error)")
92-
cache = nil
95+
dataCache = nil
9396
}
9497

98+
imageCache = ImageCache()
99+
95100
do {
96101
mathjax = try MathJax(preferredOutputFormat: .svg)
97102
}
@@ -164,7 +169,7 @@ extension Renderer {
164169
let cacheKey = ImageCacheKey(svg: svg, xHeight: xHeight)
165170

166171
// Check the cache for an image
167-
if let imageData = cache?[cacheKey.key()], let image = _Image(imageData: imageData, scale: displayScale) {
172+
if let image = imageCache[Nuke.ImageCacheKey(key: cacheKey.key())]?.image {
168173
return (Image(image: image)
169174
.renderingMode(renderingMode)
170175
.antialiased(true)
@@ -181,14 +186,13 @@ extension Renderer {
181186
#if os(iOS)
182187
renderer.scale = UIScreen.main.scale
183188
let image = renderer.image
184-
cache?[cacheKey.key()] = image?.pngData()
185189
#else
186190
renderer.scale = NSScreen.main?.backingScaleFactor ?? 1
187191
let image = renderer.image
188-
cache?[cacheKey.key()] = image?.tiffRepresentation
189192
#endif
190193

191194
if let image = image {
195+
imageCache[Nuke.ImageCacheKey(key: cacheKey.key())] = ImageContainer(image: image)
192196
return (Image(image: image)
193197
.renderingMode(renderingMode)
194198
.antialiased(true)
@@ -242,7 +246,7 @@ extension Renderer {
242246
texOptions: texOptions)
243247

244248
// Do we have the SVG in the cache?
245-
if let svgData = cache?[cacheKey.key()] {
249+
if let svgData = dataCache?[cacheKey.key()] {
246250
renderedComponents.append(Component(
247251
text: component.text,
248252
type: component.type,
@@ -270,7 +274,7 @@ extension Renderer {
270274

271275
// Create the SVG
272276
let svg = try SVG(svgString: svgString, errorText: errorText)
273-
cache?[cacheKey.key()] = try JSONEncoder().encode(svg)
277+
dataCache?[cacheKey.key()] = try JSONEncoder().encode(svg)
274278

275279
// Save the rendered component
276280
renderedComponents.append(Component(

Sources/LaTeXSwiftUI/Views/HorizontalImageScroller.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import SwiftUI
99

10+
/// A view that contains an image that can be scrolled horizontally.
1011
internal struct HorizontalImageScroller: View {
1112

1213
/// The image to display.
@@ -15,11 +16,14 @@ internal struct HorizontalImageScroller: View {
1516
/// The height of the image.
1617
let height: CGFloat
1718

19+
/// Whether the scroll view should show its indicators.
20+
var showsIndicators: Bool = false
21+
1822
// MARK: View body
1923

2024
var body: some View {
2125
GeometryReader { geometry in
22-
ScrollView(.horizontal, showsIndicators: false) {
26+
ScrollView(.horizontal, showsIndicators: showsIndicators) {
2327
HStack { image }
2428
.frame(minWidth: geometry.size.width)
2529
}

0 commit comments

Comments
 (0)