lotio 1.0.0-test
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +447 -0
- package/browser/index.js +451 -0
- package/browser/lotio.js +2 -0
- package/browser/lotio.wasm +0 -0
- package/browser/wasm.js +410 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# Lotio
|
|
2
|
+
|
|
3
|
+
High-performance Lottie animation frame renderer using Skia. Renders animations to PNG frames for video encoding.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Homebrew (Recommended)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
brew tap matrunchyk/lotio
|
|
11
|
+
brew install lotio
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
This installs:
|
|
15
|
+
- Binary: `lotio`
|
|
16
|
+
- Headers: `/opt/homebrew/include/lotio/` (or `/usr/local/include/lotio/`)
|
|
17
|
+
- Libraries: `/opt/homebrew/lib/` (Skia static libraries)
|
|
18
|
+
|
|
19
|
+
### From Source
|
|
20
|
+
|
|
21
|
+
**Prerequisites (macOS):**
|
|
22
|
+
```bash
|
|
23
|
+
brew install fontconfig freetype harfbuzz icu4c libpng ninja python@3.11
|
|
24
|
+
xcode-select --install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Build:**
|
|
28
|
+
```bash
|
|
29
|
+
# Build lotio (binary build with zero bundled dependencies)
|
|
30
|
+
./scripts/build_binary.sh
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Usage
|
|
34
|
+
|
|
35
|
+
### Command Line
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
lotio [--stream] [--debug] [--layer-overrides <config.json>] [--text-padding <0.0-1.0>] [--text-measurement-mode <fast|accurate|pixel-perfect>] <input.json> <output_dir> [fps]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Options:**
|
|
42
|
+
- `--stream` - Stream frames to stdout as PNG (for piping to ffmpeg)
|
|
43
|
+
- `--debug` - Enable debug output
|
|
44
|
+
- `--layer-overrides` - Path to layer overrides JSON (for text auto-fit, dynamic text values, and image path overrides)
|
|
45
|
+
- `--text-padding` - Text padding factor (0.0-1.0, default: 0.97 = 3% padding)
|
|
46
|
+
- `--text-measurement-mode` - Text measurement mode: `fast` | `accurate` | `pixel-perfect` (default: `accurate`)
|
|
47
|
+
- `--version` - Print version information and exit
|
|
48
|
+
- `--help, -h` - Show help message
|
|
49
|
+
- `fps` - Frames per second for output (default: 25)
|
|
50
|
+
|
|
51
|
+
**Examples:**
|
|
52
|
+
```bash
|
|
53
|
+
# Render to PNG frames
|
|
54
|
+
lotio animation.json frames/ 30
|
|
55
|
+
|
|
56
|
+
# Stream to ffmpeg
|
|
57
|
+
lotio --stream animation.json - | ffmpeg -f image2pipe -i - output.mp4
|
|
58
|
+
|
|
59
|
+
# With layer overrides
|
|
60
|
+
lotio --layer-overrides layer-overrides.json animation.json frames/
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Docker (Recommended for Video Output)
|
|
64
|
+
|
|
65
|
+
**Quick start with automatic video encoding:**
|
|
66
|
+
```bash
|
|
67
|
+
docker run --rm -v $(pwd):/workspace matrunchyk/lotio-ffmpeg:latest \
|
|
68
|
+
data.json - 30 --layer-overrides layer-overrides.json --output video.mov
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Available images:**
|
|
72
|
+
- `matrunchyk/lotio-ffmpeg:latest` - lotio + FFmpeg with automatic video encoding (recommended)
|
|
73
|
+
- `matrunchyk/lotio:latest` - lotio binary only (for programmatic use)
|
|
74
|
+
|
|
75
|
+
**Multi-platform support:** Both images support `linux/arm64` and `linux/amd64`.
|
|
76
|
+
|
|
77
|
+
See [Docker Documentation](docs/docker.html) for detailed usage.
|
|
78
|
+
|
|
79
|
+
### Browser (WebAssembly)
|
|
80
|
+
|
|
81
|
+
Install from npm:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
npm install @matrunchyk/lotio
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Basic Usage:**
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
import Lotio, { State, TextMeasurementMode } from '@matrunchyk/lotio';
|
|
91
|
+
|
|
92
|
+
// Load fonts
|
|
93
|
+
const fontResponse = await fetch('./fonts/OpenSans-Bold.ttf');
|
|
94
|
+
const fontData = new Uint8Array(await fontResponse.arrayBuffer());
|
|
95
|
+
|
|
96
|
+
// Load animation
|
|
97
|
+
const animationResponse = await fetch('./animation.json');
|
|
98
|
+
const animationData = await animationResponse.json();
|
|
99
|
+
|
|
100
|
+
// Create animation instance
|
|
101
|
+
const animation = new Lotio({
|
|
102
|
+
fonts: [{ name: 'OpenSans-Bold', data: fontData }],
|
|
103
|
+
fps: 30,
|
|
104
|
+
animation: animationData,
|
|
105
|
+
layerOverrides: { /* optional layer overrides */ },
|
|
106
|
+
textPadding: 0.97, // Optional: text padding factor (default: 0.97)
|
|
107
|
+
textMeasurementMode: TextMeasurementMode.ACCURATE, // Optional: TextMeasurementMode.FAST | TextMeasurementMode.ACCURATE | TextMeasurementMode.PIXEL_PERFECT
|
|
108
|
+
wasmPath: './lotio.wasm'
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Event handlers (fluent interface)
|
|
112
|
+
animation
|
|
113
|
+
.on('error', (error, anim) => {
|
|
114
|
+
console.error('Animation error:', error);
|
|
115
|
+
})
|
|
116
|
+
.on('loaded', (anim) => {
|
|
117
|
+
console.log('Animation loaded');
|
|
118
|
+
anim.start();
|
|
119
|
+
})
|
|
120
|
+
.on('start', (anim) => {
|
|
121
|
+
console.log('Animation started');
|
|
122
|
+
})
|
|
123
|
+
.on('pause', (anim) => {
|
|
124
|
+
console.log('Animation paused');
|
|
125
|
+
})
|
|
126
|
+
.on('stop', (anim) => {
|
|
127
|
+
console.log('Animation stopped');
|
|
128
|
+
})
|
|
129
|
+
.on('end', (anim) => {
|
|
130
|
+
console.log('Animation ended');
|
|
131
|
+
})
|
|
132
|
+
.on('frame', (frameNumber, time, anim) => {
|
|
133
|
+
// Render to canvas
|
|
134
|
+
const canvas = document.getElementById('canvas');
|
|
135
|
+
anim.renderToCanvas(canvas, '#2a2a2a');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// Control methods
|
|
139
|
+
animation
|
|
140
|
+
.setFps(60) // Change FPS
|
|
141
|
+
.seek(10) // Seek to frame 10
|
|
142
|
+
.start() // Start playback
|
|
143
|
+
.pause() // Pause
|
|
144
|
+
.stop(); // Stop and reset
|
|
145
|
+
|
|
146
|
+
// Getters
|
|
147
|
+
const fps = animation.getFps();
|
|
148
|
+
const state = animation.getState(); // 'stopped' | 'paused' | 'loaded' | 'error' | 'playing'
|
|
149
|
+
const frame = animation.getCurrentFrame();
|
|
150
|
+
const info = animation.getAnimationInfo();
|
|
151
|
+
|
|
152
|
+
// Render current frame to canvas
|
|
153
|
+
const canvas = document.getElementById('canvas');
|
|
154
|
+
animation.renderToCanvas(canvas, '#ffffff');
|
|
155
|
+
|
|
156
|
+
// Cleanup
|
|
157
|
+
animation.destroy();
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Full Example with Canvas:**
|
|
161
|
+
|
|
162
|
+
```html
|
|
163
|
+
<!DOCTYPE html>
|
|
164
|
+
<html>
|
|
165
|
+
<head>
|
|
166
|
+
<title>Lotio Animation</title>
|
|
167
|
+
</head>
|
|
168
|
+
<body>
|
|
169
|
+
<canvas id="canvas"></canvas>
|
|
170
|
+
<button id="playBtn">Play</button>
|
|
171
|
+
<button id="pauseBtn">Pause</button>
|
|
172
|
+
<button id="stopBtn">Stop</button>
|
|
173
|
+
|
|
174
|
+
<script type="module">
|
|
175
|
+
import Lotio from '@matrunchyk/lotio';
|
|
176
|
+
|
|
177
|
+
let animation;
|
|
178
|
+
|
|
179
|
+
async function init() {
|
|
180
|
+
// Load font
|
|
181
|
+
const fontRes = await fetch('./fonts/OpenSans-Bold.ttf');
|
|
182
|
+
const fontData = new Uint8Array(await fontRes.arrayBuffer());
|
|
183
|
+
|
|
184
|
+
// Load animation
|
|
185
|
+
const animRes = await fetch('./animation.json');
|
|
186
|
+
const animData = await animRes.json();
|
|
187
|
+
|
|
188
|
+
// Create animation
|
|
189
|
+
animation = new Lotio({
|
|
190
|
+
fonts: [{ name: 'OpenSans-Bold', data: fontData }],
|
|
191
|
+
fps: 30,
|
|
192
|
+
animation: animData,
|
|
193
|
+
wasmPath: './lotio.wasm'
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
const canvas = document.getElementById('canvas');
|
|
197
|
+
|
|
198
|
+
// Render frames
|
|
199
|
+
animation.on('frame', () => {
|
|
200
|
+
animation.renderToCanvas(canvas);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Controls
|
|
204
|
+
document.getElementById('playBtn').onclick = () => animation.start();
|
|
205
|
+
document.getElementById('pauseBtn').onclick = () => animation.pause();
|
|
206
|
+
document.getElementById('stopBtn').onclick = () => animation.stop();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
init();
|
|
210
|
+
</script>
|
|
211
|
+
</body>
|
|
212
|
+
</html>
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Samples
|
|
216
|
+
|
|
217
|
+
The `samples/` directory contains example Lottie animations and configurations:
|
|
218
|
+
|
|
219
|
+
- **`samples/sample1/`** - Basic animation with layer overrides
|
|
220
|
+
- `data.json` - Lottie animation file
|
|
221
|
+
- `layer-overrides.json` - Text and image customization configuration
|
|
222
|
+
- `output/` - Rendered frames (run lotio to generate)
|
|
223
|
+
|
|
224
|
+
- **`samples/sample2/`** - Animation with external images
|
|
225
|
+
- `data.json` - Lottie animation file with image references
|
|
226
|
+
- `images/` - External image assets referenced by the animation
|
|
227
|
+
- `output/` - Rendered frames (run lotio to generate)
|
|
228
|
+
|
|
229
|
+
**Try the samples:**
|
|
230
|
+
```bash
|
|
231
|
+
# Sample 1: Basic animation with text customization
|
|
232
|
+
cd samples/sample1
|
|
233
|
+
lotio --layer-overrides layer-overrides.json data.json output/ 30
|
|
234
|
+
|
|
235
|
+
# Sample 2: Animation with external images
|
|
236
|
+
cd samples/sample2
|
|
237
|
+
lotio data.json output/ 30
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## Using as a Library
|
|
241
|
+
|
|
242
|
+
### Headers
|
|
243
|
+
|
|
244
|
+
Headers are installed at `/opt/homebrew/include/lotio/` (or `/usr/local/include/lotio/`):
|
|
245
|
+
|
|
246
|
+
```cpp
|
|
247
|
+
#include <lotio/core/animation_setup.h>
|
|
248
|
+
#include <lotio/text/text_processor.h>
|
|
249
|
+
#include <lotio/utils/logging.h>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Linking
|
|
253
|
+
|
|
254
|
+
Link with Skia libraries:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
g++ -I/opt/homebrew/include -L/opt/homebrew/lib \
|
|
258
|
+
-llotio -lskottie -lskia -lskparagraph -lsksg -lskshaper \
|
|
259
|
+
-lskunicode_icu -lskunicode_core -lskresources -ljsonreader \
|
|
260
|
+
your_app.cpp -o your_app
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Or use pkg-config (recommended):
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
g++ $(pkg-config --cflags --libs lotio) your_app.cpp -o your_app
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Using Skia Directly
|
|
270
|
+
|
|
271
|
+
The lotio package includes Skia headers and libraries, so you can use Skia features directly in your code:
|
|
272
|
+
|
|
273
|
+
```cpp
|
|
274
|
+
// Use Skia directly
|
|
275
|
+
#include <skia/core/SkCanvas.h>
|
|
276
|
+
#include <skia/core/SkSurface.h>
|
|
277
|
+
#include <skia/modules/skottie/include/Skottie.h>
|
|
278
|
+
|
|
279
|
+
// Use lotio
|
|
280
|
+
#include <lotio/core/animation_setup.h>
|
|
281
|
+
|
|
282
|
+
int main() {
|
|
283
|
+
// Use Skia API directly
|
|
284
|
+
SkImageInfo info = SkImageInfo::MakeN32(800, 600, kOpaque_SkAlphaType);
|
|
285
|
+
auto surface = SkSurfaces::Raster(info);
|
|
286
|
+
|
|
287
|
+
// Use lotio functions
|
|
288
|
+
AnimationSetupResult result = setupAndCreateAnimation("input.json", "");
|
|
289
|
+
|
|
290
|
+
return 0;
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Compile with:
|
|
295
|
+
```bash
|
|
296
|
+
g++ $(pkg-config --cflags --libs lotio) your_app.cpp -o your_app
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The pkg-config file includes all necessary include paths:
|
|
300
|
+
- `-I${includedir}` - Lotio headers
|
|
301
|
+
- `-I${includedir}/skia` - Skia core headers
|
|
302
|
+
- `-I${includedir}/skia/gen` - Skia generated headers
|
|
303
|
+
|
|
304
|
+
## CI/CD Pipeline
|
|
305
|
+
|
|
306
|
+
The project uses GitHub Actions workflows for automated building, testing, and deployment:
|
|
307
|
+
|
|
308
|
+
```mermaid
|
|
309
|
+
graph TB
|
|
310
|
+
subgraph triggers["Event Triggers"]
|
|
311
|
+
mainPush["Push to main<br/>(creates semver tag)"]
|
|
312
|
+
tagPush["Tag push v*"]
|
|
313
|
+
lotioChanges["Changes to<br/>src/** or Dockerfile.lotio<br/>or build_binary.sh"]
|
|
314
|
+
docsChanges["Changes to<br/>docs/** or examples/**"]
|
|
315
|
+
manual["Manual<br/>workflow_dispatch"]
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
subgraph lotioWorkflow["build-lotio.yml<br/>Concurrency: build-lotio<br/>Cancel in-progress: true"]
|
|
319
|
+
buildLotioImg["Build Skia & lotio<br/>push matrunchyk/lotio"]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
subgraph releaseWorkflow["release.yml<br/>Concurrency: release<br/>Cancel in-progress: true"]
|
|
323
|
+
versionTag["Generate version<br/>& create tag"]
|
|
324
|
+
buildMac["Build macOS<br/>binary & dev package"]
|
|
325
|
+
buildLinux["Build Linux<br/>binary & .deb"]
|
|
326
|
+
buildWasm["Build WASM<br/>library"]
|
|
327
|
+
buildHomebrew["Build Homebrew<br/>bottle"]
|
|
328
|
+
buildDocs["Build<br/>documentation"]
|
|
329
|
+
publishRelease["Create GitHub<br/>release"]
|
|
330
|
+
|
|
331
|
+
versionTag --> buildMac
|
|
332
|
+
versionTag --> buildLinux
|
|
333
|
+
versionTag --> buildWasm
|
|
334
|
+
versionTag --> buildHomebrew
|
|
335
|
+
buildWasm --> buildDocs
|
|
336
|
+
buildHomebrew --> buildDocs
|
|
337
|
+
buildDocs --> publishRelease
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
subgraph testWorkflow["test.yml<br/>Concurrency: test<br/>Cancel in-progress: true"]
|
|
341
|
+
testDocker["Test Docker<br/>image"]
|
|
342
|
+
testWasm["Test JS/WASM<br/>library"]
|
|
343
|
+
testHomebrew["Test Homebrew<br/>package"]
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
subgraph pagesWorkflow["pages.yml<br/>Concurrency: pages<br/>Cancel in-progress: false"]
|
|
347
|
+
buildPages["Build & deploy<br/>documentation"]
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
mainPush --> lotioWorkflow
|
|
351
|
+
mainPush --> releaseWorkflow
|
|
352
|
+
mainPush --> pagesWorkflow
|
|
353
|
+
|
|
354
|
+
tagPush --> lotioWorkflow
|
|
355
|
+
tagPush --> releaseWorkflow
|
|
356
|
+
|
|
357
|
+
lotioChanges --> lotioWorkflow
|
|
358
|
+
docsChanges --> pagesWorkflow
|
|
359
|
+
|
|
360
|
+
manual --> lotioWorkflow
|
|
361
|
+
manual --> releaseWorkflow
|
|
362
|
+
manual --> testWorkflow
|
|
363
|
+
manual --> pagesWorkflow
|
|
364
|
+
|
|
365
|
+
lotioWorkflow -->|Docker image ready| releaseWorkflow
|
|
366
|
+
releaseWorkflow -->|workflow_run<br/>after completion| testWorkflow
|
|
367
|
+
|
|
368
|
+
style lotioWorkflow fill:#e1f5ff
|
|
369
|
+
style releaseWorkflow fill:#fff4e1
|
|
370
|
+
style testWorkflow fill:#e8f5e9
|
|
371
|
+
style pagesWorkflow fill:#f3e5f5
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Workflow Descriptions
|
|
375
|
+
|
|
376
|
+
**build-lotio.yml** - Builds and publishes `matrunchyk/lotio` Docker image
|
|
377
|
+
- **Purpose**: Create lotio binary Docker image using pre-built Skia base image
|
|
378
|
+
- **Triggers**: Main branch push, tag pushes, source code changes, Dockerfile.lotio changes, build_binary.sh changes, manual dispatch
|
|
379
|
+
- **Logic**: Uses `matrunchyk/skia:latest` as base image (Skia pre-built), only compiles lotio source
|
|
380
|
+
- **Build chain**: `Dockerfile.skia` → `Dockerfile.lotio` (uses pre-built Skia)
|
|
381
|
+
- **Concurrency**: Single instance per workflow (cancels in-progress runs when new one starts)
|
|
382
|
+
- **Output**: `matrunchyk/lotio:latest` and `matrunchyk/lotio:v1.2.3` (multi-platform: arm64, amd64)
|
|
383
|
+
- **Architecture tags**: Also creates `-arm64` and `-amd64` tags for clarity
|
|
384
|
+
|
|
385
|
+
**release.yml** - Builds all release artifacts and creates GitHub release
|
|
386
|
+
- **Purpose**: Build and package all distribution formats (binaries, WASM, Homebrew, docs)
|
|
387
|
+
- **Triggers**: Push to main (creates semver tag automatically), tag pushes (v*), manual dispatch
|
|
388
|
+
- **Logic**:
|
|
389
|
+
- Generates semver version from tag or creates new tag on main push
|
|
390
|
+
- Builds Skia from scratch using `build_binary.sh` (zero bundled dependencies, fast build)
|
|
391
|
+
- Builds in parallel: macOS, Linux, WASM, Homebrew
|
|
392
|
+
- Injects version into all artifacts
|
|
393
|
+
- **Concurrency**: Single instance per workflow (cancels in-progress runs when new one starts)
|
|
394
|
+
- **Output**: macOS dev package, Linux .deb, WASM package, Homebrew bottle, GitHub release
|
|
395
|
+
|
|
396
|
+
**test.yml** - Integration tests for all built artifacts
|
|
397
|
+
- **Purpose**: Validate that all release artifacts work correctly
|
|
398
|
+
- **Triggers**: After `release.yml` completes successfully, manual dispatch
|
|
399
|
+
- **Tests**:
|
|
400
|
+
- Docker image: `--help`, `--version`, library functionality, video generation with `--debug`
|
|
401
|
+
- JS/WASM library: Load, API functions, frame rendering
|
|
402
|
+
- Homebrew package: Installation, `--help`, `--version`, basic functionality
|
|
403
|
+
- **Concurrency**: Single instance per workflow (cancels in-progress runs when new one starts)
|
|
404
|
+
|
|
405
|
+
**pages.yml** - Builds and deploys documentation to GitHub Pages
|
|
406
|
+
- **Purpose**: Generate and deploy documentation with version injection
|
|
407
|
+
- **Triggers**: Changes to docs, examples, or build scripts; manual dispatch
|
|
408
|
+
- **Logic**: Installs lotio npm package, injects version from git tag
|
|
409
|
+
- **Concurrency**: Single instance per workflow (does not cancel in-progress runs)
|
|
410
|
+
- **Output**: Deployed to GitHub Pages
|
|
411
|
+
|
|
412
|
+
## Project Structure
|
|
413
|
+
|
|
414
|
+
```
|
|
415
|
+
src/
|
|
416
|
+
├── core/ # Core functionality (argument parsing, animation setup, rendering)
|
|
417
|
+
├── text/ # Text processing (configuration, font handling, sizing)
|
|
418
|
+
└── utils/ # Utilities (logging, string utils, crash handling)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## IDE Setup
|
|
422
|
+
|
|
423
|
+
The project includes IDE configuration for Cursor/VS Code:
|
|
424
|
+
- `.vscode/c_cpp_properties.json` - C/C++ extension settings
|
|
425
|
+
- `.clangd` - clangd language server settings
|
|
426
|
+
|
|
427
|
+
Reload Cursor/VS Code after cloning: `Cmd+Shift+P` → "Reload Window"
|
|
428
|
+
|
|
429
|
+
## Troubleshooting
|
|
430
|
+
|
|
431
|
+
**Skia build fails:**
|
|
432
|
+
- Ensure all dependencies are installed
|
|
433
|
+
- Check sufficient disk space (Skia build is large)
|
|
434
|
+
- Review error messages in `scripts/build_binary.sh` output
|
|
435
|
+
|
|
436
|
+
**Linker errors:**
|
|
437
|
+
- Verify Skia libraries exist in `third_party/skia/skia/out/Release/`
|
|
438
|
+
- Check library paths in build script
|
|
439
|
+
|
|
440
|
+
**IDE include errors:**
|
|
441
|
+
- Reload Cursor/VS Code
|
|
442
|
+
- Verify `.vscode/c_cpp_properties.json` has correct paths
|
|
443
|
+
|
|
444
|
+
## License
|
|
445
|
+
|
|
446
|
+
See individual component licenses:
|
|
447
|
+
- Skia: `third_party/skia/skia/LICENSE`
|