Shapes

The graphical elements are divided in 4 categories:

Shape Rendering Model

Grouping and Ordering Rules

More formally:

Notes

  1. Certain modifier operations (e.g. sequential Trim) may require information about shapes from different groups, thus Render() calls cannot always be issued based on single-pass local knowledge.

  2. Transforms can affect both shapes and styles (e.g. stroke width). For a given (shape,style), the shape and style transforms are not necessarily equal.

  3. Shapes without an applicable style are not rendered.

  4. This rendering model is based on AfterEffects' Shape Layer semantics.

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 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, implementations MAY consider similar values to be equal to overcome numerical instability.

Bezier Conversions

This documents includes algorithms to convert parametric shapes into bezier curves.

Implementations MAY use different implementations than the algorithms provided here but the output shape MUST be visually indistinguishable from the output of these algorithms.

Furthermore, when drawing individual shapes the stroke order and direction is not importand but implementations of Trim Path MUST follow the stroke order as defined by these algorithms.

Drawing instructions will contain the following commands:

Approximating Ellipses with Cubic Bezier

An elliptical quadrant can be approximated by a cubic bezier segment with tangents of length $radius * E_t.

Where

Et0.5519150244935105707435627

See this article 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

Element used to display vector data in a shape layer

Composition Diagram for Graphic Element Graphic Element Visual Object Shape PolyStar Ellipse Rectangle Path Transform Shape Modifier Pucker Bloat Trim Path Group Shape Style Stroke Gradient Gradient Stroke Fill
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string

Shape Type

Shape Type

The ty property defines the specific element type based on the following values:

ty Type
'el' Ellipse
'fl' Fill
'gf' Gradient
'gs' Gradient Stroke
'gr' Group
'sh' Path
'sr' PolyStar
'rc' Rectangle
'st' Stroke
'tr' Transform Shape
'tm' Trim Path
'pb' Pucker Bloat

Hidden shapes (hd: True) are ignored, and do not contribute to rendering nor modifier operations.

Shapes

Drawable shape, defines the actual shape but not the style

Composition Diagram for Shape Shape Graphic Element Visual Object PolyStar Ellipse Rectangle Path
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string

Shape Type

Shape Type

d Shape Direction

Direction

Direction the shape is drawn as, mostly relevant when using trim path

Ellipse

Ellipse shape

Composition Diagram for Ellipse Ellipse Shape Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'el'

Shape Type

Shape Type

d Shape Direction

Direction

Direction the shape is drawn as, mostly relevant when using trim path

p Position

Position

Position

s Vector

Size

Size


def ellipse(shape: Bezier, p: Vector2D, s: Vector2D):
    # An ellipse is drawn from the top quadrant point going clockwise:
    radius = s / 2
    tangent = radius * ELLIPSE_CONSTANT
    x = p.x
    y = p.y

    shape.closed = True
    shape.add_vertex(Vector2D(x, y - radius.y))
    shape.set_in_tangent(Vector2D(-tangent.x, 0))
    shape.set_out_tangent(Vector2D(tangent.x, 0))
    shape.add_vertex(Vector2D(x + radius.x, y))
    shape.set_in_tangent(Vector2D(0, -tangent.y))
    shape.set_out_tangent(Vector2D(0, tangent.y))
    shape.add_vertex(Vector2D(x, y + radius.y))
    shape.set_in_tangent(Vector2D(tangent.x, 0))
    shape.set_out_tangent(Vector2D(-tangent.x, 0))
    shape.add_vertex(Vector2D(x - radius.x, y))
    shape.set_in_tangent(Vector2D(0, tangent.y))
    shape.set_out_tangent(Vector2D(0, -tangent.y))

void ellipse(Bezier shape, Vector2D p, Vector2D s)
{
    // An ellipse is drawn from the top quadrant point going clockwise:
    radius = s / 2;
    tangent = radius * ELLIPSE_CONSTANT;
    x = p.x;
    y = p.y;

    shape.closed = true;
    shape.add_vertex(Vector2D(x, y - radius.y));
    shape.set_in_tangent(Vector2D(-tangent.x, 0));
    shape.set_out_tangent(Vector2D(tangent.x, 0));
    shape.add_vertex(Vector2D(x + radius.x, y));
    shape.set_in_tangent(Vector2D(0, -tangent.y));
    shape.set_out_tangent(Vector2D(0, tangent.y));
    shape.add_vertex(Vector2D(x, y + radius.y));
    shape.set_in_tangent(Vector2D(tangent.x, 0));
    shape.set_out_tangent(Vector2D(-tangent.x, 0));
    shape.add_vertex(Vector2D(x - radius.x, y));
    shape.set_in_tangent(Vector2D(0, tangent.y));
    shape.set_out_tangent(Vector2D(0, -tangent.y));
}

