Skip to content

Target API level: 15

ctx is the context that Mug Typography passes to each callback. It contains the information needed to process the current frame, including the current time, canvas information, Global settings, and the generated characters and parts.

The available fields and write permissions differ by callback. This document first lists availability for each callback, then describes the fields of ctx in order. For the callback execution order and purposes, see Callback Overview.

1. List of ctx Fields ​

The fields directly under ctx are themselves read-only and cannot be replaced with other values. However, objects such as ctx.global and ctx.chars[index] contain fields that can be modified depending on the callback. ○ indicates that a field can be read in that callback.

FieldTypeOnPreLayoutOnLayoutOnPathDescription
ctx.timenumber○○○Current time in seconds, with the beginning of the clip as 0 (§1.1)
ctx.framenumber○○○Frame number with the beginning of the clip as 0. May include subframes (§1.1)
ctx.fpsnumber○○○Frame rate (§1.1)
ctx.canvastable○○○Canvas and project pixel information (§1.2)
ctx.fontsstring[]○○○1-based array of font names that can be assigned to global.font_name (§1.3)
ctx.timelinetable○○○Clip time range and current progress (§1.4)
ctx.fonttable-○○Metrics (dimensional information) for the font in use (§1.5)
ctx.textstring○○-Deprecated text reference (§1.6)
ctx.char_countinteger-○○Number of valid text characters; shapes are not counted (§1.6)
ctx.part_countinteger-○○Number of valid text parts; shapes are not counted (§1.6)
ctx.shape_countinteger-○○Number of declared shapes (§1.17)
ctx.metatable○○○API version and feature information (§1.7)
ctx.inputstable○○○Script inputs configured in the Inspector (§1.16)
ctx.globaltable○○○Global settings. Writable in OnPreLayout and read-only thereafter (§1.8)
ctx.cameratable○○○Camera settings. Writable in OnPreLayout and read-only thereafter (§1.9)
ctx.charstable[]-○○1-based array of generated text characters (§1.10)
ctx.partstable[]-○○1-based array of generated text parts (§1.11)
ctx.bounding_boxtable-○○Bounding-box drawing settings (§1.12)
ctx.outputtable-○○Output filtering and drawing-order settings (§1.13)
ctx.pathstable--○Context for retrieving and editing part paths (§1.14)
ctx.glyphstable○--Context for declaring custom glyphs (§1.15)
ctx.shapestable○○○Context for declaring shapes and reading them by number (§1.17)
ctx.shape_camerastable○○-Context for declaring the cameras that view shapes and moving them by number (§1.17)

1.1 ctx.time / ctx.frame / ctx.fps ​

These fields express the current evaluation position relative to the beginning of the clip. ctx.time is in seconds, and ctx.frame is in frames. Because ctx.frame can include subframes, it is not necessarily an integer. ctx.fps is the number of frames per 1 second.

1.2 ctx.canvas ​

Canvas and project pixel information. All fields are read-only.

FieldTypeUnitDescription
width / heightnumberpxCanvas width and height
aspect_rationumber-Canvas width divided by height (width / height)
pixel_aspect_rationumber-Pixel aspect ratio of the host project

1.3 ctx.fonts ​

A read-only, 1-based array containing the names of installed fonts recognized by the host. Its contents match the Font list in the host UI, and in OnPreLayout its elements can be assigned to ctx.global.font_name.

Neither the array elements nor the number of elements can be changed.

1.4 ctx.timeline ​

FieldTypeUnitDescription
availableboolean-Whether a finite clip range could be obtained
start_frame / end_framenumberframeStart/end of the range (clip-local; the start is always 0)
duration_framesnumberframeend_frame - start_frame
duration_secondsnumbersecondDuration converted to seconds
progressnumber0-1Current position clamped to 0-1

Clip Range ​

The clip range is the clip's actual range on the timeline, including trimming and extensions before or after it. ctx.time, ctx.frame, and ctx.timeline receive values on the same time axis, with the beginning of the clip as 0. When applied to a video clip as a filter, they also follow that clip's range.

When available is false, the other values are 0, so provide a fallback.

NOTE

Limitation of extending the beginning in generator use: When the beginning of a generator clip (with no input connected) is extended on the timeline, the host treats that interval as a still image of the first frame, so script time does not advance until the extension boundary (the intro appears frozen in its initial state). To change the duration, change the length of the generator clip itself instead of extending it. This limitation does not apply when used as a filter.

Time-Axis Design Rules (Anchor × Speed) ​

Animation timing is designed along 2 axes: "(1) where it is fixed (anchor)" and "(2) how fast it advances (speed)." The default speed is real seconds (based on ctx.time), and progress, which scales proportionally with clip length, should be used only for the "full-span" pattern described below.

PatternAnchorHow to Write It
Intro (entrance)Beginning of clipUse ctx.time directly (real seconds). For a combined intro + outro, use mt.timeline.intro_outro_seconds (§10.9)
Outro (exit)End of clipmt.timeline.remaining(ctx) (real seconds remaining until the end; §10.9)
Typewriter, stagger, spring, etc.Beginning of clipctx.time (real seconds; specify a physical speed such as N seconds per character)
Full-span pattern (drift, sweep, or reveal across the entire duration)Entire clipprogress (the expected behavior is for the movement to lengthen when the duration is extended)

If a transition is written as a fraction of progress, extending the clip makes the fade or typing run in slow motion and breaks the intended behavior. An outro is fundamentally about aligning with the end rather than making its speed proportional to the duration, so write it in real seconds anchored to the end.

lua
-- NG: typing speed changes when the clip length changes
local visible = math.floor(mt.timeline.progress(ctx) * ctx.char_count)

-- OK: constant 0.08s per character regardless of clip length
local visible = math.floor(ctx.time / 0.08)

-- OK: outro fade over the last 0.5s, anchored to the clip end
local fade = mt.clamp(mt.timeline.remaining(ctx) / 0.5, 0.0, 1.0)

-- OK: 0.8s intro + 0.5s outro at fixed real-time speed
-- (compressed automatically only when the clip is shorter)
local intro, outro = mt.timeline.intro_outro_seconds(ctx, 0.8, 0.5)

1.5 ctx.font - Metrics for the Font in Use ​

Metrics (dimensional information) for the font in use, resolved after layout generation.

FieldTypeUnitDescription
em_sizenumbercanvas height ratioSize of one em before transforms
ascentnumbercanvas height ratioAscent upward from the baseline
descentnumbercanvas height ratioDescent downward from the baseline (positive value, expressed as a downward amount)
line_heightnumbercanvas height ratioLine height
units_per_emnumberfont design unitNumber of design units in the font's em square

