From bf48b7bd00c5ff1ceae78442cb3c9f514e95ea5a Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Thu, 28 May 2026 22:12:10 +0300 Subject: [PATCH 1/2] Add Lottie/Bodymovin support to the build-time SVG transcoder Lottie / Bodymovin JSON (.json, .lottie) files flow through the same build-time pipeline as SVG: each source file is lowered into the existing SVGDocument model, the SVG JavaCodeGenerator emits the GeneratedSVGImage subclass, and the same SVGRegistry registers it under its source filename. No new Image base class, no new registry, no per-port wiring -- the SVG path's JavaSE reflective load and the iOS / Android Stub weaving already cover the new format. New maven/lottie-transcoder/ module parses Bodymovin JSON with no external deps (built-in JSON parser): shape layers with grouped rectangle / ellipse / bezier path primitives, solid fills and strokes, layer transforms (anchor / position / scale / rotation / opacity), and animated rotation / position / scale collapsed to a 2-keyframe loop. Solid-color layers (ty:1) lower to a filled rect. TranscodeSVGMojo now scans src/main/{svg,lottie,css}/ for .svg / .json / .lottie and dispatches each file to the right transcoder. Resources.registerGeneratedImage strips any extension (not just .svg) so getImage("home") resolves regardless of source format. Hellocodenameone picks up sample lottie_spinner.json and lottie_pulse.json (proper Bodymovin exports -- open and play in lottieeditor.app) plus LottieAnimatedScreenshotTest registered next to the SVG screenshot tests, with the same HTML5 skip list. docs/developer-guide/SVG-Transcoder.asciidoc gains a Lottie section with the feature matrix and troubleshooting. The initializr skill's references/css.md picks up the same material so generated projects' Claude Code skill stays accurate. Lottie test coverage (17 cases): - animated translate / scale / rotation - bezier path "sh" shape with closed cycle - multi-shape fill propagation within a single gr group - stroke color + width extraction from "st" - multi-layer paint order (Lottie back-to-front -> SVG front-to-back) - solid-color layer (ty:1) lowered to a filled rect - unsupported layer type producing an empty group - opacity below 100% baked into the layer style as a 0..1 fraction - RGBA 0..1 -> ARGB int normalization with edge values - ellipse codegen path through gr/tr wrapping - missing top-level dimensions fall back to 100x100 - static-only layers don't emit any animation entries - realistic Bodymovin export (3D vectors, ix indices, gr/tr wrapping) - JSON parser primitives - transcodesToCompilableJava round-trip Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/com/codename1/ui/util/Resources.java | 11 +- docs/developer-guide/SVG-Transcoder.asciidoc | 104 ++- maven/codenameone-maven-plugin/pom.xml | 5 + .../com/codename1/maven/TranscodeSVGMojo.java | 87 ++- maven/lottie-transcoder/pom.xml | 55 ++ .../lottie/transcoder/LottieTranscoder.java | 57 ++ .../lottie/transcoder/package-info.java | 9 + .../lottie/transcoder/parser/JsonParser.java | 221 ++++++ .../transcoder/parser/LottieParser.java | 655 ++++++++++++++++++ .../codename1/lottie/LottieParserTest.java | 485 +++++++++++++ maven/pom.xml | 6 + .../common/src/main/css/lottie_pulse.json | 65 ++ .../common/src/main/css/lottie_spinner.json | 64 ++ .../common/src/main/css/theme.css | 19 + .../tests/Cn1ssDeviceRunner.java | 4 + .../tests/LottieAnimatedScreenshotTest.java | 71 ++ .../common/src/main/resources/skill/SKILL.md | 2 +- .../main/resources/skill/references/css.md | 18 + 18 files changed, 1911 insertions(+), 27 deletions(-) create mode 100644 maven/lottie-transcoder/pom.xml create mode 100644 maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/LottieTranscoder.java create mode 100644 maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/package-info.java create mode 100644 maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/JsonParser.java create mode 100644 maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/LottieParser.java create mode 100644 maven/lottie-transcoder/src/test/java/com/codename1/lottie/LottieParserTest.java create mode 100644 scripts/hellocodenameone/common/src/main/css/lottie_pulse.json create mode 100644 scripts/hellocodenameone/common/src/main/css/lottie_spinner.json create mode 100644 scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/LottieAnimatedScreenshotTest.java diff --git a/CodenameOne/src/com/codename1/ui/util/Resources.java b/CodenameOne/src/com/codename1/ui/util/Resources.java index 19dba4f877..f3715ad2a4 100644 --- a/CodenameOne/src/com/codename1/ui/util/Resources.java +++ b/CodenameOne/src/com/codename1/ui/util/Resources.java @@ -930,8 +930,15 @@ public static void registerGeneratedImage(String id, Image image) { } synchronized (generatedImages) { generatedImages.put(id, image); - if (id.endsWith(".svg")) { - generatedImages.put(id.substring(0, id.length() - 4), image); + // Register the bare stem too so getImage("home") works whether + // the source asset was home.svg, home.json (Lottie), or + // home.lottie. Keeps the call site format-agnostic. + int dot = id.lastIndexOf('.'); + if (dot > 0) { + String stem = id.substring(0, dot); + if (!generatedImages.containsKey(stem)) { + generatedImages.put(stem, image); + } } } } diff --git a/docs/developer-guide/SVG-Transcoder.asciidoc b/docs/developer-guide/SVG-Transcoder.asciidoc index d919128ead..5d028d6247 100644 --- a/docs/developer-guide/SVG-Transcoder.asciidoc +++ b/docs/developer-guide/SVG-Transcoder.asciidoc @@ -1,10 +1,19 @@ -= Build-Time SVG Images += Build-Time Vector & Animation Images :source-highlighter: highlight.js -The build-time SVG transcoder lets you author UI icons and -illustrations as SVG and have them rendered as native Codename One -`Image` instances on every platform (iOS, Android, JavaSE simulator, -JavaScript) without shipping a runtime SVG parser. +The build-time vector transcoder lets you author UI icons and +illustrations as SVG or Lottie / Bodymovin JSON and have them rendered +as native Codename One `Image` instances on every platform (iOS, +Android, JavaSE simulator, JavaScript) without shipping a runtime SVG +or Lottie parser. + +Both formats share the same pipeline: each source file is lowered into +the SVG transcoder's model, the same `JavaCodeGenerator` emits a +`com.codename1.ui.GeneratedSVGImage` subclass, and the same +`SVGRegistry` makes the result available via +`Resources.getImage("name.")`. The rest of this guide therefore +covers SVG in detail; Lottie gets its own section at the end that only +calls out the parts that differ. == Motivation @@ -202,15 +211,90 @@ with `Timeline`, you must register the image with a `Form`'s animation manager (or set it as a `Component.setIcon` with `isAnimation()` true) for the repaint loop to tick the SMIL clock. +== Lottie animations + +Lottie / Bodymovin JSON files are picked up by the same `transcode-svg` +goal. Drop them next to your CSS (or under `src/main/lottie/`) and the +Lottie parser lowers each one into the SVG model -- so everything in +the previous sections (sizing keys, registry, theme `url(...)` lookup, +`Resources.getImage(...)`, the per-port wiring) applies unchanged: + +[source] +---- +src/main/css/ + theme.css + spinner.json <-- Lottie/Bodymovin export +---- + +[source,css] +---- +SpinnerStyle { + background: url(spinner.json); + cn1-svg-width: 12mm; + cn1-svg-height: 12mm; + bg-type: image_scaled_fit; +} +---- + +[source,java] +---- +Image spin = Resources.getGlobalResources().getImage("spinner.json"); +// or by stem, like a multi-image: +Image spin2 = Resources.getGlobalResources().getImage("spinner"); +---- + +`.lottie` (dotLottie ZIP) files are accepted alongside `.json` for +forward compatibility, but the archive container isn't yet extracted +-- export your animation as a plain Bodymovin JSON for now. + +=== Lottie feature coverage + +The parser targets the subset of Bodymovin a "spinner" or "pulse" +animation typically uses. Anything outside the subset is dropped +without warning so a file with mixed coverage still produces a renderable +class: + +|=== +| Feature | Status + +| Shape layers (`ty:4`) with grouped `rc` / `el` / `sh` primitives | Full +| Solid color layers (`ty:1`) | Rendered as a filled rect +| Shape fills (`fl`) and strokes (`st`) -- solid colors | Full +| Layer transform (anchor, position, scale, rotation, opacity) -- static | Full +| Animated rotation / position / scale -- 2 keyframes | Full (loops indefinitely over the comp duration) +| Animated colors / opacity | Collapsed to the first keyframe +| Bezier easing on keyframes | Linear interpolation (easing curves ignored) +| Multi-keyframe properties (3+) | Collapsed to first vs. last (matches the SVG codegen's `from`/`to` model) +| Trim path (`tm`), repeater (`rp`), merge (`mm`), rounded corners (`rd`) | Ignored +| Gradient fills (`gf`) / gradient strokes (`gs`) | Ignored +| Text layers, image layers, precomp, mattes, expressions | Ignored +|=== + +For animations that need higher fidelity than the subset above, export +the relevant frame as an SVG and use the SVG transcoder path directly +-- the runtime classes are identical. + == Troubleshooting `Resources.getImage("name.svg")` returns null or a 1×1 transparent PNG:: - The transcoder didn't run, or it ran with zero SVGs. Confirm the SVG - lives under `src/main/css/` (or `src/main/svg/`) and that the - `transcode-svg` goal is bound in the project POM. For arbitrary - Resources bundles loaded outside the global slot, call + The transcoder didn't run, or it ran with zero source files. Confirm + the asset lives under `src/main/css/` (or `src/main/svg/` / + `src/main/lottie/` for the dedicated dirs) and that the + `transcode-svg` goal is bound in the project POM. The same goal + handles `.svg`, `.json`, and `.lottie` -- one goal, both formats. + For arbitrary Resources bundles loaded outside the global slot, call `com.codename1.ui.util.Resources.registerGeneratedImage(name, image)` - yourself with a fresh `new com.codename1.generated.svg.YourSvg(widthMm, heightMm)`. + yourself with a fresh + `new com.codename1.generated.svg.YourAsset(widthMm, heightMm)`. + +Lottie animation looks frozen or starts halfway through:: + The Lottie parser collapses each animated property's keyframe array + to its first and last value, then loops indefinitely over the + composition's duration. Animations with three or more keyframes on + the same property therefore play as a straight first-to-last + interpolation. Re-export the comp split into two-keyframe segments + or use an SVG/SMIL export for that animation if you need exact + keyframe playback. SVG looks the wrong size:: Switch the rule to `cn1-svg-width` / `cn1-svg-height` in millimeters. diff --git a/maven/codenameone-maven-plugin/pom.xml b/maven/codenameone-maven-plugin/pom.xml index 5ecf26f9d5..76c8f3e319 100644 --- a/maven/codenameone-maven-plugin/pom.xml +++ b/maven/codenameone-maven-plugin/pom.xml @@ -77,6 +77,11 @@ codenameone-svg-transcoder ${project.version} + + ${project.groupId} + codenameone-lottie-transcoder + ${project.version} + org.apache.maven maven-plugin-api diff --git a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeSVGMojo.java b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeSVGMojo.java index f3249812db..038677a84d 100644 --- a/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeSVGMojo.java +++ b/maven/codenameone-maven-plugin/src/main/java/com/codename1/maven/TranscodeSVGMojo.java @@ -1,5 +1,6 @@ package com.codename1.maven; +import com.codename1.lottie.transcoder.LottieTranscoder; import com.codename1.svg.transcoder.SVGTranscoder; import com.codename1.svg.transcoder.SVGTranscoder.GeneratedClass; @@ -33,17 +34,25 @@ import java.util.regex.Pattern; /** - * Scans an application module for SVG files, transcodes each into a + * Scans an application module for vector animation files (SVG and + * Lottie / Bodymovin JSON), transcodes each into a * {@code com.codename1.ui.GeneratedSVGImage} subclass under * {@code target/generated-sources/svg}, and emits a registry class that * installs each transcoded image into a Resources instance (and the global * fallback) under its source filename. * *

Source layout

- * SVG files are picked up from both {@code src/main/css/} and - * {@code src/main/svg/}. Drop your SVGs next to the CSS file that references - * them; the mojo finds them either way. Theme CSS keeps the natural - * {@code background: url(spinner.svg);} reference. + * Files are picked up from format-specific directories plus the shared + * {@code src/main/css/} directory so designers can drop assets next to the + * theme CSS that references them: + *
    + *
  • {@code src/main/svg/} -- {@code *.svg}
  • + *
  • {@code src/main/lottie/} -- {@code *.json}, {@code *.lottie}
  • + *
  • {@code src/main/css/} -- either of the above
  • + *
+ * Theme CSS keeps the natural {@code background: url(spinner.svg);} + * reference for SVG, and the same {@code url(...)} syntax resolves + * {@code .json} / {@code .lottie} at runtime. * *

CSS hints

* For each {@code url(*.svg)} occurrence the mojo also looks at the rule's @@ -67,7 +76,31 @@ requiresDependencyCollection = ResolutionScope.NONE) public class TranscodeSVGMojo extends AbstractCN1Mojo { - private static final String[] DEFAULT_SVG_DIRS = { "src/main/svg", "src/main/css" }; + private static final String[] DEFAULT_SVG_DIRS = { + "src/main/svg", + "src/main/lottie", + "src/main/css" + }; + + /** Recognized vector source extensions plus the format key the file + * parses as. Order matters only when multiple extensions could match + * the same bytes -- they cannot here. */ + private enum VectorFormat { + SVG(".svg"), + LOTTIE_JSON(".json"), + LOTTIE_PACK(".lottie"); + + final String ext; + VectorFormat(String ext) { this.ext = ext; } + + static VectorFormat fromFilename(String name) { + String lower = name.toLowerCase(); + for (VectorFormat f : values()) { + if (lower.endsWith(f.ext)) return f; + } + return null; + } + } private static final String DEFAULT_PACKAGE = "com.codename1.generated.svg"; @@ -136,15 +169,22 @@ protected void executeImpl() throws MojoExecutionException, MojoFailureException packageDir.mkdirs(); for (File svg : svgs) { String resourceName = svg.getName(); + VectorFormat fmt = VectorFormat.fromFilename(resourceName); + if (fmt == null) { + // locateSvgs() already filtered to recognized extensions; + // defensive guard for future format additions. + continue; + } String className = uniqueClassName(SVGTranscoder.classNameFor(resourceName), usedClassNames); usedClassNames.add(className); File outFile = new File(packageDir, className + ".java"); if (outFile.exists() && outFile.lastModified() >= svg.lastModified()) { - getLog().debug("SVG transcoder up-to-date for " + svg.getName()); + getLog().debug("Vector transcoder up-to-date for " + svg.getName()); } else { - getLog().info("Transcoding SVG " + svg.getName() + " -> " + className + ".java"); + getLog().info("Transcoding " + fmt.name() + " " + svg.getName() + + " -> " + className + ".java"); try { - SVGTranscoder.transcode(svg, svgPackage, className, outFile); + transcodeByFormat(fmt, svg, svgPackage, className, outFile); } catch (IOException ex) { throw new MojoExecutionException("Failed to transcode " + svg, ex); } @@ -200,9 +240,12 @@ private static final class CssHint { float heightMm; } - /** Scans theme CSS files for {@code url(*.svg)} together with the - * enclosing rule's {@code cn1-source-dpi} / {@code cn1-svg-width} / - * {@code cn1-svg-height}. Returns a map of svgFilename -> CssHint. */ + /** Scans theme CSS files for {@code url(*.svg|*.json|*.lottie)} + * together with the enclosing rule's {@code cn1-source-dpi} / + * {@code cn1-svg-width} / {@code cn1-svg-height}. Returns a map of + * filename -> CssHint. The CSS hint vocabulary is the SVG transcoder's + * -- the same {@code cn1-svg-width} property sizes Lottie outputs too + * because both share the {@code GeneratedSVGImage} base. */ private Map scanCssHints() throws MojoExecutionException { Map result = new HashMap(); File cssDir = new File(project.getBasedir(), "src/main/css"); @@ -213,7 +256,7 @@ private Map scanCssHints() throws MojoExecutionException { collectCss(cssDir, cssFiles); Pattern blockPattern = Pattern.compile("\\{([^}]*)\\}", Pattern.DOTALL); Pattern svgUrlPattern = Pattern.compile( - "url\\(\\s*['\"]?\\s*([^'\")\\s]+?\\.svg)\\s*['\"]?\\s*\\)", + "url\\(\\s*['\"]?\\s*([^'\")\\s]+?\\.(?:svg|json|lottie))\\s*['\"]?\\s*\\)", Pattern.CASE_INSENSITIVE); Pattern dpiPattern = Pattern.compile( "cn1-source-dpi\\s*:\\s*([\\w-]+)\\s*;?", @@ -365,12 +408,28 @@ private static void collect(File dir, List out) { for (File f : entries) { if (f.isDirectory()) { collect(f, out); - } else if (f.getName().toLowerCase().endsWith(".svg")) { + } else if (VectorFormat.fromFilename(f.getName()) != null) { out.add(f); } } } + private static void transcodeByFormat(VectorFormat fmt, File src, + String pkg, String className, File outFile) throws IOException { + switch (fmt) { + case SVG: + SVGTranscoder.transcode(src, pkg, className, outFile); + break; + case LOTTIE_JSON: + case LOTTIE_PACK: + // .lottie (dotLottie ZIP) needs an extra archive-extract step + // we don't perform here yet -- the parser treats the bytes as + // a JSON document. Drop a plain Lottie JSON for now. + LottieTranscoder.transcode(src, pkg, className, outFile); + break; + } + } + private static void collectCss(File dir, List out) { File[] entries = dir.listFiles(); if (entries == null) { diff --git a/maven/lottie-transcoder/pom.xml b/maven/lottie-transcoder/pom.xml new file mode 100644 index 0000000000..51b4e0bbb2 --- /dev/null +++ b/maven/lottie-transcoder/pom.xml @@ -0,0 +1,55 @@ + + + + + com.codenameone + codenameone + 8.0-SNAPSHOT + + 4.0.0 + + codenameone-lottie-transcoder + 8.0-SNAPSHOT + jar + codenameone-lottie-transcoder + + Build-time tool that parses Lottie/Bodymovin JSON animations and + emits Codename One Image subclasses that render them via the + Graphics API. Mirrors the SVG transcoder pipeline. Supports a + useful subset of shape layers (rectangles, ellipses, paths) with + fill/stroke and keyframe-animated transforms. + + + + UTF-8 + 1.8 + 1.8 + + + + + + maven-compiler-plugin + + 1.8 + 1.8 + + + + + + + + com.codenameone + codenameone-svg-transcoder + ${project.version} + + + junit + junit + test + + + diff --git a/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/LottieTranscoder.java b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/LottieTranscoder.java new file mode 100644 index 0000000000..bc56e702d5 --- /dev/null +++ b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/LottieTranscoder.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2025, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + */ +package com.codename1.lottie.transcoder; + +import com.codename1.lottie.transcoder.parser.LottieParser; +import com.codename1.svg.transcoder.codegen.JavaCodeGenerator; +import com.codename1.svg.transcoder.model.SVGDocument; + +import java.io.BufferedInputStream; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +/** + * Top-level entry point: parse a Lottie {@code .json} (or {@code .lottie} + * archive container) and emit a Codename One + * {@code GeneratedSVGImage} subclass. The output is byte-identical in + * structure to what {@code SVGTranscoder.transcode} emits -- Lottie is + * just lowered into the SVG model first, so the same {@code SVGRegistry} + * and per-port wiring picks it up at runtime. + */ +public final class LottieTranscoder { + + private LottieTranscoder() { } + + public static void transcode(InputStream in, String packageName, String className, Writer out) throws IOException { + SVGDocument doc = LottieParser.parse(in); + new JavaCodeGenerator(doc, packageName, className).generate(out); + } + + public static void transcode(File file, String packageName, String className, File outFile) throws IOException { + if (outFile.getParentFile() != null) { + outFile.getParentFile().mkdirs(); + } + InputStream in = new BufferedInputStream(new FileInputStream(file)); + try { + Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "UTF-8")); + try { + transcode(in, packageName, className, w); + } finally { + w.close(); + } + } finally { + in.close(); + } + } +} diff --git a/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/package-info.java b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/package-info.java new file mode 100644 index 0000000000..ba8597563b --- /dev/null +++ b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/package-info.java @@ -0,0 +1,9 @@ +/** + * Build-time Lottie/Bodymovin JSON transcoder. Parses a Lottie animation + * and produces a Codename One {@code GeneratedSVGImage} subclass by + * lowering the Lottie document into the SVG model the existing transcoder + * already knows how to render. The runtime registry, per-port wiring and + * theme {@code url(...)} lookup are the SVG transcoder's -- nothing new + * is wired at startup. + */ +package com.codename1.lottie.transcoder; diff --git a/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/JsonParser.java b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/JsonParser.java new file mode 100644 index 0000000000..0251084c5f --- /dev/null +++ b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/JsonParser.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2025, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + */ +package com.codename1.lottie.transcoder.parser; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Minimal recursive-descent JSON parser. Returns nested {@link Map} / + * {@link List} / {@link Double} / {@link String} / {@link Boolean} / null. + * No external dependency -- keeps the transcoder consistent with the SVG + * transcoder's "no Batik / no Jackson" stance. + */ +public final class JsonParser { + + private final String text; + private int pos; + + private JsonParser(String text) { + this.text = text; + this.pos = 0; + } + + public static Object parse(InputStream in) throws IOException { + StringBuilder sb = new StringBuilder(); + Reader r = new InputStreamReader(in, "UTF-8"); + char[] buf = new char[4096]; + int n; + while ((n = r.read(buf)) > 0) { + sb.append(buf, 0, n); + } + return parse(sb.toString()); + } + + public static Object parse(String text) { + JsonParser p = new JsonParser(text); + p.skipWhite(); + Object v = p.readValue(); + p.skipWhite(); + return v; + } + + private Object readValue() { + skipWhite(); + if (pos >= text.length()) { + throw err("unexpected end of input"); + } + char c = text.charAt(pos); + if (c == '{') return readObject(); + if (c == '[') return readArray(); + if (c == '"') return readString(); + if (c == 't' || c == 'f') return readBool(); + if (c == 'n') return readNull(); + if (c == '-' || (c >= '0' && c <= '9')) return readNumber(); + throw err("unexpected character '" + c + "'"); + } + + private Map readObject() { + expect('{'); + Map out = new LinkedHashMap(); + skipWhite(); + if (peek() == '}') { pos++; return out; } + while (true) { + skipWhite(); + String key = readString(); + skipWhite(); + expect(':'); + Object v = readValue(); + out.put(key, v); + skipWhite(); + char c = peek(); + if (c == ',') { pos++; continue; } + if (c == '}') { pos++; return out; } + throw err("expected ',' or '}'"); + } + } + + private List readArray() { + expect('['); + List out = new ArrayList(); + skipWhite(); + if (peek() == ']') { pos++; return out; } + while (true) { + Object v = readValue(); + out.add(v); + skipWhite(); + char c = peek(); + if (c == ',') { pos++; continue; } + if (c == ']') { pos++; return out; } + throw err("expected ',' or ']'"); + } + } + + private String readString() { + expect('"'); + StringBuilder sb = new StringBuilder(); + while (pos < text.length()) { + char c = text.charAt(pos++); + if (c == '"') return sb.toString(); + if (c == '\\') { + if (pos >= text.length()) throw err("bad escape"); + char e = text.charAt(pos++); + switch (e) { + case '"': sb.append('"'); break; + case '\\': sb.append('\\'); break; + case '/': sb.append('/'); break; + case 'b': sb.append('\b'); break; + case 'f': sb.append('\f'); break; + case 'n': sb.append('\n'); break; + case 'r': sb.append('\r'); break; + case 't': sb.append('\t'); break; + case 'u': + if (pos + 4 > text.length()) throw err("bad \\u escape"); + sb.append((char) Integer.parseInt(text.substring(pos, pos + 4), 16)); + pos += 4; + break; + default: throw err("bad escape \\" + e); + } + } else { + sb.append(c); + } + } + throw err("unterminated string"); + } + + private Double readNumber() { + int start = pos; + if (peek() == '-') pos++; + while (pos < text.length()) { + char c = text.charAt(pos); + if ((c >= '0' && c <= '9') || c == '.' || c == 'e' || c == 'E' || c == '+' || c == '-') { + pos++; + } else break; + } + return Double.valueOf(Double.parseDouble(text.substring(start, pos))); + } + + private Boolean readBool() { + if (text.startsWith("true", pos)) { pos += 4; return Boolean.TRUE; } + if (text.startsWith("false", pos)) { pos += 5; return Boolean.FALSE; } + throw err("expected boolean"); + } + + private Object readNull() { + if (text.startsWith("null", pos)) { pos += 4; return null; } + throw err("expected null"); + } + + private void skipWhite() { + while (pos < text.length()) { + char c = text.charAt(pos); + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') { + pos++; + } else break; + } + } + + private void expect(char c) { + if (pos >= text.length() || text.charAt(pos) != c) { + throw err("expected '" + c + "'"); + } + pos++; + } + + private char peek() { + if (pos >= text.length()) throw err("unexpected end of input"); + return text.charAt(pos); + } + + private RuntimeException err(String msg) { + int line = 1; + int col = 1; + for (int i = 0; i < pos && i < text.length(); i++) { + if (text.charAt(i) == '\n') { line++; col = 1; } else col++; + } + return new IllegalArgumentException(msg + " at line " + line + " col " + col); + } + + /** Static helpers so callers do not have to cast on every property read. */ + public static Map asMap(Object o) { + if (o == null) return null; + return (Map) o; + } + + public static List asList(Object o) { + if (o == null) return null; + return (List) o; + } + + public static double asDouble(Object o, double dflt) { + if (o instanceof Number) return ((Number) o).doubleValue(); + return dflt; + } + + public static int asInt(Object o, int dflt) { + if (o instanceof Number) return ((Number) o).intValue(); + return dflt; + } + + public static String asString(Object o, String dflt) { + if (o instanceof String) return (String) o; + return dflt; + } + + public static boolean asBoolean(Object o, boolean dflt) { + if (o instanceof Boolean) return ((Boolean) o).booleanValue(); + if (o instanceof Number) return ((Number) o).intValue() != 0; + return dflt; + } +} diff --git a/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/LottieParser.java b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/LottieParser.java new file mode 100644 index 0000000000..966ee984ac --- /dev/null +++ b/maven/lottie-transcoder/src/main/java/com/codename1/lottie/transcoder/parser/LottieParser.java @@ -0,0 +1,655 @@ +/* + * Copyright (c) 2025, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + */ +package com.codename1.lottie.transcoder.parser; + +import com.codename1.svg.transcoder.model.SVGAnimation; +import com.codename1.svg.transcoder.model.SVGDocument; +import com.codename1.svg.transcoder.model.SVGEllipse; +import com.codename1.svg.transcoder.model.SVGGroup; +import com.codename1.svg.transcoder.model.SVGNode; +import com.codename1.svg.transcoder.model.SVGPath; +import com.codename1.svg.transcoder.model.SVGRect; +import com.codename1.svg.transcoder.model.SVGShape; +import com.codename1.svg.transcoder.parser.PathCommand; +import com.codename1.svg.transcoder.parser.SVGPaint; +import com.codename1.svg.transcoder.parser.SVGStyle; +import com.codename1.svg.transcoder.parser.SVGTransform; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.codename1.lottie.transcoder.parser.JsonParser.asBoolean; +import static com.codename1.lottie.transcoder.parser.JsonParser.asDouble; +import static com.codename1.lottie.transcoder.parser.JsonParser.asInt; +import static com.codename1.lottie.transcoder.parser.JsonParser.asList; +import static com.codename1.lottie.transcoder.parser.JsonParser.asMap; +import static com.codename1.lottie.transcoder.parser.JsonParser.asString; + +/** + * Reads a Lottie/Bodymovin JSON animation and produces an + * {@link SVGDocument} the existing SVG transcoder's + * {@code JavaCodeGenerator} can render. The pipeline is identical to the + * SVG one from that point on -- no new Image base class, no new registry, + * no per-port wiring. + * + *

Supported subset:

+ *
    + *
  • Shape layers (ty=4) with grouped {@code rc} / {@code el} / {@code sh} + * primitives, plus {@code fl} fills and {@code st} strokes.
  • + *
  • Solid color layers (ty=1) -- emitted as a filled rect.
  • + *
  • Layer transforms (anchor, position, scale, rotation, opacity). + * Constant values are baked into a static {@link SVGTransform}; + * animated values are emitted as one or more {@link SVGAnimation} + * entries spanning the document's duration.
  • + *
  • Multi-keyframe properties are collapsed to first-vs-last linear + * interpolation -- the SVG codegen only honors that today, and the + * resulting motion still matches simple "spinner" / "pulse" cases.
  • + *
+ * + *

Anything outside the subset (text layers, image layers, mattes, + * expressions, repeaters, bezier easing) is silently dropped. The parser + * still produces a renderable document so a partially-supported file + * does not break the build.

+ */ +public final class LottieParser { + + private LottieParser() { } + + public static SVGDocument parse(InputStream in) throws IOException { + Object root = JsonParser.parse(in); + if (!(root instanceof Map)) { + throw new IllegalArgumentException("Lottie root must be a JSON object"); + } + return parse(asMap(root)); + } + + public static SVGDocument parse(Map root) { + SVGDocument doc = new SVGDocument(); + float w = (float) asDouble(root.get("w"), 100); + float h = (float) asDouble(root.get("h"), 100); + doc.setWidth(w); + doc.setHeight(h); + doc.setViewBoxX(0f); + doc.setViewBoxY(0f); + doc.setViewBoxWidth(w); + doc.setViewBoxHeight(h); + + float frameRate = (float) asDouble(root.get("fr"), 30); + float inFrame = (float) asDouble(root.get("ip"), 0); + float outFrame = (float) asDouble(root.get("op"), 0); + long durationMs = frameRate > 0f && outFrame > inFrame + ? Math.round((outFrame - inFrame) * 1000.0 / frameRate) + : 0L; + float fpsOffset = inFrame; // subtract from raw frame values + + List layers = asList(root.get("layers")); + if (layers == null) { + return doc; + } + // Lottie paints layers in reverse: the last entry in the array is + // drawn first, the first entry on top. SVG/CN1 paints in document + // order, so iterate the layer list back-to-front. + for (int i = layers.size() - 1; i >= 0; i--) { + Map layer = asMap(layers.get(i)); + if (layer == null) continue; + SVGNode emitted = emitLayer(layer, frameRate, fpsOffset, durationMs); + if (emitted != null) { + doc.addChild(emitted); + } + } + return doc; + } + + /** Build the SVG subtree for a single Lottie layer. */ + private static SVGNode emitLayer(Map layer, float frameRate, + float fpsOffset, long durationMs) { + int type = asInt(layer.get("ty"), -1); + SVGGroup g = new SVGGroup(); + applyLayerTransform(g, asMap(layer.get("ks")), frameRate, fpsOffset, durationMs); + + switch (type) { + case 1: // solid color layer + emitSolidLayer(g, layer); + break; + case 4: // shape layer + List shapes = asList(layer.get("shapes")); + if (shapes != null) { + emitShapes(g, shapes, frameRate, fpsOffset, durationMs); + } + break; + default: + // Unsupported layer (text, image, null, precomp, etc.). + // Return an empty group so the document still compiles. + break; + } + return g; + } + + private static void emitSolidLayer(SVGGroup g, Map layer) { + SVGRect r = new SVGRect(); + r.setX(0); + r.setY(0); + r.setWidth((float) asDouble(layer.get("sw"), 0)); + r.setHeight((float) asDouble(layer.get("sh"), 0)); + int argb = parseHexColor(asString(layer.get("sc"), "#000000")); + SVGStyle st = r.getStyle(); + st.setFill(SVGPaint.ofColor(argb)); + st.setStroke(SVGPaint.NONE); + g.addChild(r); + } + + /** Walk a "shapes" array within a single Lottie shape group and append + * the produced SVG nodes to {@code parent}. Lottie's paint convention: + * list entries earlier in the array are drawn on top of later ones, + * and fill/stroke items style the primitives that follow them in the + * list. We collect primitives in document order, scan once for the + * applicable fill/stroke (last wins -- matches AE's "Fill" effect), + * apply them to every primitive, then append in reverse so paint order + * matches Lottie. Nested {@code gr} groups recurse into a sub-{@code }. */ + private static void emitShapes(SVGGroup parent, List shapes, + float frameRate, float fpsOffset, long durationMs) { + List emitted = new ArrayList(); + Integer fillArgb = null; + Float fillOpacity = null; + Integer strokeArgb = null; + Float strokeOpacity = null; + Float strokeWidth = null; + SVGTransform groupTransform = null; + for (Object o : shapes) { + Map s = asMap(o); + if (s == null) continue; + String ty = asString(s.get("ty"), ""); + switch (ty) { + case "rc": { + SVGRect r = emitRect(s); + if (r != null) emitted.add(r); + break; + } + case "el": { + SVGEllipse e = emitEllipse(s); + if (e != null) emitted.add(e); + break; + } + case "sh": { + SVGPath p = emitPath(s); + if (p != null) emitted.add(p); + break; + } + case "fl": { + fillArgb = Integer.valueOf(extractColor(s)); + fillOpacity = extractScalar0to1(s.get("o")); + break; + } + case "st": { + strokeArgb = Integer.valueOf(extractColor(s)); + strokeOpacity = extractScalar0to1(s.get("o")); + strokeWidth = Float.valueOf(extractScalar(s.get("w"), 1f)); + break; + } + case "tr": { + // Shape group transform -- baked as a static matrix on a + // wrapping . Animated shape-group transforms collapse + // to the first keyframe. + groupTransform = staticTransformFrom(s); + break; + } + case "gr": { + SVGGroup child = new SVGGroup(); + List items = asList(s.get("it")); + if (items != null) { + emitShapes(child, items, frameRate, fpsOffset, durationMs); + } + emitted.add(child); + break; + } + default: + // "tm" (trim path), "rp" (repeater), "gf"/"gs" (gradient + // fill/stroke), "mm" (merge), "rd" (rounded corners), + // expressions -- silently ignored. + break; + } + } + + SVGGroup target = parent; + if (groupTransform != null && !groupTransform.isIdentity()) { + target = new SVGGroup(); + target.setTransform(groupTransform); + parent.addChild(target); + } + + // Lottie paints later-list-entries first, so reverse before appending + // so the first item ends up on top. + for (int i = emitted.size() - 1; i >= 0; i--) { + SVGNode n = emitted.get(i); + if (n instanceof SVGShape) { + SVGShape s = (SVGShape) n; + SVGStyle st = s.getStyle(); + if (fillArgb != null) { + int argb = applyOpacity(fillArgb.intValue(), + fillOpacity == null ? 1f : fillOpacity.floatValue()); + st.setFill(SVGPaint.ofColor(argb)); + } else { + st.setFill(SVGPaint.NONE); + } + if (strokeArgb != null) { + int argb = applyOpacity(strokeArgb.intValue(), + strokeOpacity == null ? 1f : strokeOpacity.floatValue()); + st.setStroke(SVGPaint.ofColor(argb)); + if (strokeWidth != null) { + st.setStrokeWidth(strokeWidth); + } + } + } + target.addChild(n); + } + } + + private static SVGRect emitRect(Map s) { + float[] pos = extractVector2(s.get("p"), new float[]{0f, 0f}); + float[] size = extractVector2(s.get("s"), new float[]{0f, 0f}); + float r = extractScalar(s.get("r"), 0f); + if (size[0] <= 0f || size[1] <= 0f) return null; + SVGRect rect = new SVGRect(); + rect.setX(pos[0] - size[0] / 2f); + rect.setY(pos[1] - size[1] / 2f); + rect.setWidth(size[0]); + rect.setHeight(size[1]); + if (r > 0f) { rect.setRx(r); rect.setRy(r); } + return rect; + } + + private static SVGEllipse emitEllipse(Map s) { + float[] pos = extractVector2(s.get("p"), new float[]{0f, 0f}); + float[] size = extractVector2(s.get("s"), new float[]{0f, 0f}); + if (size[0] <= 0f || size[1] <= 0f) return null; + SVGEllipse e = new SVGEllipse(); + e.setCx(pos[0]); + e.setCy(pos[1]); + e.setRx(size[0] / 2f); + e.setRy(size[1] / 2f); + return e; + } + + /** Lottie shape ("sh") encodes a path as vertices + per-vertex in/out + * tangents. Convert to cubic Beziers. */ + private static SVGPath emitPath(Map s) { + Map ks = asMap(s.get("ks")); + if (ks == null) return null; + Map k = asMap(ks.get("k")); + if (k == null) { + // Animated shape -- take the first keyframe's "s" value. + List kfs = asList(ks.get("k")); + if (kfs == null || kfs.isEmpty()) return null; + Map first = asMap(kfs.get(0)); + if (first == null) return null; + List sList = asList(first.get("s")); + if (sList == null || sList.isEmpty()) return null; + k = asMap(sList.get(0)); + if (k == null) return null; + } + List vertices = asList(k.get("v")); + List inTangents = asList(k.get("i")); + List outTangents = asList(k.get("o")); + boolean closed = asBoolean(k.get("c"), false); + if (vertices == null || vertices.isEmpty()) return null; + + List commands = new ArrayList(); + float[] first = pair(vertices.get(0)); + commands.add(new PathCommand(PathCommand.Type.MOVE, + new float[]{ first[0], first[1] })); + int n = vertices.size(); + for (int i = 1; i <= n; i++) { + int prev = i - 1; + int curr = i % n; + if (curr == 0 && !closed) break; + float[] p0 = pair(vertices.get(prev)); + float[] p1 = pair(vertices.get(curr)); + // Lottie tangents are *relative* to the vertex they belong to. + float[] out0 = outTangents != null && prev < outTangents.size() + ? pair(outTangents.get(prev)) : new float[]{0f, 0f}; + float[] in1 = inTangents != null && curr < inTangents.size() + ? pair(inTangents.get(curr)) : new float[]{0f, 0f}; + float c1x = p0[0] + out0[0]; + float c1y = p0[1] + out0[1]; + float c2x = p1[0] + in1[0]; + float c2y = p1[1] + in1[1]; + commands.add(new PathCommand(PathCommand.Type.CUBIC, + new float[]{ c1x, c1y, c2x, c2y, p1[0], p1[1] })); + } + if (closed) { + commands.add(new PathCommand(PathCommand.Type.CLOSE, new float[0])); + } + SVGPath path = new SVGPath(); + path.setCommands(commands); + return path; + } + + /** Apply the layer "ks" block to the group: bake static parts into a + * matrix, emit animateTransform for any animated rotation/position/ + * scale. */ + private static void applyLayerTransform(SVGGroup g, Map ks, + float frameRate, float fpsOffset, long durationMs) { + if (ks == null) return; + + // Decompose into anchor, position, scale, rotation, opacity. + Map a = asMap(ks.get("a")); + Map p = asMap(ks.get("p")); + Map s = asMap(ks.get("s")); + Map r = asMap(ks.get("r")); + Map o = asMap(ks.get("o")); + + float[] anchor = extractInitial(a, new float[]{0f, 0f}); + float[] position = extractInitial(p, new float[]{0f, 0f}); + float[] scale = extractInitial(s, new float[]{100f, 100f}); + float rotation = extractInitial(r, new float[]{0f})[0]; + float opacity = extractInitial(o, new float[]{100f})[0]; + + // Bake the constant transform first so the painter sees the correct + // resting pose for non-animated values. + SVGTransform mt = SVGTransform.identity() + .multiply(SVGTransform.translate(position[0], position[1])) + .multiply(SVGTransform.rotate(rotation, 0, 0)) + .multiply(SVGTransform.scale(scale[0] / 100f, scale[1] / 100f)) + .multiply(SVGTransform.translate(-anchor[0], -anchor[1])); + if (!mt.isIdentity()) { + g.setTransform(mt); + } + if (opacity != 100f) { + g.getStyle().setOpacity(Float.valueOf(opacity / 100f)); + } + + // Animated rotation -- most common Lottie animation. The SVG codegen + // already pre-applies the static transform, so we emit additional + // animateTransform deltas relative to the resting pose. + if (durationMs > 0L) { + emitAnimatedRotation(g, r, durationMs, rotation); + emitAnimatedTranslate(g, p, durationMs, position); + emitAnimatedScale(g, s, durationMs, scale); + } + } + + private static void emitAnimatedRotation(SVGGroup g, Map r, + long durationMs, float restingDeg) { + if (r == null) return; + if (asInt(r.get("a"), 0) != 1) return; + List keyframes = asList(r.get("k")); + if (keyframes == null || keyframes.size() < 2) return; + float[] startEnd = firstAndLastScalar(keyframes); + if (startEnd == null) return; + SVGAnimation an = new SVGAnimation(); + an.setKind(SVGAnimation.Kind.ANIMATE_TRANSFORM); + an.setTransformType(SVGAnimation.TransformType.ROTATE); + an.setBeginMs(0L); + an.setDurMs(durationMs); + an.setRepeatCount(SVGAnimation.REPEAT_INDEFINITE); + an.setFrom(formatRotateValue(startEnd[0] - restingDeg)); + an.setTo(formatRotateValue(startEnd[1] - restingDeg)); + g.addAnimation(an); + } + + private static void emitAnimatedTranslate(SVGGroup g, Map p, + long durationMs, float[] restingXY) { + if (p == null) return; + if (asInt(p.get("a"), 0) != 1) return; + List keyframes = asList(p.get("k")); + if (keyframes == null || keyframes.size() < 2) return; + float[][] startEnd = firstAndLastVector(keyframes); + if (startEnd == null) return; + SVGAnimation an = new SVGAnimation(); + an.setKind(SVGAnimation.Kind.ANIMATE_TRANSFORM); + an.setTransformType(SVGAnimation.TransformType.TRANSLATE); + an.setBeginMs(0L); + an.setDurMs(durationMs); + an.setRepeatCount(SVGAnimation.REPEAT_INDEFINITE); + an.setFrom((startEnd[0][0] - restingXY[0]) + " " + (startEnd[0][1] - restingXY[1])); + an.setTo((startEnd[1][0] - restingXY[0]) + " " + (startEnd[1][1] - restingXY[1])); + g.addAnimation(an); + } + + private static void emitAnimatedScale(SVGGroup g, Map s, + long durationMs, float[] restingScale) { + if (s == null) return; + if (asInt(s.get("a"), 0) != 1) return; + List keyframes = asList(s.get("k")); + if (keyframes == null || keyframes.size() < 2) return; + float[][] startEnd = firstAndLastVector(keyframes); + if (startEnd == null) return; + SVGAnimation an = new SVGAnimation(); + an.setKind(SVGAnimation.Kind.ANIMATE_TRANSFORM); + an.setTransformType(SVGAnimation.TransformType.SCALE); + an.setBeginMs(0L); + an.setDurMs(durationMs); + an.setRepeatCount(SVGAnimation.REPEAT_INDEFINITE); + // Lottie scale is in percent (100 = identity); convert to multiplier + // relative to the resting scale baked into the static transform. + float fx = (startEnd[0][0] / restingScale[0]); + float fy = (startEnd[0][1] / restingScale[1]); + float tx = (startEnd[1][0] / restingScale[0]); + float ty = (startEnd[1][1] / restingScale[1]); + an.setFrom(fx + " " + fy); + an.setTo(tx + " " + ty); + g.addAnimation(an); + } + + private static String formatRotateValue(float deg) { + // SVG rotate transform takes "angle [cx cy]" -- a single scalar is + // sufficient here because the static transform already moved the + // pivot to the anchor point. + return Float.toString(deg); + } + + // --------------------------------------------------------------------- + // Lottie property readers. + // --------------------------------------------------------------------- + + /** Read either a constant scalar/vector or the first keyframe's "s" + * value -- the "resting" value the static transform should use. */ + private static float[] extractInitial(Map prop, float[] fallback) { + if (prop == null) return fallback; + int animated = asInt(prop.get("a"), 0); + Object k = prop.get("k"); + if (animated == 0) { + return floatsFrom(k, fallback); + } + List keyframes = asList(k); + if (keyframes == null || keyframes.isEmpty()) { + return fallback; + } + Map first = asMap(keyframes.get(0)); + if (first == null) return fallback; + Object sv = first.get("s"); + return floatsFrom(sv, fallback); + } + + private static float[] floatsFrom(Object o, float[] fallback) { + if (o instanceof Number) { + return new float[]{ ((Number) o).floatValue() }; + } + List list = asList(o); + if (list == null) return fallback; + float[] out = new float[list.size()]; + for (int i = 0; i < list.size(); i++) { + out[i] = (float) asDouble(list.get(i), 0); + } + return out; + } + + private static float extractScalar(Object prop, float fallback) { + if (prop == null) return fallback; + Map p = asMap(prop); + if (p == null) return fallback; + Object k = p.get("k"); + if (k instanceof Number) return ((Number) k).floatValue(); + List kfs = asList(k); + if (kfs != null && !kfs.isEmpty()) { + Map first = asMap(kfs.get(0)); + if (first != null) { + Object sv = first.get("s"); + if (sv instanceof Number) return ((Number) sv).floatValue(); + List sList = asList(sv); + if (sList != null && !sList.isEmpty()) { + return (float) asDouble(sList.get(0), fallback); + } + } + } + return fallback; + } + + private static Float extractScalar0to1(Object prop) { + float v = extractScalar(prop, 100f) / 100f; + if (v < 0f) v = 0f; + if (v > 1f) v = 1f; + return Float.valueOf(v); + } + + private static float[] extractVector2(Object prop, float[] fallback) { + if (prop == null) return fallback; + Map p = asMap(prop); + if (p == null) return fallback; + Object k = p.get("k"); + List list = asList(k); + if (list != null && !list.isEmpty()) { + Object e0 = list.get(0); + if (e0 instanceof Map) { + // Animated -- take first keyframe's "s". + Map first = asMap(e0); + Object sv = first.get("s"); + List sList = asList(sv); + if (sList != null && sList.size() >= 2) { + return new float[]{ + (float) asDouble(sList.get(0), fallback[0]), + (float) asDouble(sList.get(1), fallback[1]) + }; + } + return fallback; + } + if (list.size() >= 2) { + return new float[]{ + (float) asDouble(list.get(0), fallback[0]), + (float) asDouble(list.get(1), fallback[1]) + }; + } + } + return fallback; + } + + private static int extractColor(Map s) { + Map c = asMap(s.get("c")); + if (c == null) return 0xFF000000; + Object k = c.get("k"); + List list = asList(k); + if (list == null) return 0xFF000000; + // Animated colors collapse to the first keyframe's "s". + if (!list.isEmpty() && list.get(0) instanceof Map) { + Map first = asMap(list.get(0)); + list = asList(first.get("s")); + if (list == null) return 0xFF000000; + } + double r = list.size() > 0 ? asDouble(list.get(0), 0) : 0; + double gC = list.size() > 1 ? asDouble(list.get(1), 0) : 0; + double bC = list.size() > 2 ? asDouble(list.get(2), 0) : 0; + double aC = list.size() > 3 ? asDouble(list.get(3), 1) : 1; + int ri = clampByte((int) Math.round(r * 255)); + int gi = clampByte((int) Math.round(gC * 255)); + int bi = clampByte((int) Math.round(bC * 255)); + int ai = clampByte((int) Math.round(aC * 255)); + return (ai << 24) | (ri << 16) | (gi << 8) | bi; + } + + private static int applyOpacity(int argb, float scale) { + int a = (argb >>> 24) & 0xFF; + a = clampByte(Math.round(a * scale)); + return (a << 24) | (argb & 0x00FFFFFF); + } + + private static SVGTransform staticTransformFrom(Map tr) { + float[] anchor = extractInitial(asMap(tr.get("a")), new float[]{0f, 0f}); + float[] position = extractInitial(asMap(tr.get("p")), new float[]{0f, 0f}); + float[] scale = extractInitial(asMap(tr.get("s")), new float[]{100f, 100f}); + float rotation = extractInitial(asMap(tr.get("r")), new float[]{0f})[0]; + return SVGTransform.identity() + .multiply(SVGTransform.translate(position[0], position[1])) + .multiply(SVGTransform.rotate(rotation, 0, 0)) + .multiply(SVGTransform.scale(scale[0] / 100f, scale[1] / 100f)) + .multiply(SVGTransform.translate(-anchor[0], -anchor[1])); + } + + private static float[] firstAndLastScalar(List keyframes) { + Float s = null; + Float e = null; + for (int i = 0; i < keyframes.size(); i++) { + Map kf = asMap(keyframes.get(i)); + if (kf == null) continue; + Object sv = kf.get("s"); + float v; + if (sv instanceof Number) v = ((Number) sv).floatValue(); + else { + List sList = asList(sv); + if (sList == null || sList.isEmpty()) continue; + v = (float) asDouble(sList.get(0), 0); + } + if (s == null) s = Float.valueOf(v); + e = Float.valueOf(v); + } + if (s == null || e == null) return null; + return new float[]{ s.floatValue(), e.floatValue() }; + } + + private static float[][] firstAndLastVector(List keyframes) { + float[] s = null; + float[] e = null; + for (int i = 0; i < keyframes.size(); i++) { + Map kf = asMap(keyframes.get(i)); + if (kf == null) continue; + List sList = asList(kf.get("s")); + if (sList == null || sList.size() < 2) continue; + float[] v = new float[]{ + (float) asDouble(sList.get(0), 0), + (float) asDouble(sList.get(1), 0) + }; + if (s == null) s = v; + e = v; + } + if (s == null || e == null) return null; + return new float[][]{ s, e }; + } + + private static float[] pair(Object o) { + List list = asList(o); + if (list == null || list.size() < 2) return new float[]{ 0f, 0f }; + return new float[]{ + (float) asDouble(list.get(0), 0), + (float) asDouble(list.get(1), 0) + }; + } + + private static int parseHexColor(String s) { + if (s == null) return 0xFF000000; + String t = s.trim(); + if (t.startsWith("#")) t = t.substring(1); + try { + if (t.length() == 6) { + return 0xFF000000 | Integer.parseInt(t, 16); + } + if (t.length() == 8) { + long l = Long.parseLong(t, 16); + return (int) l; + } + } catch (NumberFormatException ignored) { /* fall through */ } + return 0xFF000000; + } + + private static int clampByte(int v) { + if (v < 0) return 0; + if (v > 255) return 255; + return v; + } +} diff --git a/maven/lottie-transcoder/src/test/java/com/codename1/lottie/LottieParserTest.java b/maven/lottie-transcoder/src/test/java/com/codename1/lottie/LottieParserTest.java new file mode 100644 index 0000000000..b68c0b8d7a --- /dev/null +++ b/maven/lottie-transcoder/src/test/java/com/codename1/lottie/LottieParserTest.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2025, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + */ +package com.codename1.lottie; + +import com.codename1.lottie.transcoder.LottieTranscoder; +import com.codename1.lottie.transcoder.parser.LottieParser; +import com.codename1.svg.transcoder.model.SVGAnimation; +import com.codename1.svg.transcoder.model.SVGDocument; +import com.codename1.svg.transcoder.model.SVGGroup; +import com.codename1.svg.transcoder.model.SVGNode; +import com.codename1.svg.transcoder.model.SVGRect; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class LottieParserTest { + + private static final String SIMPLE_RECT = "{\n" + + " \"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":30,\"w\":100,\"h\":100,\n" + + " \"layers\":[{\n" + + " \"ty\":4,\"nm\":\"sq\",\"ip\":0,\"op\":30,\n" + + " \"ks\":{\n" + + " \"a\":{\"a\":0,\"k\":[0,0]},\n" + + " \"p\":{\"a\":0,\"k\":[50,50]},\n" + + " \"s\":{\"a\":0,\"k\":[100,100]},\n" + + " \"r\":{\"a\":0,\"k\":0},\n" + + " \"o\":{\"a\":0,\"k\":100}\n" + + " },\n" + + " \"shapes\":[\n" + + " {\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[40,40]},\"r\":{\"a\":0,\"k\":0}},\n" + + " {\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,0,0,1]},\"o\":{\"a\":0,\"k\":100}}\n" + + " ]\n" + + " }]\n" + + "}\n"; + + private static final String SPINNING_RECT = "{\n" + + " \"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":30,\"w\":100,\"h\":100,\n" + + " \"layers\":[{\n" + + " \"ty\":4,\"nm\":\"sq\",\"ip\":0,\"op\":30,\n" + + " \"ks\":{\n" + + " \"a\":{\"a\":0,\"k\":[0,0]},\n" + + " \"p\":{\"a\":0,\"k\":[50,50]},\n" + + " \"s\":{\"a\":0,\"k\":[100,100]},\n" + + " \"r\":{\"a\":1,\"k\":[\n" + + " {\"t\":0,\"s\":[0]},{\"t\":30,\"s\":[360]}\n" + + " ]},\n" + + " \"o\":{\"a\":0,\"k\":100}\n" + + " },\n" + + " \"shapes\":[\n" + + " {\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[30,30]},\"r\":{\"a\":0,\"k\":4}},\n" + + " {\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0,0.5,1,1]},\"o\":{\"a\":0,\"k\":100}}\n" + + " ]\n" + + " }]\n" + + "}\n"; + + @Test + public void parsesStaticRectIntoSvgDocument() throws Exception { + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream( + SIMPLE_RECT.getBytes(StandardCharsets.UTF_8))); + assertNotNull(doc); + assertEquals(100f, doc.getWidth(), 0.001f); + assertEquals(100f, doc.getHeight(), 0.001f); + // One layer -> one group -> one rect + assertEquals(1, doc.getChildren().size()); + SVGGroup g = (SVGGroup) doc.getChildren().get(0); + assertEquals(1, g.getChildren().size()); + SVGNode rectNode = g.getChildren().get(0); + assertTrue(rectNode instanceof SVGRect); + SVGRect r = (SVGRect) rectNode; + assertEquals(40f, r.getWidth(), 0.001f); + assertNotNull(r.getStyle().getFill()); + // Layer has anim list -- empty for a static layer. + assertTrue(g.getAnimations().isEmpty()); + } + + @Test + public void emitsRotationAnimationForSpinner() throws Exception { + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream( + SPINNING_RECT.getBytes(StandardCharsets.UTF_8))); + SVGGroup g = (SVGGroup) doc.getChildren().get(0); + // One animateTransform expected. + assertEquals(1, g.getAnimations().size()); + SVGAnimation an = g.getAnimations().get(0); + assertEquals(SVGAnimation.Kind.ANIMATE_TRANSFORM, an.getKind()); + assertEquals(SVGAnimation.TransformType.ROTATE, an.getTransformType()); + assertEquals(1000L, an.getDurMs()); + assertEquals(SVGAnimation.REPEAT_INDEFINITE, an.getRepeatCount()); + } + + /** Real Bodymovin exports use 3D vectors for position / anchor / scale, + * wrap shape primitives in a {@code gr} group with a per-group {@code tr} + * transform, and decorate every property with an {@code ix} index. The + * parser must ignore all of that extra metadata and still produce the + * same renderable subtree as the minimal hand-crafted format. */ + private static final String REAL_BODYMOVIN_SPINNER = + "{\"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":30,\"w\":120,\"h\":120,\"nm\":\"spin\",\"ddd\":0,\"assets\":[],\n" + + " \"layers\":[{\"ddd\":0,\"ind\":1,\"ty\":4,\"nm\":\"sq\",\"sr\":1,\n" + + " \"ks\":{\n" + + " \"o\":{\"a\":0,\"k\":100,\"ix\":11},\n" + + " \"r\":{\"a\":1,\"k\":[{\"i\":{\"x\":[0.8],\"y\":[0.8]},\"o\":{\"x\":[0.2],\"y\":[0.2]},\"t\":0,\"s\":[0]},{\"t\":30,\"s\":[360]}],\"ix\":10},\n" + + " \"p\":{\"a\":0,\"k\":[60,60,0],\"ix\":2},\n" + + " \"a\":{\"a\":0,\"k\":[0,0,0],\"ix\":1},\n" + + " \"s\":{\"a\":0,\"k\":[100,100,100],\"ix\":6}},\n" + + " \"ao\":0,\n" + + " \"shapes\":[{\"ty\":\"gr\",\"it\":[\n" + + " {\"ty\":\"rc\",\"d\":1,\"s\":{\"a\":0,\"k\":[16,32],\"ix\":2},\"p\":{\"a\":0,\"k\":[0,-32],\"ix\":3},\"r\":{\"a\":0,\"k\":4,\"ix\":4},\"nm\":\"Rect\",\"hd\":false},\n" + + " {\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,0,0,1],\"ix\":4},\"o\":{\"a\":0,\"k\":100,\"ix\":5},\"r\":1,\"bm\":0,\"nm\":\"Fill\",\"hd\":false},\n" + + " {\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0],\"ix\":2},\"a\":{\"a\":0,\"k\":[0,0],\"ix\":1},\"s\":{\"a\":0,\"k\":[100,100],\"ix\":3},\"r\":{\"a\":0,\"k\":0,\"ix\":6},\"o\":{\"a\":0,\"k\":100,\"ix\":7},\"sk\":{\"a\":0,\"k\":0,\"ix\":4},\"sa\":{\"a\":0,\"k\":0,\"ix\":5},\"nm\":\"Transform\"}\n" + + " ],\"nm\":\"Group\",\"np\":3,\"cix\":2,\"bm\":0,\"ix\":1,\"hd\":false}],\n" + + " \"ip\":0,\"op\":30,\"st\":0,\"bm\":0}],\n" + + " \"markers\":[]}"; + + @Test + public void parsesRealBodymovinExport() throws Exception { + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream( + REAL_BODYMOVIN_SPINNER.getBytes(StandardCharsets.UTF_8))); + assertEquals(120f, doc.getWidth(), 0.001f); + // Layer group with rotation animation + SVGGroup layer = (SVGGroup) doc.getChildren().get(0); + assertEquals(1, layer.getAnimations().size()); + assertEquals(SVGAnimation.TransformType.ROTATE, + layer.getAnimations().get(0).getTransformType()); + // The rect inside the gr/tr wrapping has the fill applied even though + // the fl entry uses normalized 0..1 RGBA quadruplets and an ix index. + SVGRect rect = findFirstRect(layer); + assertNotNull("rect should be reachable through the gr/tr wrapping", rect); + assertEquals(16f, rect.getWidth(), 0.001f); + assertEquals(32f, rect.getHeight(), 0.001f); + assertNotNull(rect.getStyle().getFill()); + assertEquals(0xFFFF0000, rect.getStyle().getFill().getColor()); + } + + private static SVGRect findFirstRect(SVGNode n) { + if (n instanceof SVGRect) return (SVGRect) n; + if (n instanceof SVGGroup) { + for (SVGNode c : ((SVGGroup) n).getChildren()) { + SVGRect r = findFirstRect(c); + if (r != null) return r; + } + } + return null; + } + + @Test + public void transcodesToCompilableJava() throws Exception { + StringWriter w = new StringWriter(); + LottieTranscoder.transcode(new ByteArrayInputStream( + SPINNING_RECT.getBytes(StandardCharsets.UTF_8)), + "com.example", "Spin", w); + String src = w.toString(); + assertTrue(src.contains("package com.example;")); + assertTrue(src.contains("class Spin extends GeneratedSVGImage")); + assertTrue(src.contains("paintSVG")); + } + + // ------------------------------------------------------------------ + // Animation extraction + // ------------------------------------------------------------------ + + private static String layer(String ksBody, String shapes, int op) { + return "{\"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":" + op + + ",\"w\":100,\"h\":100,\"layers\":[{" + + "\"ty\":4,\"nm\":\"l\",\"ip\":0,\"op\":" + op + + ",\"ks\":{" + ksBody + "}," + + "\"shapes\":" + shapes + "}]}"; + } + + @Test + public void emitsTranslateAnimationForAnimatedPosition() throws Exception { + // Animated position [10,20] -> [60,80] on a layer that rests at [10,20]. + // The static transform bakes in the resting position; the + // animateTransform from/to are deltas relative to that pose, so the + // expected delta is (50, 60) on the to side and (0, 0) on the from side. + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]}," + + "\"s\":{\"a\":0,\"k\":[100,100]}," + + "\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":100}," + + "\"p\":{\"a\":1,\"k\":[" + + "{\"t\":0,\"s\":[10,20]}," + + "{\"t\":30,\"s\":[60,80]}" + + "]}", + "[{\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[10,10]},\"r\":{\"a\":0,\"k\":0}}," + + "{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,0,0,1]},\"o\":{\"a\":0,\"k\":100}}]", + 30); + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGGroup g = (SVGGroup) doc.getChildren().get(0); + SVGAnimation an = findAnimation(g, SVGAnimation.TransformType.TRANSLATE); + assertNotNull("expected an animateTransform translate", an); + assertEquals(1000L, an.getDurMs()); + assertEquals(SVGAnimation.REPEAT_INDEFINITE, an.getRepeatCount()); + assertEquals("0.0 0.0", an.getFrom()); + assertEquals("50.0 60.0", an.getTo()); + } + + @Test + public void emitsScaleAnimationForAnimatedScale() throws Exception { + // Lottie scale is percent. Start at 50% (resting), end at 150% -- + // codegen normalizes to multipliers relative to resting (1.0 -> 3.0). + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]}," + + "\"p\":{\"a\":0,\"k\":[0,0]}," + + "\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":100}," + + "\"s\":{\"a\":1,\"k\":[" + + "{\"t\":0,\"s\":[50,50]}," + + "{\"t\":30,\"s\":[150,150]}" + + "]}", + "[{\"ty\":\"el\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[20,20]}}," + + "{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0,1,0,1]},\"o\":{\"a\":0,\"k\":100}}]", + 30); + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGGroup g = (SVGGroup) doc.getChildren().get(0); + SVGAnimation an = findAnimation(g, SVGAnimation.TransformType.SCALE); + assertNotNull("expected an animateTransform scale", an); + assertEquals("1.0 1.0", an.getFrom()); + assertEquals("3.0 3.0", an.getTo()); + } + + @Test + public void noAnimationsForFullyStaticLayer() throws Exception { + // Static rect should generate zero animation entries even though + // the layer's "op" defines a non-zero duration. + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream( + SIMPLE_RECT.getBytes(StandardCharsets.UTF_8))); + SVGGroup g = (SVGGroup) doc.getChildren().get(0); + assertTrue("static layer must not emit animations", g.getAnimations().isEmpty()); + } + + // ------------------------------------------------------------------ + // Shape parsing + // ------------------------------------------------------------------ + + @Test + public void parsesBezierPathShape() throws Exception { + // 3-vertex closed path with tangent vectors. The parser converts + // each vertex pair into a cubic curve so we should see one path + // node with the expected command sequence (move + n cubics + + // close). + String shapes = "[{\"ty\":\"sh\",\"ks\":{\"k\":{" + + "\"v\":[[0,0],[10,0],[10,10]]," + + "\"i\":[[0,0],[0,0],[0,0]]," + + "\"o\":[[0,0],[0,0],[0,0]]," + + "\"c\":true}}}," + + "{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0,0,1,1]},\"o\":{\"a\":0,\"k\":100}}]"; + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]}," + + "\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":100}", + shapes, 30); + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGGroup layer = (SVGGroup) doc.getChildren().get(0); + com.codename1.svg.transcoder.model.SVGPath path = findFirst(layer, com.codename1.svg.transcoder.model.SVGPath.class); + assertNotNull("expected a path from the sh shape", path); + assertNotNull(path.getCommands()); + assertTrue("path must have at least move + 1 curve + close", + path.getCommands().size() >= 3); + assertEquals(com.codename1.svg.transcoder.parser.PathCommand.Type.MOVE, + path.getCommands().get(0).getType()); + // closed=true means last command is CLOSE + assertEquals(com.codename1.svg.transcoder.parser.PathCommand.Type.CLOSE, + path.getCommands().get(path.getCommands().size() - 1).getType()); + } + + @Test + public void multipleShapesShareOneFill() throws Exception { + // Lottie convention: a single "fl" within a shape group applies to + // every primitive in that group. The parser must propagate the + // fill to both primitives. + String shapes = "[{\"ty\":\"gr\",\"it\":[" + + "{\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[10,10]},\"r\":{\"a\":0,\"k\":0}}," + + "{\"ty\":\"el\",\"p\":{\"a\":0,\"k\":[5,5]},\"s\":{\"a\":0,\"k\":[8,8]}}," + + "{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,0.5,0,1]},\"o\":{\"a\":0,\"k\":100}}" + + "]}]"; + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]}," + + "\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":100}", + shapes, 30); + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGGroup layer = (SVGGroup) doc.getChildren().get(0); + SVGRect rect = findFirst(layer, SVGRect.class); + com.codename1.svg.transcoder.model.SVGEllipse ellipse = + findFirst(layer, com.codename1.svg.transcoder.model.SVGEllipse.class); + assertNotNull(rect); + assertNotNull(ellipse); + // Both shapes carry the same fill ARGB derived from (1, 0.5, 0, 1). + // 0.5 * 255 rounds to 128 (0x80). + int expected = 0xFFFF8000; + assertEquals(expected, rect.getStyle().getFill().getColor()); + assertEquals(expected, ellipse.getStyle().getFill().getColor()); + } + + @Test + public void extractsStrokeWidthAndColor() throws Exception { + String shapes = "[{\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[10,10]},\"r\":{\"a\":0,\"k\":0}}," + + "{\"ty\":\"st\",\"c\":{\"a\":0,\"k\":[0,1,1,1]},\"o\":{\"a\":0,\"k\":100},\"w\":{\"a\":0,\"k\":3}}]"; + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]}," + + "\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":100}", + shapes, 30); + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGRect rect = findFirst((SVGGroup) doc.getChildren().get(0), SVGRect.class); + assertNotNull(rect); + assertNotNull("stroke must be set when an st entry is present", + rect.getStyle().getStroke()); + assertEquals(0xFF00FFFF, rect.getStyle().getStroke().getColor()); + assertNotNull(rect.getStyle().getStrokeWidth()); + assertEquals(3f, rect.getStyle().getStrokeWidth().floatValue(), 0.001f); + } + + // ------------------------------------------------------------------ + // Layer handling + // ------------------------------------------------------------------ + + @Test + public void multipleLayersPaintBackToFront() throws Exception { + // Lottie array order: top layer first, bottom layer last. SVG/CN1 + // paint in document order, so the parser reverses the list. The + // bottom layer (last in JSON) must appear FIRST in the document. + String json = "{\"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":30,\"w\":100,\"h\":100,\"layers\":[" + + "{\"ty\":4,\"nm\":\"top\",\"ip\":0,\"op\":30," + + " \"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0},\"o\":{\"a\":0,\"k\":100}}," + + " \"shapes\":[{\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[20,20]},\"r\":{\"a\":0,\"k\":0}},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,0,0,1]},\"o\":{\"a\":0,\"k\":100}}]}," + + "{\"ty\":4,\"nm\":\"bot\",\"ip\":0,\"op\":30," + + " \"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0},\"o\":{\"a\":0,\"k\":100}}," + + " \"shapes\":[{\"ty\":\"el\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[40,40]}},{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0,1,0,1]},\"o\":{\"a\":0,\"k\":100}}]}" + + "]}"; + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + assertEquals(2, doc.getChildren().size()); + // First child = last layer in JSON = the ellipse (green) + SVGGroup first = (SVGGroup) doc.getChildren().get(0); + SVGGroup second = (SVGGroup) doc.getChildren().get(1); + assertNotNull(findFirst(first, com.codename1.svg.transcoder.model.SVGEllipse.class)); + assertNotNull(findFirst(second, SVGRect.class)); + } + + @Test + public void solidColorLayerEmitsRect() throws Exception { + // ty:1 (solid) with explicit sw/sh/sc should produce one filled rect. + String json = "{\"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":30,\"w\":100,\"h\":100,\"layers\":[{" + + "\"ty\":1,\"nm\":\"bg\",\"ip\":0,\"op\":30,\"sw\":80,\"sh\":60,\"sc\":\"#33aaff\"," + + "\"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0},\"o\":{\"a\":0,\"k\":100}}" + + "}]}"; + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGGroup layer = (SVGGroup) doc.getChildren().get(0); + SVGRect bg = findFirst(layer, SVGRect.class); + assertNotNull("solid layer should produce a rect", bg); + assertEquals(80f, bg.getWidth(), 0.001f); + assertEquals(60f, bg.getHeight(), 0.001f); + assertEquals(0xFF33AAFF, bg.getStyle().getFill().getColor()); + } + + @Test + public void unsupportedLayerTypeProducesEmptyGroup() throws Exception { + // Text (ty:5), image (ty:2), null (ty:3), precomp (ty:0) are not + // rendered but must not throw and must still produce a child node + // so the layer index/ordering stays stable. + String json = "{\"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":30,\"w\":100,\"h\":100,\"layers\":[{" + + "\"ty\":5,\"nm\":\"txt\",\"ip\":0,\"op\":30," + + "\"ks\":{\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0},\"o\":{\"a\":0,\"k\":100}}" + + "}]}"; + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + assertEquals(1, doc.getChildren().size()); + SVGGroup g = (SVGGroup) doc.getChildren().get(0); + assertTrue("unsupported layer must produce an empty group", + g.getChildren().isEmpty()); + } + + @Test + public void opacityBelowFullValueIsBakedIntoStyle() throws Exception { + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]}," + + "\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":40}", + "[{\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[10,10]},\"r\":{\"a\":0,\"k\":0}}," + + "{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,0,0,1]},\"o\":{\"a\":0,\"k\":100}}]", + 30); + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGGroup layer = (SVGGroup) doc.getChildren().get(0); + assertNotNull(layer.getStyle().getOpacity()); + assertEquals(0.4f, layer.getStyle().getOpacity().floatValue(), 0.001f); + } + + // ------------------------------------------------------------------ + // Color normalization + // ------------------------------------------------------------------ + + @Test + public void normalizesRgbaZeroToOneIntoArgbInt() throws Exception { + // Edge cases: 0, 0.5, 1, plus alpha quarter -- verify the + // round(value * 255) conversion. + String shapes = "[{\"ty\":\"rc\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[10,10]},\"r\":{\"a\":0,\"k\":0}}," + + "{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[0,0.5,1,0.25]},\"o\":{\"a\":0,\"k\":100}}]"; + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[0,0]}," + + "\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":100}", + shapes, 30); + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + SVGRect rect = findFirst((SVGGroup) doc.getChildren().get(0), SVGRect.class); + // alpha .25 -> 64 (0x40), R=0, G=128, B=255 -> 0x4000 80FF + assertEquals(0x400080FF, rect.getStyle().getFill().getColor()); + } + + // ------------------------------------------------------------------ + // Codegen + // ------------------------------------------------------------------ + + @Test + public void codegenForEllipseLayerProducesGeneralPathDraw() throws Exception { + // Ensure the codegen reaches an ellipse path even when the source + // is a Lottie "el" shape inside a gr/tr wrapping. + StringWriter w = new StringWriter(); + String shapes = "[{\"ty\":\"gr\",\"it\":[" + + "{\"ty\":\"el\",\"p\":{\"a\":0,\"k\":[0,0]},\"s\":{\"a\":0,\"k\":[40,40]}}," + + "{\"ty\":\"fl\",\"c\":{\"a\":0,\"k\":[1,0,0,1]},\"o\":{\"a\":0,\"k\":100}}," + + "{\"ty\":\"tr\",\"p\":{\"a\":0,\"k\":[0,0]},\"a\":{\"a\":0,\"k\":[0,0]}," + + " \"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0}," + + " \"o\":{\"a\":0,\"k\":100},\"sk\":{\"a\":0,\"k\":0},\"sa\":{\"a\":0,\"k\":0}}" + + "]}]"; + String json = layer( + "\"a\":{\"a\":0,\"k\":[0,0]},\"p\":{\"a\":0,\"k\":[60,60]}," + + "\"s\":{\"a\":0,\"k\":[100,100]},\"r\":{\"a\":0,\"k\":0}," + + "\"o\":{\"a\":0,\"k\":100}", + shapes, 60); + LottieTranscoder.transcode(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)), + "com.example", "Ell", w); + String src = w.toString(); + assertTrue(src.contains("class Ell extends GeneratedSVGImage")); + // Ellipses lower to a drawArc/fillShape on a GeneralPath -- either + // way the rendered code references one of those APIs. + assertTrue("expected fillShape or drawArc in generated paint", + src.contains("fillShape") || src.contains("drawArc")); + } + + @Test + public void parseHandlesMissingTopLevelDimensions() throws Exception { + // Bodymovin always sets w/h; absence falls back to 100x100 so the + // generated subclass still compiles even on corrupt exports. + String json = "{\"v\":\"5.7.0\",\"fr\":30,\"ip\":0,\"op\":30,\"layers\":[]}"; + SVGDocument doc = LottieParser.parse(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + assertEquals(100f, doc.getWidth(), 0.001f); + assertEquals(100f, doc.getHeight(), 0.001f); + assertEquals(0, doc.getChildren().size()); + } + + // ------------------------------------------------------------------ + // Helpers + // ------------------------------------------------------------------ + + private static SVGAnimation findAnimation(SVGGroup g, SVGAnimation.TransformType ty) { + for (SVGAnimation a : g.getAnimations()) { + if (a.getKind() == SVGAnimation.Kind.ANIMATE_TRANSFORM + && a.getTransformType() == ty) { + return a; + } + } + return null; + } + + @SuppressWarnings("unchecked") + private static T findFirst(SVGNode n, Class cls) { + if (cls.isInstance(n)) return (T) n; + if (n instanceof SVGGroup) { + for (SVGNode c : ((SVGGroup) n).getChildren()) { + T hit = findFirst(c, cls); + if (hit != null) return hit; + } + } + return null; + } +} diff --git a/maven/pom.xml b/maven/pom.xml index 228e9f32af..51fd5be6b7 100644 --- a/maven/pom.xml +++ b/maven/pom.xml @@ -62,6 +62,7 @@ factory css-compiler svg-transcoder + lottie-transcoder sqlite-jdbc javase javase-svg @@ -121,6 +122,11 @@ codenameone-svg-transcoder ${project.version} + + com.codenameone + codenameone-lottie-transcoder + ${project.version} + com.codenameone sqlite-jdbc diff --git a/scripts/hellocodenameone/common/src/main/css/lottie_pulse.json b/scripts/hellocodenameone/common/src/main/css/lottie_pulse.json new file mode 100644 index 0000000000..4bd21bd7aa --- /dev/null +++ b/scripts/hellocodenameone/common/src/main/css/lottie_pulse.json @@ -0,0 +1,65 @@ +{ + "v": "5.7.0", + "fr": 30, + "ip": 0, + "op": 60, + "w": 120, + "h": 120, + "nm": "pulse", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "dot", + "sr": 1, + "ks": { + "o": {"a": 0, "k": 100, "ix": 11}, + "r": {"a": 0, "k": 0, "ix": 10}, + "p": {"a": 0, "k": [60, 60, 0], "ix": 2}, + "a": {"a": 0, "k": [0, 0, 0], "ix": 1}, + "s": {"a": 1, "k": [ + {"i": {"x": [0.833, 0.833, 0.833], "y": [0.833, 0.833, 0.833]}, + "o": {"x": [0.167, 0.167, 0.167], "y": [0.167, 0.167, 0.167]}, + "t": 0, "s": [60, 60, 100]}, + {"t": 60, "s": [140, 140, 100]} + ], "ix": 6} + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + {"ty": "el", "d": 1, + "s": {"a": 0, "k": [40, 40], "ix": 2}, + "p": {"a": 0, "k": [0, 0], "ix": 3}, + "nm": "Ellipse Path 1", "mn": "ADBE Vector Shape - Ellipse", "hd": false}, + {"ty": "fl", + "c": {"a": 0, "k": [0.12, 0.54, 0.88, 1], "ix": 4}, + "o": {"a": 0, "k": 100, "ix": 5}, + "r": 1, "bm": 0, + "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false}, + {"ty": "tr", + "p": {"a": 0, "k": [0, 0], "ix": 2}, + "a": {"a": 0, "k": [0, 0], "ix": 1}, + "s": {"a": 0, "k": [100, 100], "ix": 3}, + "r": {"a": 0, "k": 0, "ix": 6}, + "o": {"a": 0, "k": 100, "ix": 7}, + "sk": {"a": 0, "k": 0, "ix": 4}, + "sa": {"a": 0, "k": 0, "ix": 5}, + "nm": "Transform"} + ], + "nm": "Ellipse 1", "np": 3, "cix": 2, "bm": 0, "ix": 1, + "mn": "ADBE Vector Group", "hd": false + } + ], + "ip": 0, + "op": 60, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/scripts/hellocodenameone/common/src/main/css/lottie_spinner.json b/scripts/hellocodenameone/common/src/main/css/lottie_spinner.json new file mode 100644 index 0000000000..32028c7bb2 --- /dev/null +++ b/scripts/hellocodenameone/common/src/main/css/lottie_spinner.json @@ -0,0 +1,64 @@ +{ + "v": "5.7.0", + "fr": 30, + "ip": 0, + "op": 30, + "w": 120, + "h": 120, + "nm": "spinner", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "sq", + "sr": 1, + "ks": { + "o": {"a": 0, "k": 100, "ix": 11}, + "r": {"a": 1, "k": [ + {"i": {"x": [0.833], "y": [0.833]}, "o": {"x": [0.167], "y": [0.167]}, "t": 0, "s": [0]}, + {"t": 30, "s": [360]} + ], "ix": 10}, + "p": {"a": 0, "k": [60, 60, 0], "ix": 2}, + "a": {"a": 0, "k": [0, 0, 0], "ix": 1}, + "s": {"a": 0, "k": [100, 100, 100], "ix": 6} + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + {"ty": "rc", "d": 1, + "s": {"a": 0, "k": [16, 32], "ix": 2}, + "p": {"a": 0, "k": [0, -32], "ix": 3}, + "r": {"a": 0, "k": 4, "ix": 4}, + "nm": "Rectangle Path 1", "mn": "ADBE Vector Shape - Rect", "hd": false}, + {"ty": "fl", + "c": {"a": 0, "k": [0.96, 0.26, 0.21, 1], "ix": 4}, + "o": {"a": 0, "k": 100, "ix": 5}, + "r": 1, "bm": 0, + "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false}, + {"ty": "tr", + "p": {"a": 0, "k": [0, 0], "ix": 2}, + "a": {"a": 0, "k": [0, 0], "ix": 1}, + "s": {"a": 0, "k": [100, 100], "ix": 3}, + "r": {"a": 0, "k": 0, "ix": 6}, + "o": {"a": 0, "k": 100, "ix": 7}, + "sk": {"a": 0, "k": 0, "ix": 4}, + "sa": {"a": 0, "k": 0, "ix": 5}, + "nm": "Transform"} + ], + "nm": "Rectangle 1", "np": 3, "cix": 2, "bm": 0, "ix": 1, + "mn": "ADBE Vector Group", "hd": false + } + ], + "ip": 0, + "op": 30, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/scripts/hellocodenameone/common/src/main/css/theme.css b/scripts/hellocodenameone/common/src/main/css/theme.css index 5e6fc687ca..94d443611d 100644 --- a/scripts/hellocodenameone/common/src/main/css/theme.css +++ b/scripts/hellocodenameone/common/src/main/css/theme.css @@ -292,3 +292,22 @@ SVGClippedBadgeStyle { bg-type: image_scaled_fit; padding: 2mm; } + +/* Build-time Lottie transcoder: Bodymovin JSON files are lowered into the + * same SVG model the SVG transcoder uses, so the cn1-svg-width / -height + * hints sized for SVG apply here too. */ +LottieSpinnerStyle { + background: url(lottie_spinner.json); + cn1-svg-width: 14mm; + cn1-svg-height: 14mm; + bg-type: image_scaled_fit; + padding: 2mm; +} + +LottiePulseStyle { + background: url(lottie_pulse.json); + cn1-svg-width: 14mm; + cn1-svg-height: 14mm; + bg-type: image_scaled_fit; + padding: 2mm; +} diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java index 2cd24f099d..18a0c2a2d2 100644 --- a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/Cn1ssDeviceRunner.java @@ -220,6 +220,9 @@ private static int testTimeoutMs() { // AnimationTime so the captured frame is deterministic. new SVGStaticScreenshotTest(), new SVGAnimatedScreenshotTest(), + // Build-time Lottie transcoder -- same pipeline as SVG, lowers + // the Bodymovin JSON into the SVG model and reuses SVGRegistry. + new LottieAnimatedScreenshotTest(), // Keep this as the last screenshot test; orientation changes can leak into subsequent screenshots. new OrientationLockScreenshotTest(), new InPlaceEditViewTest(), @@ -385,6 +388,7 @@ private static boolean isJsSkippedScreenshotTest(String testName) { // on top of the current suite; revisit when that budget is bumped. || "SVGStaticScreenshotTest".equals(testName) || "SVGAnimatedScreenshotTest".equals(testName) + || "LottieAnimatedScreenshotTest".equals(testName) || "MainScreenScreenshotTest".equals(testName) || "SheetScreenshotTest".equals(testName) || "StatusBarTapDiagnosticScreenshotTest".equals(testName) diff --git a/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/LottieAnimatedScreenshotTest.java b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/LottieAnimatedScreenshotTest.java new file mode 100644 index 0000000000..fe74ac033e --- /dev/null +++ b/scripts/hellocodenameone/common/src/main/java/com/codenameone/examples/hellocodenameone/tests/LottieAnimatedScreenshotTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + */ +package com.codenameone.examples.hellocodenameone.tests; + +import com.codename1.ui.Display; +import com.codename1.ui.Graphics; +import com.codename1.ui.Image; +import com.codename1.ui.util.Resources; + +/// End-to-end test for the build-time Lottie transcoder. Mirrors +/// {@link SVGAnimatedScreenshotTest}: the source asset lands in +/// `src/main/css/`, the build-time transcoder lowers it into the SVG +/// pipeline, and the auto-generated SVGRegistry replaces the CSS-emitted +/// placeholder before this test runs. The clock is pinned by +/// {@link AbstractAnimationScreenshotTest} so the captured frame is +/// deterministic. +public class LottieAnimatedScreenshotTest extends AbstractAnimationScreenshotTest { + + private static final int ANIM_DURATION_MS = 1000; + + private Image spinner; + private Image pulse; + + @Override + public boolean shouldTakeScreenshot() { + return !"HTML5".equals(Display.getInstance().getPlatformName()); + } + + @Override + public boolean runTest() throws Exception { + if ("HTML5".equals(Display.getInstance().getPlatformName())) { + done(); + return true; + } + return super.runTest(); + } + + @Override + protected int getAnimationDurationMillis() { + return ANIM_DURATION_MS; + } + + @Override + protected void prepareCapture(int frameWidth, int frameHeight) { + super.prepareCapture(frameWidth, frameHeight); + Resources res = SVGStaticScreenshotTest.resolveGlobalResources(); + spinner = res == null ? null : res.getImage("lottie_spinner.json"); + pulse = res == null ? null : res.getImage("lottie_pulse.json"); + } + + @Override + protected void renderFrame(Graphics g, int width, int height, + double progress, int frameIndex) { + g.setColor(0xFFFFFF); + g.fillRect(0, 0, width, height); + + if (spinner == null || pulse == null) { + g.setColor(0xFF0000); + g.drawString("Lottie registry not installed", 10, 20); + return; + } + + int half = width / 2; + Image scaledSpinner = spinner.scaled(half, height); + Image scaledPulse = pulse.scaled(width - half, height); + g.drawImage(scaledSpinner, 0, 0); + g.drawImage(scaledPulse, half, 0); + } +} diff --git a/scripts/initializr/common/src/main/resources/skill/SKILL.md b/scripts/initializr/common/src/main/resources/skill/SKILL.md index 9ca0667cea..9aee8d1d85 100644 --- a/scripts/initializr/common/src/main/resources/skill/SKILL.md +++ b/scripts/initializr/common/src/main/resources/skill/SKILL.md @@ -25,7 +25,7 @@ This skill teaches you how to write code for a Codename One (CN1) cross-platform - `references/java-api-subset.md` — How to inspect the supported Java API subset, IO (`Storage`, `FileSystemStorage`), networking (`ConnectionRequest`, `Rest`), concurrency, dates, SQLite. **Read this whenever the compliance check fails or when you reach for a `java.*` API.** - `references/ui-components.md` — Form, Toolbar, Container layouts (Border/Box/Flow/Grid/Layered), common components, navigation, dialogs. - `references/binding-and-validation.md` — `@Bindable` / `@Bind` annotation binding **and** annotation-driven validation (`@Required`, `@Length`, `@Regex`, `@Email`, `@Url`, `@Numeric`, `@ExistIn`, `@Validate`). Read this whenever you see one of those annotations, wire a model to a form, or need to gate a submit button on validation. -- `references/css.md` — CSS capabilities and (important) **limitations**. Selectors, supported properties, 9-patch borders, theme constants. +- `references/css.md` — CSS capabilities and (important) **limitations**. Selectors, supported properties, 9-patch borders, theme constants, and the build-time vector transcoder that compiles SVG and Lottie / Bodymovin JSON referenced via `url(...)` into `GeneratedSVGImage` subclasses. - `references/swing-comparison.md` — Mapping Swing concepts and code to Codename One. Read this when porting Swing code. - `references/html-css-cheatsheet.md` — Converting common HTML/CSS snippets to CN1 components + CSS. - `references/android-to-cn1.md` — Porting Android (XML + Kotlin/Java) screens to Codename One. diff --git a/scripts/initializr/common/src/main/resources/skill/references/css.md b/scripts/initializr/common/src/main/resources/skill/references/css.md index 3212364959..90786b6c9e 100644 --- a/scripts/initializr/common/src/main/resources/skill/references/css.md +++ b/scripts/initializr/common/src/main/resources/skill/references/css.md @@ -313,6 +313,24 @@ Image home = Resources.getGlobalResources().getImage("home.svg"); For the full feature matrix and troubleshooting, point users to `docs/developer-guide/SVG-Transcoder.asciidoc`. +### Lottie — same pipeline, same URL syntax + +The `transcode-svg` goal also picks up Lottie / Bodymovin JSON (`.json`, `.lottie`). The file is lowered into the same SVG model and registered in the same `SVGRegistry`, so the developer-facing API is identical to the SVG path: + +```css +SpinnerStyle { background: url(spinner.json); cn1-svg-width: 12mm; cn1-svg-height: 12mm; bg-type: image_scaled_fit; } +``` + +```java +Image spin = Resources.getGlobalResources().getImage("spinner.json"); +``` + +Source directories: `common/src/main/lottie/` for Lottie, or drop next to `theme.css` like SVGs. + +**Lottie coverage**: shape layers (rect / ellipse / bezier path) with solid fills and strokes, layer transform (anchor / position / scale / rotation / opacity), animated rotation / position / scale collapsed to a first-to-last linear loop over the comp duration. Color / opacity animations, bezier easing, multi-keyframe paths (3+ keys), trim-path, gradients, text layers, image layers, expressions, and `.lottie` ZIP archives are **not** rendered — the parser drops them silently so a partially-supported file still produces a renderable class. + +For the full Lottie feature matrix and troubleshooting, point users to `docs/developer-guide/SVG-Transcoder.asciidoc`. + ### Custom TTF fonts Drop a `.ttf` (or `.otf`) under `common/src/main/css/fonts/`, then reference its **font name (not file name)** in `font-family`: From d94fe81fabcb9012bba063ee5548c3198bfda862 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 29 May 2026 07:48:38 +0300 Subject: [PATCH 2/2] Add LottieAnimatedScreenshotTest goldens for iOS, iOS-Metal, and Android Captured from the first CI run that included the new test. Android and iOS-Metal both show the expected animation: red rotating spinner and blue pulsing ellipse across the six progress samples (0%, 20%, 40%, 60%, 80%, 100%) the AbstractAnimationScreenshotTest grid renders. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../LottieAnimatedScreenshotTest.png | Bin 0 -> 14660 bytes .../LottieAnimatedScreenshotTest.png | Bin 0 -> 142725 bytes .../LottieAnimatedScreenshotTest.png | Bin 0 -> 89697 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 scripts/android/screenshots/LottieAnimatedScreenshotTest.png create mode 100644 scripts/ios/screenshots-metal/LottieAnimatedScreenshotTest.png create mode 100644 scripts/ios/screenshots/LottieAnimatedScreenshotTest.png diff --git a/scripts/android/screenshots/LottieAnimatedScreenshotTest.png b/scripts/android/screenshots/LottieAnimatedScreenshotTest.png new file mode 100644 index 0000000000000000000000000000000000000000..cf74a43956e4f861b14a5bd17dbeb37c11b31e51 GIT binary patch literal 14660 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�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 literal 0 HcmV?d00001 diff --git a/scripts/ios/screenshots-metal/LottieAnimatedScreenshotTest.png b/scripts/ios/screenshots-metal/LottieAnimatedScreenshotTest.png new file mode 100644 index 0000000000000000000000000000000000000000..093bb369440767ba7dcbbc43b8177d4bc4a88175 GIT binary patch literal 142725 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 z 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 z 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

fo$?JlIlAiv{G&~2+Z;a2ef=^x&O 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 J3lBM7{y!rE%u4_O literal 0 HcmV?d00001 diff --git a/scripts/ios/screenshots/LottieAnimatedScreenshotTest.png b/scripts/ios/screenshots/LottieAnimatedScreenshotTest.png new file mode 100644 index 0000000000000000000000000000000000000000..68f26d03c296db72d61ce855ac8b039dbfc78c4c GIT binary patch literal 89697 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