function ellipse(shape: Bezier, p: Vector2D, s: Vector2D) {
    // An ellipse is drawn from the top quadrant point going clockwise:
    radius = s / 2;
    tangent = radius * ELLIPSE_CONSTANT;
    x = p.x;
    y = p.y;

    shape.closed = true;
    shape.addVertex(new Vector2D(x, y - radius.y));
    shape.setInTangent(new Vector2D(-tangent.x, 0));
    shape.setOutTangent(new Vector2D(tangent.x, 0));
    shape.addVertex(new Vector2D(x + radius.x, y));
    shape.setInTangent(new Vector2D(0, -tangent.y));
    shape.setOutTangent(new Vector2D(0, tangent.y));
    shape.addVertex(new Vector2D(x, y + radius.y));
    shape.setInTangent(new Vector2D(tangent.x, 0));
    shape.setOutTangent(new Vector2D(-tangent.x, 0));
    shape.addVertex(new Vector2D(x - radius.x, y));
    shape.setInTangent(new Vector2D(0, tangent.y));
    shape.setOutTangent(new Vector2D(0, -tangent.y));
}

Implementations MAY use elliptical arcs to render an ellipse.

Ellipse rendering guide

Rectangle

A simple rectangle shape

Composition Diagram for Rectangle Rectangle Shape Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'rc'

Shape Type

Shape Type

d Shape Direction

Direction

Direction the shape is drawn as, mostly relevant when using trim path

p Position

Position

Center of the rectangle

s Vector

Size

Size

r Scalar

Roundness

Rounded corners radius

Rendering algorithm:


def rectangle(shape: Bezier, p: Vector2D, s: Vector2D, r: float):
    left: float = p.x - s.x / 2
    right: float = p.x + s.x / 2
    top: float = p.y - s.y / 2
    bottom: float = p.y + s.y / 2

    shape.closed = True

    if r <= 0:

        # The rectangle is rendered from the top-right going clockwise

        shape.add_vertex(Vector2D(right, top))
        shape.add_vertex(Vector2D(right, bottom))
        shape.add_vertex(Vector2D(left, bottom))
        shape.add_vertex(Vector2D(left, top))

    else:

        # Rounded corners must be taken into account

        rounded: float = min(s.x/2, s.y/2, r)
        tangent: float = rounded * ELLIPSE_CONSTANT

        shape.add_vertex(Vector2D(right, top + rounded))
        shape.set_in_tangent(Vector2D(0, -tangent))
        shape.add_vertex(Vector2D(right, bottom - rounded))
        shape.set_out_tangent(Vector2D(0, tangent))
        shape.add_vertex(Vector2D(right - rounded, bottom))
        shape.set_in_tangent(Vector2D(tangent, 0))
        shape.add_vertex(Vector2D(left + rounded, bottom))
        shape.set_out_tangent(Vector2D(-tangent, 0))
        shape.add_vertex(Vector2D(left, bottom - rounded))
        shape.set_in_tangent(Vector2D(0, tangent))
        shape.add_vertex(Vector2D(left, top + rounded))
        shape.set_out_tangent(Vector2D(0, -tangent))
        shape.add_vertex(Vector2D(left + rounded, top))
        shape.set_in_tangent(Vector2D(-tangent, 0))
        shape.add_vertex(Vector2D(right - rounded, top))
        shape.set_out_tangent(Vector2D(tangent, 0))

