From 0b7019ede06fcbfb801813c9737f50a375393b82 Mon Sep 17 00:00:00 2001 From: Brandon Wilson Date: Tue, 13 Feb 2024 13:44:38 -0500 Subject: [PATCH 1/3] Update Approvals Needed Guidelines (#17) Update contributing guide to explicitly state changes that don't meaningfully change the specification only require one approval. We should add a tag to mark and track these as well. --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 18541a5..43943d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,8 +57,8 @@ Submissions fall into one of two categories: 1. **Contributions that _do not_ change the specifications, or their interpretation** These are issues and PRs largely aimed at improving legibility, fixing editorial errors, - clearing up ambiguities, or adding examples to existing specifications. - These may be progressed and merged without much process. + clearing up ambiguities, adding examples to existing specifications, or updating the tooling that generates the documentation. + These may be progressed and merged without much process and typically only require one approval from a member of the LAC working group. 2. **Contributions that _do_ meaningfully change the specifications** These must progress through the stages of RFC, guided by a sponsor until From 086e57b4531f18297a8e522e5b8292c33fe75037 Mon Sep 17 00:00:00 2001 From: Mattia Basaglia Date: Tue, 13 Feb 2024 19:45:00 +0100 Subject: [PATCH 2/3] Update links to the new github org (#18) --- README.md | 2 +- mkdocs.yml | 6 +++--- schema/root.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 565e406..7d0af6f 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,5 @@ Participation in this group requires signing the do by creating a pull request that adds your name, email address, and Community Specification License version that you agree to be bound by. -Here is an [example of what that pull request should look like](https://github.com/lottie-animation-community/docs/pull/2). +Here is an [example of what that pull request should look like](https://github.com/lottie/lottie-spec/pull/9). diff --git a/mkdocs.yml b/mkdocs.yml index b853777..e71e970 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -6,10 +6,10 @@ theme: name: cinder custom_dir: tools/theme highlightjs: false -repo_url: https://github.com/lottie-animation-community/lottie-spec -edit_uri: https://github.com/lottie-animation-community/lottie-spec/edit/main/docs +repo_url: https://github.com/lottie/lottie-spec +edit_uri: https://github.com/lottie/lottie-spec/edit/main/docs use_directory_urls: true -site_url: https://lottie-animation-community.github.io/lottie-spec/ +site_url: https://lottie.github.io/lottie-spec/ markdown_extensions: # - md_in_html - lottie_markdown diff --git a/schema/root.json b/schema/root.json index eb21d34..f66c405 100644 --- a/schema/root.json +++ b/schema/root.json @@ -1,5 +1,5 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com/lottie-animation-community/lottie-spec/schema/", + "$id": "https://lottie.github.io/lottie-spec/specs/schema/", "$ref": "#/$defs/composition/animation" } From eeb17d3fe8699b64ec46e6671bfb8b6fb7ff4ee4 Mon Sep 17 00:00:00 2001 From: Mattia Basaglia Date: Tue, 13 Feb 2024 19:45:38 +0100 Subject: [PATCH 3/3] Initial wording for shape rendering --- docs/editing/extensions.md | 65 ++++ docs/editing/schema.md | 2 +- docs/index.md | 26 +- docs/specs/shapes.md | 192 +++++++++- docs/static/css/style.css | 16 + docs/static/img/ellipse-guide.svg | 305 +++++++++++++++ docs/static/img/rect-guide.svg | 601 ++++++++++++++++++++++++++++++ mkdocs.yml | 3 +- tools/latex_markdown.py | 51 +++ tools/lottie_markdown.py | 33 ++ tools/requirements.txt | 1 + 11 files changed, 1273 insertions(+), 22 deletions(-) create mode 100644 docs/static/img/ellipse-guide.svg create mode 100644 docs/static/img/rect-guide.svg create mode 100644 tools/latex_markdown.py diff --git a/docs/editing/extensions.md b/docs/editing/extensions.md index ce183ea..66bea23 100644 --- a/docs/editing/extensions.md +++ b/docs/editing/extensions.md @@ -20,6 +20,21 @@ Output: {schema_string:shapes/graphic-element/description} +### `link` + +Links to the relevant section in the specs. + +Example: + +``` +{link:shapes/ellipse} +``` + +Output: + +{schema_link:shapes/ellipse} + + ### `schema_link` Link to the relevant section in the formatted schema. @@ -177,3 +192,53 @@ Output: lottie.layers[0].shapes[0].it[0].r.k = data["Roundness"]; + +## Miscellaneous + +### `[RFC]` + +Links to a IETF RFC. + +Example: + +``` +[RFC9402] +``` + +Output: + +[RFC9402] + + +### BCP14 + +Automatically highlights keywords from BCP 14 ([RFC2119] [RFC8174]). + +Example: + +``` +MUST +``` + +Output: + +MUST + +### Math Input + +You can embed $\LaTeX$ math mode code. + +Example: + +``` +Normal text with inline $\LaTeX$: $\frac{1}{x}$. + +$$\sum\limits_{i=1}^n{n\choose i}t^i(1-t)^{n-1}P_i$$ +``` + +Output: + + +Normal text with inline $\LaTeX$: $\frac{1}{x}$. + +$$\sum\limits_{i=1}^n{n\choose i}t^i(1-t)^{n-1}P_i$$ diff --git a/docs/editing/schema.md b/docs/editing/schema.md index 3c00817..b05abe7 100644 --- a/docs/editing/schema.md +++ b/docs/editing/schema.md @@ -76,7 +76,7 @@ There are several Python scripts that are used in the build process, ensure the requirements listed under `/tools/requirements.txt` are installed in your Python environment. -> If you facing errors on MacOS - you might need to install `graphviz` [differently]((https://graphviz.org/download/#mac)) +The [graphviz](https://graphviz.org/download/) system package needs to be installed as well. The first step is to build the combined schema: diff --git a/docs/index.md b/docs/index.md index 8812d30..617a1c8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,16 +22,34 @@ Once the draft is complete, there will be an announcement by the Lottie Animation Community. +## Conventions + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", +"SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", +and "OPTIONAL" in this document are to be interpreted as described in +BCP 14 [RFC2119] [RFC8174] when, and only when, +they appear in all capitals, as shown here. + + +## Document Structure + +Lottie documents MUST use JSON [RFC8259] to structure their data. +The top-level object in a Lottie document MUST be an +[Animation](./specs/composition.md#animation) object. + +Implementation MAY store additional data in the JSON objects. + +A machine-readable specification of the JSON structure is available +as [JSON Schema](./specs/schema.md). + + ## Where to start -Lottie files use JSON to structure its data so basic JSON knowledge -is required to understand the specidification. +Since Lottie uses JSON, basic JSON knowledge is required to understand the specification. To understand Lottie data, it's useful to start learning about [basic values](./specs/values.md) and [animated properties](./specs/properties.md). The root object of any Lottie animation is the [Animation](./specs/composition.md#animation) object. -If you want a machine readable specification the [JSON Schema](./specs/schema.md) is also available. - diff --git a/docs/specs/shapes.md b/docs/specs/shapes.md index 964cac9..3d4e467 100644 --- a/docs/specs/shapes.md +++ b/docs/specs/shapes.md @@ -7,6 +7,55 @@ The graphical elements are divided in 4 categories: * [Styles](#shape-style), that define the visual appearance of shapes * [Modifiers](#modifier) alter the curves of the shapes +## Rendering Convention + +Shapes defined in this section contain rendering instructions. +These instructions are used to generate the path as a bezier curve. + +Implementations MAY use different algorithms or primitives to render +the shapes but the result MUST be equivalent to the paths defined here. + +Some instructions define named values for clarity and illustrative purposes, +implementations are not required to have them explicitly defined in +their rendering process. + +When referencing animated properties, the rendering instruction will +use the same name as in the JSON but it's assumed they refer to their +value at a given point in time rather than the property itself. + +For {link:values/vector} values, $value.x$ and $value.y$ in +the instructions are equivalent to `value[0]` and `value[1]` respectively. + +All paths MUST be closed unless specified otherwise in the rendering instructions. + +When instructions call for an equality comparison between two values, +implementaions MAY consider similar values to be equal to overcome numerical instability. + +### Drawing Commands + +Drawing instructions will contain the following commands: + +* _add vertex_: Adds a vertex to the bezier shape in global coordinates +* _set in tangent_: Sets the cubic tangent to the last added vertex, with coordinates relative to it. If omitted, tangents MUST be $(0, 0)$. +* _set out tangent_: Sets the cubic tangent from the last added vertex, with coordinates relative to it. If omitted, tangents MUST be $(0, 0)$. +* _lerp_: Linerarly interpolates two points or scalars by a given amount. + + +### Approximating Ellipses with Cubic Bezier + +An elliptical quadrant can be approximated by a cubic bezier segment +with tangents of length $radius \cdot E_t. + +Where + +$$E_t \approx 0.5519150244935105707435627$$ + +See [this article](https://spencermortensen.com/articles/bezier-circle/) for the math behind it. + +When implementations render elliptical arcs using bezier curves, they SHOULD +use this constant, a similar approximation, or elliptical arc drawing primitives. + +

Graphic Element

{schema_string:shapes/graphic-element/description} @@ -50,25 +99,34 @@ The `ty` property defines the specific element type based on the following value +An ellipse is drawn from the top quandrant point going clockwise: -

Path

+$$ +\begin{align*} +radius & = \frac{s}{2} \\ +tangent & = radius \cdot E_t \\ +x & = p.x \\ +y & = p.y \\ +\end{align*} +$$ -{schema_string:shapes/path/description} +1. Add vertex $(x, y - radius.y)$ +1. Set in tangent $(-tangent.x, 0)$ +1. Set out tangent $(tangent.x, 0)$ +1. Add vertex $(x + radius.x, y)$ +1. Set in tangent $(0, -tangent.y)$ +1. Set out tangent $(0, tangent.y)$ +1. Add vertex $(x, y + radius.y)$ +1. Set in tangent $(tangent.x, 0)$ +1. Set out tangent $(-tangent.x, 0)$ +1. Add vertex $(x - radius.x, y)$ +1. Set in tangent $(0, tangent.y)$ +1. Set out tangent $(0, -tangent.y)$ -{schema_object:shapes/path} +Implementations MAY use elliptical arcs to render an ellipse. + +![Ellipse rendering guide](../static/img/ellipse-guide.svg) - - Example -
- -
- lottie.layers[0].shapes[0].it[0] - -

Rectangle

@@ -97,6 +155,73 @@ The `ty` property defines the specific element type based on the following value + +Definitions: + +$$ +\begin{align*} +left & = p.x - \frac{s.x}{2} \\ +right & = p.x + \frac{s.x}{2} \\ +top & = p.y - \frac{s.y}{2} \\ +bottom & = p.y + \frac{s.y}{2} \\ +\end{align*} +$$ + +If $r = 0$, then the rectangle is rendered from the top-right going clockwise: + +1. Add vertex $(right, top)$ +1. Add vertex $(right, bottom)$ +1. Add vertex $(left, bottom)$ +1. Add vertex $(left, top)$ + +If $r > 0$, the rounded corners must be taken into account. + +$$ +\begin{align*} +rounded & = \min\left(\frac{s.x}{2}, \frac{s.y}{2}, r\right) \\ +tangent & = rounded \cdot E_t \\ +\end{align*} +$$ + +1. Add vertex $(right, top + rounded)$ +1. Set in tangent $(0, -tangent)$ +1. Add vertex $(right, bottom - rounded)$ +1. Set out tangent $(0, tangent)$ +1. Add vertex $(right - rounded, bottom)$ +1. Set in tangent $(tangent, 0)$ +1. Add vertex $(left + rounded, bottom)$ +1. Set out tangent $(-tangent, 0)$ +1. Add vertex $(left, bottom - rounded)$ +1. Set in tangent $(0, tangent)$ +1. Add vertex $(left, top + rounded)$ +1. Set out tangent $(0, -tangent)$ +1. Add vertex $(left + rounded, top)$ +1. Set in tangent $(-tangent, 0)$ +1. Add vertex $(right - rounded, top)$ +1. Set out tangent $(tangent, 0)$ + +![Rectangle rendering guide](../static/img/rect-guide.svg) + + +

Path

+ +{schema_string:shapes/path/description} + +{schema_object:shapes/path} + + + Example +
+ +
+ lottie.layers[0].shapes[0].it[0] + +
+

Grouping

Group

@@ -170,3 +295,40 @@ The `ty` property defines the specific element type based on the following value lottie.layers[0].shapes[4].m = Number(data["Multiple Shapes"]); + +When rendering trim path, the order of bezier points MUST be the same as +rendering instructions given for each shape in this section. + +Rendering trim path can be rather complex. + +Given + +$$ +\begin{align*} +offset & = +\begin{cases} +\frac{o}{360} - \lfloor \frac{o}{360} \rfloor & o \ge 0 \\ +\frac{o}{360} - \lceil \frac{o}{360} \rceil & o < 0 +\end{cases} \\ +start & = offset + \min\left(1, \max\left(0, \frac{\min(s, e)}{100}\right)\right) \\ +end & = offset + \min\left(1, \max\left(0, \frac{\max(s, e)}{100}\right)\right) \\ +\end{align*} +$$ + +If $s$ and $e$ are equal, implementations MUST NOT render any shapes. + +If $s = 0$ and $e = 1$, the input shape MUST be rendered as-is. + +To render trim path, implementations MUST consider the actual length of +each shape (they MAY use approximations). Once the shapes are collected, +the segment to render is given by the percentages $start$ and $end$. + +When trim path is applied to multiple shapes, the `m` property MUST +be considered when applying the modifier: + +* When `m` has a value of `1` (Parallel), each shape MUST considered +separately, $start$ and $end$ being applied to each shape. + +* When `m` has a value of `2` (Sequential), all the shapes MUST be considered +as following each other in render order. $start$ and $end$ refer to the whole +length created by concatenating each shape. diff --git a/docs/static/css/style.css b/docs/static/css/style.css index 1bd8272..89d6d13 100644 --- a/docs/static/css/style.css +++ b/docs/static/css/style.css @@ -105,3 +105,19 @@ canvas.bezier-editor { margin: 1ex; height: 48px; } + +/* mathml */ + +math { + margin: 1em 0; +} + +mtd[columnalign=right] { + text-align: -webkit-right; + text-align: -moz-right; +} + +mtd[columnalign=left] { + text-align: -webkit-left; + text-align: -moz-left; +} diff --git a/docs/static/img/ellipse-guide.svg b/docs/static/img/ellipse-guide.svg new file mode 100644 index 0000000..9959578 --- /dev/null +++ b/docs/static/img/ellipse-guide.svg @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + s.x + + s.y + + p + + + s.x/2 + + + s.y/2 + + + + diff --git a/docs/static/img/rect-guide.svg b/docs/static/img/rect-guide.svg new file mode 100644 index 0000000..d81c4bb --- /dev/null +++ b/docs/static/img/rect-guide.svg @@ -0,0 +1,601 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + s.x + + s.y + + r + + + + + + + + r/2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + p + + + s.x/2 + + + s.y/2 + + diff --git a/mkdocs.yml b/mkdocs.yml index e71e970..fe128f0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,9 +11,8 @@ edit_uri: https://github.com/lottie/lottie-spec/edit/main/docs use_directory_urls: true site_url: https://lottie.github.io/lottie-spec/ markdown_extensions: - # - md_in_html - lottie_markdown - # - footnotes + - latex_markdown extra_css: # - /lottie-spec/style/style.css # - /lottie-spec/style/lottie-theme.css diff --git a/tools/latex_markdown.py b/tools/latex_markdown.py new file mode 100644 index 0000000..a051e3f --- /dev/null +++ b/tools/latex_markdown.py @@ -0,0 +1,51 @@ +import latex2mathml.converter + +from markdown.extensions import Extension +from markdown.inlinepatterns import InlineProcessor +from markdown.blockprocessors import BlockProcessor + + +class InlineLatex(InlineProcessor): + def __init__(self, md): + super().__init__(r'\$([^\$\n]+)\$', md) + + def handleMatch(self, match, data): + element = latex2mathml.converter.convert_to_element(match.group(1)) + return element, match.start(0), match.end(0) + + +class BlockLatex(BlockProcessor): + fence = "$$" + + def __init__(self, md): + super().__init__(md.parser) + + def test(self, parent, block): + return block.startswith("$$") + + def run(self, parent, blocks): + code = "" + last_block = blocks.pop(0)[2:] + while True: + if last_block.endswith("$$"): + code += last_block[:-2] + break + else: + code += last_block + last_block.pop(0) + + for chunk in code.split("$$\n$$"): + element = latex2mathml.converter.convert_to_element(chunk, display="block") + parent.append(element) + + return True + + +class LatexExtension(Extension): + def extendMarkdown(self, md): + md.inlinePatterns.register(InlineLatex(md), "inline_latex", 175) + md.parser.blockprocessors.register(BlockLatex(md), "block_latex", 175) + + +def makeExtension(**kwargs): + return LatexExtension(**kwargs) diff --git a/tools/lottie_markdown.py b/tools/lottie_markdown.py index 4d9ae73..dc410b3 100644 --- a/tools/lottie_markdown.py +++ b/tools/lottie_markdown.py @@ -1072,6 +1072,37 @@ def typed_schema(schema: Schema): return ts +class BCP14(InlineProcessor): + words = [ + "SHALL NOT", "MUST NOT", "SHOULD NOT", "NOT RECOMMENDED", + "REQUIRED", "SHALL", "MUST", "SHOULD", "RECOMMENDED", "MAY", + "OPTIONAL" + ] + + def __init__(self, md): + super().__init__("|".join(self.words), md) + + def handleMatch(self, match, data): + span = etree.Element("strong") + span.attrib["class"] = "bcp14" + span.text = match.group(0) + return span, match.start(0), match.end(0) + + +class RfcLink(InlineProcessor): + def __init__(self, md): + super().__init__(r"\[(RFC([0-9]+))\]", md) + + def handleMatch(self, match, data): + span = etree.Element("span") + span.text = "[" + link = etree.SubElement(span, "a") + link.attrib["href"] = "https://www.rfc-editor.org/info/rfc%s" % match.group(2) + link.text = match.group(1) + link.tail = "]" + return span, match.start(0), match.end(0) + + class LottieExtension(Extension): def extendMarkdown(self, md): ts = typed_schema(Schema.load(docs_path / "lottie.schema.json")) @@ -1083,6 +1114,8 @@ def extendMarkdown(self, md): md.inlinePatterns.register(SubTypeTable(md, ts), "schema_subtype_table", 175) md.inlinePatterns.register(LottieColor(r'{lottie_color:(([^,]+),\s*([^,]+),\s*([^,]+))}', md, 1), 'lottie_color', 175) md.inlinePatterns.register(SchemaInheritance(md, ts), "schema_inheritance", 175) + md.inlinePatterns.register(BCP14(md), "bcp14", 175) + md.inlinePatterns.register(RfcLink(md), "rfc", 175) md.parser.blockprocessors.register( RawHTML( diff --git a/tools/requirements.txt b/tools/requirements.txt index 3e04134..be82464 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,5 +1,6 @@ mkdocs==1.5.3 mkdocs-cinder==1.2.0 graphviz==0.20.1 +latex2mathml==3.77.0 # Used for link validations lxml==4.9.3