NOTE

The final on-screen size can be predicted from the product of scale-related fields (2D presets only). The host internally calculates font size as a fixed ratio of canvas.height, so the same ratio is preserved even when the canvas resolution changes. For 2D presets, multiplying this base size by character.scale × character.stretch_x/y, part.scale, and ctx.global.scale × ctx.global.stretch_x/y gives the actual on-screen size (because all of these transformations are composed multiplicatively).

For 3D presets, this product is only an approximation. Camera projection performs perspective division according to depth (including z, apparent depth changes caused by yaw/pitch, and the camera's perspective / zoom), so on-screen size changes nonlinearly with depth, and a tilted character is distorted differently at each vertex and cannot be represented by a single scale factor. ctx.font and character.geometry contain pre-projection values, and the post-projection on-screen size cannot be obtained from OnLayout.

1.6 ctx.text / ctx.char_count / ctx.part_count ​

ctx.text is deprecated. Use ctx.global.text to read the current text, and assign to ctx.global.text in OnPreLayout to rewrite it.

ctx.char_count and ctx.part_count are the number of valid text characters and parts, respectively, after layout generation. Shapes (§1.17) are not counted; use ctx.shape_count. When the text or font changes, these values also change according to the shaping result.

1.7 ctx.meta ​

FieldTypeUnitDescription
ctx.meta.api_levelinteger-Lua API compatibility level
ctx.meta.plugin_versionstring-Running plugin version (for display)
ctx.meta.capabilitiestable-Information about currently available features. Read-only
ctx.meta.capabilities.has_3dboolean-Whether 3D Projection is enabled for the current preset
ctx.meta.limitstable-Limits applied to this instance. Read-only
ctx.meta.limits.max_charactersinteger-Maximum number of characters this instance can handle
ctx.meta.limits.max_partsinteger-Maximum number of parts this instance can handle
ctx.meta.limits.max_shapesinteger-Maximum number of shapes this instance can handle. Shapes use no character or part limit, so it is the same for every preset
ctx.meta.instance_seedinteger-Stable random seed unique to this instance (between 0 and 2^31-1 inclusive). It remains the same throughout the lifetime of a given instance and differs between instances

Feature Detection ​

Determine feature availability using ctx.meta.api_level >= required level and capabilities. Even when has_3d is false, assigning to Camera and yaw / pitch / z does not cause an error, but it does not affect rendering.

Uses of instance_seed ​

Using instance_seed as the seed for mt.random gives each instance a different random sequence, even with the same script.

1.8 ctx.global ​

FieldTypeUnitDescription
textstring-UTF-8 text. Changing it reshapes the text and regenerates the character and part counts
font_namestring-Font family name (one of ctx.fonts). Changing it changes the font in use and reshapes the text
trackingnumberemCharacter spacing added to the trailing advance of each character. 1.0 is 1 em
line_spacingnumbermultiplierAdvance between lines / vertical-writing columns
verticalboolean-When true, enables vertical writing and changes the shaping and placement direction
single_part_glyphboolean-true combines each font glyph into one part, preventing darker translucent contour intersections and internal stroke edges. Custom glyphs and color emoji are unaffected. Writable in OnPreLayout; changing it rebuilds the layout. Initialized from the Inspector each frame (API level 15).
h_alignstring-Horizontal alignment. left / center / right
v_alignstring-Vertical alignment. top / center / bottom / baseline
marginsnumber[]emTrailing advance for each character. Uses the same unit as tracking
emojitable-Emoji placement and font settings (table below)
position_x / position_ynumbercanvas normalized coordinateGlobal position. 0.5 is the center; positive X is right and positive Y is up
rotationnumberdegreeGlobal 2D rotation. Positive (+) rotates right (clockwise); negative (-) rotates left (counterclockwise)
yaw / pitchnumberdegreeGlobal 3D orientation. Affects rendering only in 3D presets. Positive yaw rotates the right edge toward the viewer; positive pitch rotates the top edge toward the viewer
scalenumbermultiplierGlobal uniform scale. Clamped to a minimum of 0.0001 when written
stretch_x / stretch_ynumbermultiplierGlobal nonuniform scale. Unlike scale, it has no positive lower bound; 0 and negative values are accepted
pivot_x / pivot_ynumbercanvas normalized displacementPivot for Global transformations. 0.5 means displacement 0; larger values move it right/up from global.position
opacitynumber0-1Global opacity. The default is 1.0. The host clamps out-of-range values to 0-1 immediately before rendering. It is multiplied as alpha after colors have been determined for fill, gradient, stroke, shadow, and bounding box (it does not participate in fill.use resolution. See Roles of use / opacity / fill.transparent). No corresponding keyframe parameter exists on the host; it can be written only from scripts
filltable-Global fill settings (table below)
gradienttable-Gradient settings (table below)
stroketable-Global stroke settings (table below)
shadowtable-Global shadow settings (table below)
texturetable-Global texture settings (table below)
blurtable-Global blur of the text itself (table below)

Inspector text input forms \n, <br>, <br/>, and <br /> become actual line-feed characters before OnPreLayout. CRLF becomes LF as well. Escaped input \\n and \<br> becomes literal \n and <br>. ctx.text reads the same interpreted text.

Text assigned in OnPreLayout is not interpreted again. In Lua, use "A\nB" or a multiline string for an actual line break, and "A\\nB" for literal \n. Character arrays are generated after custom glyph declaration and insertion; line breaks themselves are not entries in ctx.chars.

ctx.global.margins ​

Indexing and Calculation ​

margins is a 1-based array keyed by character number in layout order. This number is the same as the index later assigned in ctx.chars. margins[i] is added in em units, together with tracking, to the advance after the i-th character.

text
(margins[i] + tracking) * designUnitsPerEm
Array Format ​

Keys must be integers from 1 and values must be numbers. Non-integer, string, zero, or negative keys and non-number values cause an error. Unset indices add no spacing, so assigning only margins[2], or a table such as { [2] = 0.5 }, is valid and leaves margins[1] at 0.

In OnPreLayout, ctx.global.margins has ctx.meta.limits.max_characters entries, so #ctx.global.margins is not the character count. Indices above that limit can never reach a character and are ignored.

Character Count ​

ctx.char_count is undefined in OnPreLayout. utf8.len(ctx.global.text) returns the number of UTF-8 code points, which may differ from the number of characters after shaping because of combining marks, ligatures, grapheme clusters, and similar constructs. Perform operations requiring the exact count in OnLayout.

Merging Subobjects ​

Assigning an entire table, as in ctx.global.shadow = { enabled = true, distance = 0.8 }, performs a per-field merge. Omitted fields retain their current values.

ctx.global.fill ​

NOTE

The color Type

color is a table with r (red), g (green), b (blue), and a (alpha). Each field is a number from 0-1; a = 0 means fully transparent, and a = 1 means fully opaque.

Fields can be assigned directly. When assigning an entire table, you can specify only the fields to change. Omitted fields retain their current values.

lua
ctx.global.fill.color.r = 1.0
ctx.global.fill.color = { a = 0.5 }

Fields other than r / g / b / a, and values other than numbers, cause an error.

FieldTypeUnitDescription
colorcolorRGBA 0-1Global fill color. Also serves as the gradient start color
transparentboolean-When true, hides the fill while preserving strokes and effects

Characters whose chars[index].fill.use is false use fill.color. For the inheritance order including parts, see "Inheritance Mechanism".

ctx.global.gradient ​

FieldTypeUnitDescription
color_spacestring-Gradient / Crayon mode. The values are the enumeration below
end_colorcolorRGBA 0-1Gradient end color
midpointnumber0-1Midpoint of color interpolation
anglenumberdegreeGradient direction. At 0 degrees it progresses from left to right, and positive (+) rotates right (clockwise)
start_position / end_positionnumberPosition relative to the transformed Global boundsGradient interval. The standard range is 0.0-1.0. For details, see Gradient Range and Direction

Values of gradient.color_space:

  • none
  • okhsv
  • okhsl
  • oklab
  • linear_rgb
  • oklch_short - API Level 11. The shorter OKLCH hue path.
  • oklch_long - API Level 11. The longer OKLCH hue path.
  • crayon

ctx.global.texture ​

The material layered over the fill. p1 to p4 are shared generic controls whose meaning follows the selected type.

FieldTypeUnitDescription
useboolean-Whether this element uses its own texture instead of the inherited one. Ignored on ctx.global; it only matters on ctx.chars[i] / ctx.parts[i]
typestring-Material kind. The values are the enumeration below
p1numbermaterial-dependentFirst control
p2numbermaterial-dependentSecond control
p3numbermaterial-dependentThird control
p4numbermaterial-dependentFourth control
p5numbermaterial-dependentFifth control. Only halftone, halftone_diamond, colored_pencil, prism_foil, iridescent and metallic read it: as the fade direction in degrees for the halftones (0 thickens the dots downwards), as the direction the light comes from on screen for colored_pencil (0 is the right, 90 the bottom), as the direction the tiles run for prism_foil (0 along the part's x axis), as the film variation from 0 to 1 for iridescent (0 is an even film), and as the hairline angle in degrees for metallic (0 along the part's x axis); other materials ignore it. Defaults to 0 (API level 14)
colorcolorRGBA 0-1Colors what the material adds over the fill: the lead for stained_glass, the metal colour for glitter, the stripes for stripe, the dots for halftone, the foil for prism_foil (white is silver), the light for the others. White leaves the light as the material makes it, and paints white lead (iridescent ignores it)

Values of texture.type, and what p1 to p4 mean for each:

typep1p2p3p4
none----
water_dropletThicknessRoundnessGlossLight angle
air_cushionInflationSeal widthWrinklesLight angle
glitterFlake sizeHighlight ratioFlake amountLight angle
iridescentFilm thicknessHue rangeStrengthLight angle
facetedFacet countBevel depthPolishLight angle
stained_glassPiece sizeLead widthHue rangeGlow
metallicPolishReliefReflection contrastLight angle
metallic_2PolishReliefExposureLight angle
stripeStripe density (count)Stripe widthStrengthStripe angle (degrees)
colored_pencilScratch densityScratch lengthLight amountStroke angle (degrees)
halftone, halftone_diamondDot density (count)Dot sizeFade amountGrid angle (degrees)
prism_foilTile sizeHue rangeStrengthLight angle (degrees on screen)

Flake Amount controls all glitter flakes: 0 removes flakes and glow, 1 keeps full density. Hue spread is fixed at the former Hue Range 1.0.

Lit flakes have a near-white core and a soft glow. Glow stays within the text outline and follows Highlight Ratio.

p1 to p4 are all generic slots whose range and unit the material decides. Most read 0 to 1, but a light angle, for one, reads degrees. Each material folds the value it receives into the range it can use (a control that turns repeats each cycle, one with a limit rests against its end), so writing outside that range never rewrites the stored value or its keyframes.

Texture is inherited across three levels: global, character, and part. Setting use = true on ctx.chars[i].texture / ctx.parts[i].texture swaps in a different material for that element alone. The character and part values are script-only (no host parameter backs them), so they always read as unused unless a script sets them.

ctx.global.blur ​

The blur of the text itself: fill, gradient, texture, stroke and crayon together. The shadow is not blurred by this setting; it has its own shadow.blur_x / blur_y. A bounding box is never blurred.

FieldTypeUnitDescription
useboolean-Whether this element uses its own blur instead of the inherited one. Ignored on ctx.global, which has no use; it only matters on ctx.chars[i] / ctx.parts[i]
amount_xnumberemHorizontal blur, as a Gaussian sigma. 1.0 = 1 em; 0 leaves the horizontal edges sharp. Clamped to 0-10 when written
amount_ynumberemVertical blur, with the same unit and range as amount_x
glownumber-Glow strength: brightens the blurred text into a glow, and a saturated core turns toward white. 0 = no glow. No effect while the text is not blurred. Clamped to 0-10 when written

The blur is measured in each part's own space along the canvas axes, so it follows the scale, stretch and perspective of the part like shadow.distance does. Blur is inherited across three levels: global, character, and part. Setting use = true on ctx.chars[i].blur / ctx.parts[i].blur gives that element its own blur. The character and part values are script-only (no host parameter backs them), so they always read as unused and equal to the inherited value until a script sets them. The shadow blur follows the same shadow.use as the shadow color, so overriding only the blur of one part means writing the inherited color back; mt.shadow.resolve(ctx, target, { level = "character" }) returns it.

ctx.global.stroke ​

FieldTypeUnitDescription
widthnumberem (1.0 = font size 1 em. Multiplied by fontSize during rendering)Stroke width
gapnumberem (1.0 = font size 1 em)Gap between the fill and stroke
colorcolorRGBA 0-1Stroke color
outer_widthnumberem (1.0 = font size 1 em)Width of the Outer Stroke adjacent to the outside of the Inner Stroke. Active only when width > 0
outer_colorcolorRGBA 0-1Outer Stroke color
orderstring-Stacking order of fill and stroke. fill_over_stroke / stroke_over_fill / fill_over_stroke_per_character
joinstring-Join style for stroke corners. miter_clip / round / bevel / miter_round / miter_bevel
miter_limitnumbermultiplierMaximum length of sharp miter corners

fill_over_stroke_per_character produces the same stacking as fill_over_stroke (fill above stroke), but completes it one character at a time. Every pass finishes for one run of adjacent items sharing a character index before the next run starts, so each character is finished with its own stroke already hidden under its own fill. Where characters overlap, a later character's stroke therefore never lands on an earlier character's fill. Items are never resorted by character index, so a draw order of A A B A forms three runs. When a gap is present, its erasure is confined to its own run and cannot cut into an earlier character. Frames with 3D projection enabled always draw per character regardless of order. Crayon is still drawn in one batch after the normal passes, so it is outside the scope of this setting.

The simulator's canvas preview does not reproduce order compositing. Selecting, saving, and reading or writing the value from a script all work, but confirm the final stacking in the plugin itself.

Characters whose chars[index].stroke.use is false use the Global stroke settings above. For the inheritance order including parts, see "Inheritance Mechanism".

ctx.global.shadow ​

NOTE

The shadow is a copy of the character or part with the same shape, offset to the position specified by distance / angle, and blurred by blur_x / blur_y (both 0 by default, which leaves its outlines sharp). Its size is always identical to the original, so at distance = 0 and no blur it overlaps exactly behind the main text and the shadow is not visible at all (it does not become visible through shrinkage or transparency).

FieldTypeUnitDescription
enabledboolean-Whether the Global shadow is enabled
distancenumberemShadow distance. 1.0 = 1 em. Measured in each part's own space, so the shadow keeps its distance from the glyph under any scale, stretch, rotation or perspective; the direction set by angle stays fixed on the canvas. Positive values move away in the angle direction. Maximum on write: 10.0
anglenumberdegreeShadow direction. 0 degrees points right, and positive (+) rotates right (clockwise) (90 degrees points straight down)
blur_x / blur_ynumberemBlur of the shadow, as a Gaussian sigma. 1.0 = 1 em. Same range as blur.amount_x / amount_y: clamped to 0-10 when written
glownumber-Glow strength: brightens the blurred shadow into a glow, and a saturated core turns toward white. 0 = no glow. No effect while the shadow is not blurred. Clamped to 0-10 when written
colorcolorRGBA 0-1Shadow color

Characters whose chars[index].shadow.use is false use shadow.color. For the inheritance order including parts, see "Inheritance Mechanism".

WARNING

Enabling the shadow increases drawing cost and reduces rendering performance. The impact is especially large at high resolutions or with complex text, so use it only when needed.

The gradient fills only areas where both the character and part fill.use values are false. An individual color with fill.use set to true takes precedence over the gradient and becomes a solid color. For the separation of stages between fill.use and opacity, see "Inheritance Mechanism".

ctx.global.emoji ​

Corresponds to the host's Emoji group in Text Settings. Writable in OnPreLayout and read-only in OnLayout and OnPath.

FieldTypeUnitDescription
offset_x / offset_ynumberem (1.0 = 1 em of the font size)How far emoji move from where the font places them. Positive Y is up. The advance is unchanged
scalenumbermultiplierEmoji are set as if in a font this many times the text size, advance included. Clamped to a minimum of 0.001 on write
use_custom_fontboolean-true uses the font_name font; false uses the system emoji font (Windows: Segoe UI Emoji, macOS: Apple Color Emoji)
font_namestring-Custom emoji font, one of ctx.fonts. Kept but ignored while use_custom_font is false. An unknown name falls back to the system emoji font
use_outlineboolean-true draws emoji from the custom font's outlines. Takes effect only while use_custom_font is true

WARNING

Changing any of these values rebuilds the layout (and, for deformed emoji, their images). Varying them every frame slows rendering considerably, so use fixed values.

Emoji drawn with use_outline are ordinary characters: fill color, gradient, texture, stroke and shadow apply as they do to text. A glyph without an outline, such as a color-only glyph, becomes a character with no parts and draws nothing.

The simulator shows emoji in a simplified way: each emoji sequence is one character with one part, placed with offset_x / offset_y / scale, and drawn with the browser's emoji font. It does not load the custom emoji font or draw use_outline outlines, and in the 3D preset it shows only the emoji's square. Check the final look in the plugin.

1.9 ctx.camera ​

The camera for 3D presets. Assignments do not cause errors in 2D presets, but affect rendering only in 3D presets.

FieldTypeUnitDescription
target_x / target_ynumbercanvas normalized coordinateCamera look-at point. Positive X is right and positive Y is up. When the look-at point moves, the object appears to move in the opposite direction
yaw / pitchnumberdegreeCamera orbit. Positive yaw moves the camera around to the right; positive pitch moves it upward (looking down)
perspectivenumber-Strength representing camera distance / perspective. Internally mapped to camera distance as distancePx = f(perspective) * canvas.height (f is a monotonic mapping from strength to distance coefficient; larger values move the camera closer and strengthen perspective). Held between 0 and 1. Every camera field must be finite; writing NaN or inf is an error
zoomnumbermultiplierProjection magnification. Clamped to a minimum of 0.001 when written
lens_shift_x / lens_shift_ynumbercanvas ratioPrincipal point offset. Positive lens_shift_x shifts everything right; positive lens_shift_y shifts everything up

1.10 ctx.chars[index] ​

ctx.chars is a 1-based array, as is standard in Lua. When the text or font changes, the number of characters and the index of each element also change.

When use for fill / stroke / shadow is false, the setting is not hidden; it inherits the Global value. For details, see "Inheritance Mechanism".

FieldTypeUnitDescription
indexinteger-1-based character index. Read-only
textstring-Original text of this character cluster. Read-only
part_start / part_countinteger-1-based starting global index and count of owned parts. Read-only
line_indexinteger-1-based index of the line it belongs to. Read-only
geometrytable-Placement information before transformations (below). Read-only
offset_x / offset_ynumbercanvas normalized displacementCharacter displacement. 0.5 means displacement 0; larger values move right/up
pivot_x / pivot_ynumbercanvas normalized displacementPivot for character transformations. At 0.5, the pivot equals the natural center; larger values move it right/up
scalenumbermultiplierUniform scale. Clamped to a minimum of 0.001 when written
stretch_x / stretch_ynumbermultiplierNonuniform scale. Unlike scale, it has no positive lower bound; 0 and negative values are accepted
rotationnumberdegree2D rotation. Positive (+) is clockwise (right rotation); negative (-) is counterclockwise (left rotation)
znumbercanvas height ratioDepth. Positive (+) moves deeper (away from the camera); negative (-) moves forward (toward the camera). Affects rendering only in 3D presets
yaw / pitchnumberdegreeIndividual 3D rotation. Positive yaw rotates the right edge toward the viewer; positive pitch rotates the top edge toward the viewer. Affects rendering only in 3D presets
opacitynumber0-1Opacity of this character. The default is 1.0, and the host clamps out-of-range values immediately before rendering. Multiplied by ctx.global.opacity (does not participate in fill.use resolution. See division of roles)
z_indexnumber-Paint order key for shapes (ctx.shapes) only. A text character ignores it (a write is accepted but does not affect the paint order)
filltable-Individual fill (table below)
stroketable-Individual stroke (table below)
shadowtable-Individual shadow color (table below)
texturetable-Individual texture. Same structure as ctx.global.texture, plus use
blurtable-Individual blur of the text. Same structure as ctx.global.blur, plus use

chars[index].fill ​

FieldTypeUnitDescription
useboolean-When true, uses fill.color
colorcolorRGBA 0-1Individual color used when use is true

chars[index].stroke ​

FieldTypeUnitDescription
useboolean-When true, uses the individual stroke settings, including Inner and Outer
widthnumberem (1.0 = font size 1 em)Used when use is true
gapnumberem (1.0 = font size 1 em)Used when use is true
colorcolorRGBA 0-1Used when use is true
outer_widthnumberem (1.0 = font size 1 em)Width of the Outer Stroke adjacent to the outside of the Inner Stroke. Active only when width > 0
outer_colorcolorRGBA 0-1Outer Stroke color

chars[index].shadow ​

FieldTypeUnitDescription
useboolean-When true, uses shadow.color, blur_x, blur_y and glow
colorcolorRGBA 0-1Individual color used when use is true
blur_x / blur_ynumberemIndividual shadow blur used when use is true. Reads the inherited blur until set. Clamped to 0-10 when written
glownumber-Individual shadow glow strength used when use is true. Reads the inherited value until set. Clamped to 0-10 when written

Assigning an entire table to a subobject follows the same merge rules as §1.8.

chars[index].geometry ​

Represents the layout from before character script transformations, Deform, and 3D projection.

Positions and dimensions use canvas normalization. Internal pixel coordinates are converted with X as 0.5 + px/width and Y as 0.5 - px/height.

FieldTypeUnitDescription
canvas_origin_x / canvas_origin_ynumbercanvas normalized coordinateTypesetting origin (horizontal writing: placement origin corresponding to the cell's left edge × baseline)
vertical_origin_x / vertical_origin_ynumbercanvas normalized coordinateVertical-typesetting origin (column center axis × cell top)
bounds_center_x / bounds_center_ynumbercanvas normalized coordinateCenter of the area occupied by the character outline before transformations (ink bounds)
bounds_widthnumbercanvas width ratioInk bounds width / canvas width
bounds_heightnumbercanvas height ratioInk bounds height / canvas height
advance_xnumbercanvas width ratioFont horizontal advance (px) / canvas width. Does not include tracking / margins
advance_ynumbercanvas height ratioFont vertical advance (px) / canvas height (sign-inverted to Y-up). Downward advance is negative. Does not include tracking / margins

A glyph-less character such as a space has collapsed ink bounds, so the font-defined character cell is reported instead. Along the layout flow axis (X in horizontal writing, Y in vertical writing) the character's own advance provides the extent, and the cross axis uses the cell height (horizontal) or the column width (vertical). A space's bounds_center_y therefore does not sit half a cell below its neighboring glyphs.

1.11 ctx.parts[index] ​

ctx.parts is a 1-based array, as is standard in Lua. index is not a number within each character, but a part number across the entire frame. When the text or font changes, the number of parts and the index of each element also change.

FieldTypeUnitDescription
indexinteger-1-based global part index. Read-only
character_indexinteger-1-based index of the owning character. Read-only
index_in_characterinteger-1-based part index within the character. Read-only
geometrytable-Placement information before transformations (table below). Read-only
offset_x / offset_ynumbercanvas normalized displacementPart displacement. 0.5 means displacement 0; larger values move right/up
pivot_x / pivot_ynumbercanvas normalized displacementPivot for part transformations. At 0.5, the pivot equals the natural position; larger values move it right/up
scalenumbermultiplierUniform scale. Clamped to a minimum of 0.001 when written
rotationnumberdegree2D rotation. Positive (+) is clockwise (right rotation); negative (-) is counterclockwise (left rotation)
stretch_x / stretch_ynumbermultiplierNonuniform scale. Unlike scale, it has no positive lower bound; 0 and negative values are accepted
znumbercanvas height ratioDepth. Positive (+) moves deeper (away from the camera); negative (-) moves forward (toward the camera). Affects rendering only in 3D presets
yaw / pitchnumberdegreeIndividual 3D rotation of the part. Applied around the part pivot inside the owning character's rotated space. Positive yaw rotates the right edge toward the viewer; positive pitch rotates the top edge toward the viewer. Affects rendering only in 3D presets
opacitynumber0-1Opacity of this part. The default is 1.0, and the host clamps out-of-range values immediately before rendering. Multiplied by the owning character's opacity (and further by ctx.global.opacity) (does not participate in fill.use resolution. See division of roles)
filltable-Individual fill with use / color. Has the same structure as the character's fill; when use is true, uses color
stroketable-Individual stroke with use / width / gap / color / outer_width / outer_color. Has the same structure as the character's stroke; when use is true, uses the individual values
shadowtable-Individual shadow color with use / color. Has the same structure as the character's shadow; when use is true, uses color
texturetable-Individual texture. Has the same structure as the character's texture; when use is true, uses that material
blurtable-Individual blur of the text. Has the same structure as the character's blur; when use is true, uses amount_x / amount_y / glow

Inheritance from the Character ​

A part's fill, stroke, and shadow are determined by their respective use values as follows.

Part Settinguse = falseuse = true
fillInherits the fill selected at the owning character levelUses the part's fill.color
strokeInherits the Inner / Outer settings selected at the owning character levelUses the part's stroke settings
shadowInherits the shadow.color selected at the owning character levelUses the part's shadow.color

If use = false on the owning character as well, resolution continues up to the ctx.global settings. For details, see "Inheritance Mechanism".

Individual Settings Not in the Inspector ​

There are no parameters for a part's stroke and shadow in the Inspector, so they begin unused/with default values. When a script sets use to true, the configured individual values take precedence.

There are also no yaw / pitch parameters in the Inspector, and their initial values are 0. They are independent of the owning character's rotation and are applied as additional rotation within the character's rotation space.

parts[index].geometry ​

Represents the layout before script transformations, Deform, and 3D projection. Positive Y is up.

FieldTypeUnitDescription
local_center_x / local_center_ynumberdisplacement in canvas-axis ratiosPart center relative to the owning character's pre-transformation outline bounds (ink bounds) center as the origin. No neutral offset (0 is at the character center). The Y component is Y-up
canvas_center_x / canvas_center_ynumbercanvas normalized coordinateCanvas coordinates of the part's natural position (center after layout)
bounds_center_x / bounds_center_ynumbercanvas normalized coordinateCenter of the area occupied by the part outline before transformations (ink bounds)
bounds_width / bounds_heightnumberrespective canvas-axis ratiosWidth and height of the ink bounds

1.12 ctx.bounding_box ​

When enabling a box that is disabled in the Inspector, explicitly set every box field the effect needs, including edges, width, margins, colors, fill, opacity, caps, and offsets. Native does not load current Inspector values for disabled box settings; do not use those fields as a style snapshot.

Base Rectangle ​

In 2D, the base rectangle encloses transformed fill bounds measured before Global rotation and rotates with the text. In 3D, it is authored on the global layout plane and then projected. Margins are added outside it, and fill and stroke have independent offsets.

Dimension Reference ​

The following dimensional fields are percentages of fontSize (rendering: value * fontSizePx / 100; 100.0 = the same length as fontSize).

FieldTypeUnitDescription
enabledboolean-Enables drawing the frame
draw_top / draw_bottom / draw_left / draw_rightboolean-Whether to draw the stroke on each side
colorcolorRGBA 0-1Frame line color
widthnumberfontSize percentageFrame line thickness
fill_enabledboolean-Enables filling inside the frame
fill_colorcolorRGBA 0-1Interior fill color (its alpha is multiplied by fill_opacity)
fill_opacitynumber0-1Opacity multiplier for the interior fill
start_cap / end_capstringenumeration belowCaps for open path endpoints
margin_x / margin_ynumberfontSize percentageMargin outside the text bounds (added to each side)
corner_radiusnumberfontSize percentageCorner radius when all four sides are enabled (capped at half the rectangle size)
fill_offset_x / fill_offset_ynumberfontSize percentageTranslation of the fill rectangle.
stroke_offset_x / stroke_offset_ynumberfontSize percentageTranslation of the frame line (independent of the fill)

Values of start_cap / end_cap: butt / square / round / round_rev / triangle / triangle_rev

1.13 ctx.output ​

Output filtering and drawing-order controls read and written in OnLayout. Separation-related fields are read-only.

FieldTypeUnitDescription
separation_enabledboolean-Whether Separation output is enabled. Read-only
render_modestring-Current output slot. default / separation_1 / separation_2. Read-only
targets_1 / targets_2string-Target specification string assigned to each Separation output. Read-only
write_on_modestring-Write-on unit. character or part
write_on_startnumber0-1Start of the visible interval (normalized progress)
write_on_endnumber0-1End of the visible interval. No filter (everything visible) when start = 0 and end = 1
reorder_partsboolean-When true, uses writing order (stroke order) for part drawing order and reveal order. Has no visual effect on single-part characters
manual_order_enabledboolean-When true, overrides reveal / drawing order with manual_order_text
manual_order_textstring-Manual order specification (syntax below)
force_disable_3d_projectionboolean-When true, makes the final decision to disable 3D projection for that frame. Defaults to false (API Level 7)

2D-only Scripts ​

Set ctx.output.force_disable_3d_projection = true to use the 2D rendering path even in a 3D-capable preset. This ignores all Camera, Global, Character, and Part 3D controls, including Inspector settings and keyframes. Do not set it merely because the current 3D values happen to be neutral; use it only when the effect is explicitly intended to be 2D-only.

lua
function OnLayout(ctx)
    -- This effect intentionally guarantees 2D output.
    ctx.output.force_disable_3d_projection = true
end

Write-on ​

Visible elements are limited to those whose 1-based rank within the unit lies in [startLimit, endLimit].

text
startLimit = floor(write_on_start * N) + 1
endLimit   = ceil(write_on_end * N)

N is the number of characters when write_on_mode == "character" and the number of parts when it is "part".

How ranks are determined:

modeDefault RankWhen manual_order_enabledreorder_parts Only
characterCharacter index in layout order (1..N)Character-level manual reveal orderCharacter rank remains in layout order
partctx.parts[index].index (1..N)Manual reveal order of partsReveal order in stroke order

manual_order_text Syntax ​

Token Interpretation ​

Tokens are separated by commas or spaces. Invalid tokens are ignored, and if the same part is specified multiple times, only the first specification is used.

TokenMeaning
c<n> / C<n>1-based character n. Expands all parts of that character in the part order in effect at the time (reorder_parts true uses stroke order; false uses layout order)
p<n> / P<n>1-based global part n
<n>No prefix. Interpreted as a part index
<a>-<b>Inclusive range. Both endpoints must be of the same type (both characters or both parts). If only one endpoint has a prefix, the other inherits its type. Expands in ascending order

Examples: "3,1,2" (parts 3→1→2), "c1-c3" (characters 1-3), "p2-p5" (parts 2-5).

Effect on Ordering ​

Specified elements are moved to the front in the specified order. Unlisted parts are not hidden; they follow afterward while preserving their relative order from the base ordering.

The order is determined from the undeformed layout and the specification string; it is not recomputed each frame from spatial positions after animation. write_on_start/end determines the visible range, while manual order changes only the order of elements.

In 2D, paint order follows manual order directly. In 3D, each part's depth remains primary; manual order, stroke order, then layout order break equal-depth ties. Write-on reveal order always follows manual order in both 2D and 3D.

1.14 ctx.paths ​

Because ctx.paths retrieves targets lazily, paths for parts that are not retrieved are not copied to the Lua side. ctx.paths is valid only during the OnPath call that received it; keeping it in a variable and using it in a later call raises an error.

FieldTypeUnitDescription
units_per_emnumberpath unit/emPath coordinate scale. Always 1000
part(index)function-Retrieves 1 part by its 1-based global part number. Returns nil if it does not exist
character(index)function-Retrieves all parts owned by the 1-based character number as an array ordered by global part number
select(selector)function-Retrieves parts matching the selection syntax as an array ordered by global part number
shape(number)function-Retrieves the part of the shape with the 1-based shape number, as a one-element array; an empty array if there is no such shape. part, character and select reach the text only

Selection Syntax for select(selector) ​

c denotes a character number, p denotes a global part number, and ! denotes exclusion. Multiple conditions can be specified with commas, and ranges such as c3-5 and p8-10 are supported.

lua
local parts = ctx.paths:select("c1, p8-10, !p9")

When the same part is retrieved multiple times, the same mutable object is returned within a frame.

Retrieved Parts ​

FieldTypeUnitDescription
indexinteger-1-based global part number across the frame
character_indexinteger-1-based number of the character it belongs to
index_in_characterinteger-1-based part number within the character it belongs to
line_indexinteger-1-based number of the line it belongs to
textstring-Text represented by the character it belongs to
pathpath-Drawing path to modify

Editing path ​

path is a mutable object representing the part's drawing outline. You can clear or replace the original outline and add straight lines or Bézier curves. For details on the methods and coordinate system, see Drawing Path Editing API.

1.15 ctx.glyphs ​

API level: 9+

ctx.glyphs:declare(declaration) ​

ctx.glyphs:declare(declaration) declares a custom glyph with a path and character metrics, then returns a one-character placeholder string that can be inserted into ctx.global.text. Because the declaration must be finalized before layout generation, this method is available only in OnPreLayout.

This declaration spans two coordinate spaces. The shape-drawing path uses Y-down like the existing drawing API, while the shape-placement advance_* and bounds use Y-up like the existing geometry API. Therefore, a 900-unit downward advance in vertical writing is specified as advance_y = -900.

FieldTypeDefaultUnit and description
pathMtDrawingPathEmpty pathInitial shape. Part-center origin, Y-down, 1000 units = 1 em
advance_xnumber0.0Horizontal advance. Positive is right, 1000 units = 1 em
advance_ynumber0.0Vertical advance. Positive is up, 1000 units = 1 em; downward vertical flow uses a negative value
boundsnumber[4]Derived from path{minimumX, minimumY, maximumX, maximumY}. Baseline origin, Y-up, 1000 units = 1 em

When bounds is omitted, the Part center's X is placed at advance_x / 2, and the path's bottom edge is aligned to the baseline. The derived bounds are { advance_x / 2 + pathMinimumX, 0, advance_x / 2 + pathMaximumX, pathMaximumY - pathMinimumY }. SVG icons normalized around the origin with mt.svg_path and view_box can therefore be reused without repeating their size. An empty path produces {0,0,0,0} bounds. advance_y remains an advance metric and does not affect automatic placement in horizontal writing.

In vertical writing, the ink center is placed at the center of the cell created by advance_y, independent of its horizontal-writing baseline placement. tracking and per-character margins are trailing cell spacing and do not shift the ink center. With advance_y = 0, the ink center is placed on the current vertical pen position.

bounds with neither width nor height (for example {0,0,0,0}) declares a glyph that draws but does not measure: it is excluded from the alignment box that h_align / v_align work against. Appending advance-less decoration markers to the text therefore leaves the line at its alignment edge. Because alignment is decided by bounds rather than advance, cancelling the advance with a negative margins entry is not a substitute for this exclusion.

When bounds is explicit, its center becomes the Part center. Values below the baseline are negative, and values above it are positive. For example, use { -500, 0, 500, 700 } for a shape extending 700 units above the baseline and 500 units to each side. Path coordinates remain Y-down, matching the existing drawing-path API.

lua
local icon = mt.svg_path("M 2 2 L 22 2 L 22 22 L 2 22 Z", {
    view_box = { 0, 0, 24, 24 },
    em_scale = 0.8,
})

function OnPreLayout(ctx)
    local marker = ctx.glyphs:declare({
        path = icon,
        advance_x = 1000,
        advance_y = -1000,
    })
    ctx.global.text = "A" .. marker .. "B"
end

The path is snapshotted at declaration time. Later changes to the source path do not alter an existing declaration. Omitting path while specifying bounds creates an invisible character carrying only the declared advance and bounds. To place the same custom glyph more than once, insert the returned marker at multiple positions in the text. advance_x and advance_y are pure glyph advances that exclude tracking and per-character margins. As with a normal character, the host adds tracking and the corresponding margins entry to the trailing advance. A glyph with advance_x = 0 therefore retains that spacing in horizontal layout.

The host assigns placeholders in declaration order from Unicode private-use code point U+E002. U+E000 and U+E001 are reserved for internal newline processing. Custom glyphs consume the same character and part limits as normal glyphs. If the hook or commit fails, every declaration from that frame is discarded.

1.16 ctx.inputs ​

ctx.inputs is a read-only object that exposes the Inspector's Script Inputs values to every callback. Each array is 1-based and has a fixed length.

FieldTypeLengthDescription
numbersnumber[]14Unitless numeric inputs
colorsMtColor[]8RGBA color inputs
textsstring[]4Single-line text inputs
booleansboolean[]8Checkbox boolean inputs
lua
local amount = ctx.inputs.numbers[1]
local primary = ctx.inputs.colors[1]
local caption = ctx.inputs.texts[1]
local enabled = ctx.inputs.booleans[1]

ctx.inputs, each array, and every color channel are read-only. An out-of-range index returns nil.

1.17 ctx.shapes ​

ctx.shapes handles shapes placed apart from the text (backgrounds, frames, decorations). A shape is not part of ctx.global.text and takes no part in the layout flow: advance widths, alignment and wrapping. ctx.chars, ctx.char_count, ctx.parts and ctx.part_count count the text only; shapes are reached through ctx.shapes[number] and ctx.shape_count.

Declare shapes in OnPreLayout, then read them by their 1-based shape number in OnLayout and OnPath.

ctx.shapes:declare(declaration) ​

Declares one shape and returns its 1-based shape number (in declaration order). It can only be called in OnPreLayout. Unknown fields and invalid values raise an error.

fieldtypeDefaultDescription
pathMtDrawingPathempty pathThe outline of the shape. Its origin is the shape position, Y-down, 1000 units = 1 em (the same coordinate system as a custom glyph's path)
layerstring"back""back" draws behind the text, "front" in front of it
parentintegernoneThe number of the shape to use as parent. It must be a shape declared earlier (a value an earlier declare returned). Omit it for no parent
cameraintegerthe parent's camera, else noneThe number of the camera that projects this shape in 3D. It must be one declared earlier with ctx.shape_cameras:declare(). A shape with a parent takes its parent's camera, and naming a different one is an error (a child of a shape with no camera cannot name one either). A shape with no camera stays fixed to the canvas as before

path is a snapshot taken at declaration. Shapes of the same layer overlap from the lowest z_index, and in declaration order when z_index is equal.

A shape with a parent is placed relative to it: its position, rotation, scale and stretch work inside the parent's transformed space.

  • With offset_x / offset_y at 0.5, the child sits on the parent's position (the origin of the parent's path). The offset is affected by the parent's scale, rotation and stretch.
  • The child's rotation, scale, stretch_* and pivot_* work about the child's own position, in the parent's space. Under an uneven parent stretch a rotated child looks sheared.
  • The parent's opacity, fill, stroke, shadow, blur and texture are not inherited. Each shape sets its own.
  • A child can be a parent too (the chain is followed up to the grandparents). A parent set to opacity = 0 can leave only its children visible.
  • When the parent's scale reaches 0, the child loses its size too and nothing is drawn. The chain's scale is accumulated in double precision; a shape whose own drawing matrix would exceed the range of a float is not drawn either (a shape in the middle may be out of range while a later one, whose scale comes back, is drawn).
  • Only a shape can be a parent; a text character cannot.
lua
local rectangle = mt.svg_path("M -500 -300 L 500 -300 L 500 300 L -500 300 Z")

function OnPreLayout(ctx)
    local background = ctx.shapes:declare({ path = rectangle })
    local frame = ctx.shapes:declare({ path = rectangle, layer = "front" })
end

Shape settings ​

ctx.shapes[number] can be read in OnLayout and OnPath. In OnLayout you can set the following fields with the same rules as ctx.chars[index].

FieldTypeDescription
indexintegerThe shape number, the same value ctx.shapes:declare returned
offset_x / offset_ynumberPosition offset. 0.5 means no offset. A shape you leave alone appears at the center of the canvas
pivot_x / pivot_y / scale / stretch_x / stretch_y / rotation / z / yaw / pitchnumberThe same transforms as ctx.chars[index]. A shape turns and scales about its position (the origin of its path), and pivot_* is measured from there
opacitynumberOpacity multiplier (0 to 1)
parentintegerRead-only. The parent shape number given to ctx.shapes:declare, 0 when there is no parent
cameraintegerRead-only. The number of the camera that projects this shape (taken from its parent when it names none), 0 when it has none
z_indexnumberPaint order key (default 0). Within a layer shapes are drawn from the lowest (a higher value is in front), and equal values keep declaration order. It never crosses the back/front layers of layer. +inf and -inf rank as the extremes, and assigning NaN raises an error. It is independent of the parent relation
fill / stroke / shadow / texture / blurtableIndividual appearance, the same fields as ctx.chars[index]
pathpathThe outline of the shape, in OnLayout only. It starts as the declared path and is edited in place, for example with path:assign(p). It returns to the declared path every frame

path cannot be assigned. shape.path:assign(p) replaces the outline with a copy of the path you pass. Only a path you edited is used for that frame. In OnPath, ctx.paths:shape(number) lets you edit the path further, starting from what OnLayout left.

lua
function OnLayout(ctx)
    local shape = ctx.shapes[1]
    shape.path:assign(drawLights(ctx))
    shape.opacity = 0.8
    shape.fill.use = true
    shape.fill.color = { r = 1.0, g = 0.5, b = 0.7, a = 1.0 }
end

ctx.shape_cameras:declare() ​

Declares one camera for shapes and returns its 1-based camera number (in declaration order). It can only be called in OnPreLayout, and at most 16 can be declared. A camera for shapes is a camera of its own, apart from the text's camera (ctx.camera): the text's camera, the Global and the text's 3D pose do not reach shapes, and a camera for shapes does not reach the text.

ctx.shape_cameras[number] can be read and set in OnLayout. Its values start every frame at the defaults of the text's camera, so write them in each frame in which you want them to move.

FieldTypeDefaultDescription
target_x / target_ynumber0.5 / 0.5The camera's look-at point (canvas fractions). target_y is positive upward
yaw / pitchnumber0The camera's orbit angles in degrees, in the same sense as ctx.camera
perspectivenumber0.5Perspective strength; held between 0 and 1
zoomnumber1Zoom multiplier
lens_shift_x / lens_shift_ynumber0Lens shift (canvas fractions). lens_shift_y is positive upward

The numbers must be finite; writing NaN or inf is an error. A shape whose pose or camera can no longer be carried out in numbers (z = 1e308, for example) is not drawn.

A shape that names a camera (ctx.shapes:declare({ camera = number })) has its own pose, yaw, pitch and z included, in 3D, and is projected by that camera.

  • Each shape turns about its own position (the origin of its path). A parent's pose carries its children: with the parent tilted by pitch, a child offset up the parent's height moves in depth.
  • One parent-and-child tree is projected by one camera. A child that omits camera uses its parent's camera, and naming a different one is an error.
  • On a shape with no camera, yaw, pitch and z have no visible effect.
  • scale acts across a shape's plane (x and y) only and does not reach depth, so a rotated child under a parent whose scale is not 1 shears. To keep the form of what hangs from a rotated shape, leave scale at 1 and put the size in the path's dimensions and in the child's offset (the parent's length).
  • The paint order is decided by the layer and then z_index; inside one layer and one z_index, only the shapes of the same camera are put in depth order, farthest first. Shapes of another camera and shapes with no camera keep their places.
  • The stroke width and the size of the shadow and blur follow the depth scale at the shape's position.
lua
function OnPreLayout(ctx)
    local view = ctx.shape_cameras:declare()
    local card = ctx.shapes:declare({ path = rectangle, camera = view })
end

function OnLayout(ctx)
    ctx.shape_cameras[1].pitch = 20
    ctx.shapes[1].yaw = ctx.time * 90
end

How shapes differ from the text ​

  • They take no part in the layout flow (advance widths, alignment, wrapping).
  • Write On, Gradient, Bounding Box, Deform, Crayon and selection in the viewer do not apply to them.
  • Global transforms and the text's 3D camera do not reach them. A shape is fixed to the canvas, and only a shape that names a camera from ctx.shape_cameras is projected in 3D, by that camera.
  • A shape's number cannot be written in the manual order (manual_order_text). The paint order follows layer, z_index and the order of declaration. The text cannot be placed between shapes.
  • They use no character or part limit. The limit on shapes is ctx.meta.limits.max_shapes, the same for every preset. A declaration beyond it raises an error.
  • Output Separation does apply to them.
  • Limitation: the text's Crayon strokes (an experimental feature) are drawn in front of layer = "front" shapes.
  • mt.layout.place_2d, mt.layout.get_canvas_position_2d and mt.layout.canvas_to_offset_2d take a shape like a character: its anchor is its position. For a shape with a parent they work through the parent's transform, and place_2d and canvas_to_offset_2d return the offset in the parent's space (an error when the parent's scale is 0).
  • The appearance set for the text (text color, stroke, shadow, blur, glow, texture) applies to shapes too. To override a setting you do not want, set it on ctx.shapes[number] in OnLayout.