void rectangle(Bezier shape, Vector2D p, Vector2D s, float r)
{
    float left = p.x - s.x / 2;
    float right = p.x + s.x / 2;
    float top = p.y - s.y / 2;
    float bottom = p.y + s.y / 2;

    shape.closed = true;

    if ( r <= 0 )
    {
        // The rectangle is rendered from the top-right going clockwise

        shape.add_vertex(Vector2D(right, top));
        shape.add_vertex(Vector2D(right, bottom));
        shape.add_vertex(Vector2D(left, bottom));
        shape.add_vertex(Vector2D(left, top));
    }
    // Rounded corners must be taken into account

    else
    {
        float rounded = std::min(s.x / 2, s.y / 2, r);
        float tangent = rounded * ELLIPSE_CONSTANT;

        shape.add_vertex(Vector2D(right, top + rounded));
        shape.set_in_tangent(Vector2D(0, -tangent));
        shape.add_vertex(Vector2D(right, bottom - rounded));
        shape.set_out_tangent(Vector2D(0, tangent));
        shape.add_vertex(Vector2D(right - rounded, bottom));
        shape.set_in_tangent(Vector2D(tangent, 0));
        shape.add_vertex(Vector2D(left + rounded, bottom));
        shape.set_out_tangent(Vector2D(-tangent, 0));
        shape.add_vertex(Vector2D(left, bottom - rounded));
        shape.set_in_tangent(Vector2D(0, tangent));
        shape.add_vertex(Vector2D(left, top + rounded));
        shape.set_out_tangent(Vector2D(0, -tangent));
        shape.add_vertex(Vector2D(left + rounded, top));
        shape.set_in_tangent(Vector2D(-tangent, 0));
        shape.add_vertex(Vector2D(right - rounded, top));
        shape.set_out_tangent(Vector2D(tangent, 0));
    }
}

function rectangle(shape: Bezier, p: Vector2D, s: Vector2D, r: number) {
    let left: number = p.x - s.x / 2;
    let right: number = p.x + s.x / 2;
    let top: number = p.y - s.y / 2;
    let bottom: number = p.y + s.y / 2;

    shape.closed = true;

    if ( r <= 0 ) {

        // The rectangle is rendered from the top-right going clockwise

        shape.addVertex(new Vector2D(right, top));
        shape.addVertex(new Vector2D(right, bottom));
        shape.addVertex(new Vector2D(left, bottom));
        shape.addVertex(new Vector2D(left, top));

    } else {

        // Rounded corners must be taken into account

        let rounded: number = Math.min(s.x / 2, s.y / 2, r);
        let tangent: number = rounded * ELLIPSE_CONSTANT;

        shape.addVertex(new Vector2D(right, top + rounded));
        shape.setInTangent(new Vector2D(0, -tangent));
        shape.addVertex(new Vector2D(right, bottom - rounded));
        shape.setOutTangent(new Vector2D(0, tangent));
        shape.addVertex(new Vector2D(right - rounded, bottom));
        shape.setInTangent(new Vector2D(tangent, 0));
        shape.addVertex(new Vector2D(left + rounded, bottom));
        shape.setOutTangent(new Vector2D(-tangent, 0));
        shape.addVertex(new Vector2D(left, bottom - rounded));
        shape.setInTangent(new Vector2D(0, tangent));
        shape.addVertex(new Vector2D(left, top + rounded));
        shape.setOutTangent(new Vector2D(0, -tangent));
        shape.addVertex(new Vector2D(left + rounded, top));
        shape.setInTangent(new Vector2D(-tangent, 0));
        shape.addVertex(new Vector2D(right - rounded, top));
        shape.setOutTangent(new Vector2D(tangent, 0));
    }
}

Rectangle rendering guide

Path

Custom Bezier shape

Composition Diagram for Path Path Shape Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'sh'

Shape Type

Shape Type

d Shape Direction

Direction

Direction the shape is drawn as, mostly relevant when using trim path

ks Bezier

Shape

Bezier path

PolyStar

Star or regular polygon

Composition Diagram for PolyStar PolyStar Shape Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'sr'

Shape Type

Shape Type

d Shape Direction

Direction

Direction the shape is drawn as, mostly relevant when using trim path

p Position

Position

Position

or Scalar

Outer Radius

Outer Radius

os Scalar

Outer Roundness

Outer Roundness as a percentage

r Scalar

Rotation

Rotation, clockwise in degrees

pt Scalar

Points

Points

sy Star Type

Star Type

Star Type

ir Scalar

Inner Radius

Inner Radius

is Scalar

Inner Roundness

Inner Roundness as a percentage


