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 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`