def polystar(shape: Bezier, p: Vector2D, pt: float, r: float, or_: float, os: float, sy: int, ir: float, is_: float):
    points: int = int(round(pt))
    alpha: float = -r * math.pi / 180 - math.pi / 2
    theta: float = -math.pi / points
    tan_len_out: float = (2 * math.pi * or_) / (4 * points) * (os / 100)
    tan_len_in: float = (2 * math.pi * ir) / (4 * points) * (is_ / 100)

    shape.closed = True

    for i in range(points):
        beta: float = alpha + i * theta * 2
        v_out: Vector2D = Vector2D(or_ * math.cos(beta),  or_ * math.sin(beta))
        shape.add_vertex(p + v_out)

        if os != 0 and or_ != 0:
            # We need to add bezier tangents
            tan_out: Vector2D = v_out * tan_len_out / or_
            shape.set_in_tangent(Vector2D(-tan_out.y, tan_out.x))
            shape.set_out_tangent(Vector2D(tan_out.y, -tan_out.x))

        if sy == 1:
            # We need to add a vertex towards the inner radius to make a star
            v_in: Vector2D = Vector2D(ir * math.cos(beta + theta), ir * math.sin(beta + theta))
            shape.add_vertex(p + v_in)

            if is_ != 0 and ir != 0:
                # We need to add bezier tangents
                tan_in = v_in * tan_len_in / ir
                shape.set_in_tangent(Vector2D(-tan_in.y, tan_in.x))
                shape.set_out_tangent(Vector2D(tan_in.y, -tan_in.x))

void polystar(Bezier shape, Vector2D p, float pt, float r, float or_, float os, int sy, float ir, float is)
{
    int points = std::round(pt);
    float alpha = -r * std::numbers::pi / 180 - std::numbers::pi / 2;
    float theta = -std::numbers::pi / points;
    float tan_len_out = 2 * std::numbers::pi * or_ / 4 * points * os / 100;
    float tan_len_in = 2 * std::numbers::pi * ir / 4 * points * is / 100;

    shape.closed = true;

    for ( int i = 0; i < points; i++ )
    {
        float beta = alpha + i * theta * 2;
        Vector2D v_out(or_ * std::cos(beta), or_ * std::sin(beta));
        shape.add_vertex(p + v_out);

        if ( os != 0 && or_ != 0 )
        {
            // We need to add bezier tangents
            Vector2D tan_out = v_out * tan_len_out / or_;
            shape.set_in_tangent(Vector2D(-tan_out.y, tan_out.x));
            shape.set_out_tangent(Vector2D(tan_out.y, -tan_out.x));
        }

        if ( sy == 1 )
        {
            // We need to add a vertex towards the inner radius to make a star
            Vector2D v_in(ir * std::cos(beta + theta), ir * std::sin(beta + theta));
            shape.add_vertex(p + v_in);

            if ( is != 0 && ir != 0 )
            {
                // We need to add bezier tangents
                tan_in = v_in * tan_len_in / ir;
                shape.set_in_tangent(Vector2D(-tan_in.y, tan_in.x));
                shape.set_out_tangent(Vector2D(tan_in.y, -tan_in.x));
            }
        }
    }
}

function polystar(shape: Bezier, p: Vector2D, pt: number, r: number, or: number, os: number, sy: number, ir: number, is: number) {
    let points: number = new Number(round(pt));
    let alpha: number = -r * Math.PI / 180 - Math.PI / 2;
    let theta: number = -Math.PI / points;
    let tanLenOut: number = 2 * Math.PI * or / 4 * points * os / 100;
    let tanLenIn: number = 2 * Math.PI * ir / 4 * points * is / 100;

    shape.closed = true;

    for ( let i: number = 0; i < points; i++ ) {
        let beta: number = alpha + i * theta * 2;
        let vOut: Vector2D = new Vector2D(or * Math.cos(beta), or * Math.sin(beta));
        shape.addVertex(p + vOut);

        if ( os != 0 && or != 0 ) {
            // We need to add bezier tangents
            let tanOut: Vector2D = vOut * tanLenOut / or;
            shape.setInTangent(new Vector2D(-tanOut.y, tanOut.x));
            shape.setOutTangent(new Vector2D(tanOut.y, -tanOut.x));
        }

        if ( sy == 1 ) {
            // We need to add a vertex towards the inner radius to make a star
            let vIn: Vector2D = new Vector2D(ir * Math.cos(beta + theta), ir * Math.sin(beta + theta));
            shape.addVertex(p + vIn);

            if ( is != 0 && ir != 0 ) {
                // We need to add bezier tangents
                tanIn = vIn * tanLenIn / ir;
                shape.setInTangent(new Vector2D(-tanIn.y, tanIn.x));
                shape.setOutTangent(new Vector2D(tanIn.y, -tanIn.x));
            }
        }
    }
}

Grouping

Group

Shape Element that can contain other shapes

Composition Diagram for Group Group Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'gr'

Shape Type

Shape Type

np number

Number Of Properties

Number Of Properties

it array of Graphic Element

Shapes

Shapes

A group defines a render stack, elements within a group MUST be rendered in reverse order (the first object in the list will appear on top of elements further down).

  1. Apply the transform
  2. Render Styles and child groups in the transformed coordinate system.

Transform

Group transform

Composition Diagram for Transform Shape Transform Shape Graphic Element Transform Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'tr'

Shape Type

Shape Type

a Position

Anchor Point

Anchor point: a position (relative to its parent) around which transformations are applied (ie: center for rotation / scale)

p Splittable Position

Position

Position / Translation

r Scalar

Rotation

Rotation in degrees, clockwise

s Vector

Scale

Scale factor, [100, 100] for no scaling

o Scalar

Opacity

Opacity

sk Scalar

Skew

Skew amount as an angle in degrees

sa Scalar

Skew Axis

Direction along which skew is applied, in degrees (0 skews along the X axis, 90 along the Y axis)

Transform shapes MUST always be present in the group and they MUST be the last item in the it array.

They modify the group's coordinate system the same way as Layer Transform.

Style

Describes the visual appearance (like fill and stroke) of neighbouring shapes

Composition Diagram for Shape Style Shape Style Graphic Element Visual Object Stroke Gradient Gradient Stroke Fill
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string

Shape Type

Shape Type

o Scalar

Opacity

Opacity, 100 means fully opaque

Shapes styles MUST apply their style to the collected shapes that come before them in stacking order.

Fill

Solid fill color

Composition Diagram for Fill Fill Shape Style Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'fl'

Shape Type

Shape Type

o Scalar

Opacity

Opacity, 100 means fully opaque

c Color

Color

Color

r Fill Rule

Fill Rule

Fill Rule

Stroke

Solid stroke

Composition Diagram for Stroke Stroke Shape Style Base Stroke Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'st'

Shape Type

Shape Type

o Scalar

Opacity

Opacity, 100 means fully opaque

lc Line Cap

Line Cap

Line Cap

lj Line Join

Line Join

Line Join

ml number

Miter Limit

Miter Limit

ml2 Scalar

Miter Limit

Animatable alternative to ml

w Scalar

Width

Stroke width

d array of Stroke Dash

Dashes

Dashed line definition

c Color

Color

Stroke color

Stroke Dashes

An item used to described the dash pattern in a stroked path

A stroke dash array consists of n dash entries, [n-1,n] gap entries and [0-1] offset entries.

Dash and gap entries MUST all be in a continuous order and alternate between dash and gap, starting with dash. If there are an odd number of dashes + gaps, the sequence will repeat with dashes and gaps reversed. For example a sequence of [4d, 8g, 16d] MUST be rendered as [4d, 8g, 16d, 4g, 8d, 16g].

If any of the dash or gap entries are less than 0, or if the total sum of all their values is 0, the whole dash array is ignored, and the stroke MUST be treated as if the dash was not set.

Offset entry, if present, MUST be at the end of the array.

Composition Diagram for Stroke Dash Stroke Dash Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

n Stroke Dash Type

Dash Type

Dash Type

v Scalar

Length

Length of the dash

Gradient Fill

Gradient fill color

Composition Diagram for Gradient Gradient Shape Style Base Gradient Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'gf'

Shape Type

Shape Type

o Scalar

Opacity

Opacity, 100 means fully opaque

g Gradient

Colors

Gradient colors

s Position

Start Point

Starting point for the gradient

e Position

End Point

End point for the gradient

t Gradient Type

Gradient Type

Type of the gradient

h Scalar

Highlight Length

Highlight Length, as a percentage between s and e

a Scalar

Highlight Angle

Highlight Angle in clockwise degrees, relative to the direction from s to e

r Fill Rule

Fill Rule

Fill Rule

Gradient Stroke

Gradient stroke

Composition Diagram for Gradient Stroke Gradient Stroke Shape Style Base Stroke Base Gradient Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'gs'

Shape Type

Shape Type

o Scalar

Opacity

Opacity, 100 means fully opaque

lc Line Cap

Line Cap

Line Cap

lj Line Join

Line Join

Line Join

ml number

Miter Limit

Miter Limit

ml2 Scalar

Miter Limit

Animatable alternative to ml

w Scalar

Width

Stroke width

d array of Stroke Dash

Dashes

Dashed line definition

g Gradient

Colors

Gradient colors

s Position

Start Point

Starting point for the gradient

e Position

End Point

End point for the gradient

t Gradient Type

Gradient Type

Type of the gradient

h Scalar

Highlight Length

Highlight Length, as a percentage between s and e

a Scalar

Highlight Angle

Highlight Angle in clockwise degrees, relative to the direction from s to e

Modifiers

Modifiers change the bezier curves of neighbouring shapes

Modifiers replace shapes in the render stack by applying operating on the bezier path of to the collected shapes that come before it in stacking order.

Trim Path

Trims shapes into a segment

Composition Diagram for Trim Path Trim Path Modifier Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'tm'

Shape Type

Shape Type

s Scalar

Start

Segment start

e Scalar

End

Segment end

o Scalar

Offset

Offset

m Trim Multiple Shapes

Multiple

How to treat multiple copies

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

offset={o360o360o0o360o360o<0start=offset+min(1,max(0,min(s,e)100))end=offset+min(1,max(0,max(s,e)100))

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:

Pucker / Bloat

Interpolates bezier vertices towards the center of the shape, and tangent handles away from it (or vice-versa)

Composition Diagram for Pucker Bloat Pucker Bloat Modifier Graphic Element Visual Object
Attribute Type Title Description
nm string

Name

Human readable name, as seen from editors and the like

hd boolean

Hidden

Whether the shape is hidden

ty string = 'pb'

Shape Type

Shape Type

a Scalar

Amount

Amount as a percentage

When a is 0, nothing changes.
When a is positive, the vertices are pulled towards the center, with 100 being at the center. And the tangents are pushed away.
When a is negative the vertices are pushed away from the center with 100 being twice far away from the center. And the tangents are pulled towards the center.

The center is defined as the mean of the bezier vertices.


def pucker_bloat(shape: Bezier, a: float) -> Bezier:
    shape_out: Bezier = Bezier()
    amount: float = a / 100

    if len(shape) == 0:
        return shape

    # Find the center (arithmetic mean of all the points)
    center: Vector2D = Vector2D(0, 0)
    for point in shape:
        center += point.vertex
    center /= len(shape)

    # Move the points
    for point in shape:
        vertex: Vector2D = lerp(point.vertex, center, amount)
        shape_out.add_vertex(vertex)
        shape_out.set_in_tangent(lerp(point.absolute.in_tangent, center, -amount) - vertex)
        shape_out.set_out_tangent(lerp(point.absolute.out_tangent, center, -amount) - vertex)

    shape_out.closed = shape.closed
    return shape_out

Bezier pucker_bloat(Bezier shape, float a)
{
    Bezier shape_out();
    float amount = a / 100;

    if ( len(shape) == 0 )
    {
        return shape;
    }

    // Find the center (arithmetic mean of all the points)
    Vector2D center(0, 0);
    for ( const auto& point : shape )
    {
        center += point.vertex;
    }
    center /= len(shape);

    // Move the points
    for ( const auto& point : shape )
    {
        Vector2D vertex = lerp(point.vertex, center, amount);
        shape_out.add_vertex(vertex);
        shape_out.set_in_tangent(lerp(point.absolute.in_tangent, center, -amount) - vertex);
        shape_out.set_out_tangent(lerp(point.absolute.out_tangent, center, -amount) - vertex);
    }

    shape_out.closed = shape.closed;
    return shape_out;
}

function puckerBloat(shape: Bezier, a: number): Bezier {
    let shapeOut: Bezier = new Bezier();
    let amount: number = a / 100;

    if ( shape.length == 0 ) {
        return shape;
    }

    // Find the center (arithmetic mean of all the points)
    let center: Vector2D = new Vector2D(0, 0);
    for ( let point of shape ) {
        center += point.vertex;
    }
    center /= shape.length;

    // Move the points
    for ( let point of shape ) {
        let vertex: Vector2D = lerp(point.vertex, center, amount);
        shapeOut.addVertex(vertex);
        shapeOut.setInTangent(lerp(point.absolute.inTangent, center, -amount) - vertex);
        shapeOut.setOutTangent(lerp(point.absolute.outTangent, center, -amount) - vertex);
    }

    shapeOut.closed = shape.closed;
    return shapeOut;
}