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.
| Field | Type | OnPreLayout | OnLayout | OnPath | Description |
|---|---|---|---|---|---|
ctx.time | number | ○ | ○ | ○ | Current time in seconds, with the beginning of the clip as 0 (§1.1) |
ctx.frame | number | ○ | ○ | ○ | Frame number with the beginning of the clip as 0. May include subframes (§1.1) |
ctx.fps | number | ○ | ○ | ○ | Frame rate (§1.1) |
ctx.canvas | table | ○ | ○ | ○ | Canvas and project pixel information (§1.2) |
ctx.fonts | string[] | ○ | ○ | ○ | 1-based array of font names that can be assigned to global.font_name (§1.3) |
ctx.timeline | table | ○ | ○ | ○ | Clip time range and current progress (§1.4) |
ctx.font | table | - | ○ | ○ | Metrics (dimensional information) for the font in use (§1.5) |
ctx.text | string | ○ | ○ | - | Deprecated text reference (§1.6) |
ctx.char_count | integer | - | ○ | ○ | Number of valid text characters; shapes are not counted (§1.6) |
ctx.part_count | integer | - | ○ | ○ | Number of valid text parts; shapes are not counted (§1.6) |
ctx.shape_count | integer | - | ○ | ○ | Number of declared shapes (§1.17) |
ctx.meta | table | ○ | ○ | ○ | API version and feature information (§1.7) |
ctx.inputs | table | ○ | ○ | ○ | Script inputs configured in the Inspector (§1.16) |
ctx.global | table | ○ | ○ | ○ | Global settings. Writable in OnPreLayout and read-only thereafter (§1.8) |
ctx.camera | table | ○ | ○ | ○ | Camera settings. Writable in OnPreLayout and read-only thereafter (§1.9) |
ctx.chars | table[] | - | ○ | ○ | 1-based array of generated text characters (§1.10) |
ctx.parts | table[] | - | ○ | ○ | 1-based array of generated text parts (§1.11) |
ctx.bounding_box | table | - | ○ | ○ | Bounding-box drawing settings (§1.12) |
ctx.output | table | - | ○ | ○ | Output filtering and drawing-order settings (§1.13) |
ctx.paths | table | - | - | ○ | Context for retrieving and editing part paths (§1.14) |
ctx.glyphs | table | ○ | - | - | Context for declaring custom glyphs (§1.15) |
ctx.shapes | table | ○ | ○ | ○ | Context for declaring shapes and reading them by number (§1.17) |
ctx.shape_cameras | table | ○ | ○ | - | 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.
| Field | Type | Unit | Description |
|---|---|---|---|
width / height | number | px | Canvas width and height |
aspect_ratio | number | - | Canvas width divided by height (width / height) |
pixel_aspect_ratio | number | - | 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
| Field | Type | Unit | Description |
|---|---|---|---|
available | boolean | - | Whether a finite clip range could be obtained |
start_frame / end_frame | number | frame | Start/end of the range (clip-local; the start is always 0) |
duration_frames | number | frame | end_frame - start_frame |
duration_seconds | number | second | Duration converted to seconds |
progress | number | 0-1 | Current 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.
| Pattern | Anchor | How to Write It |
|---|---|---|
| Intro (entrance) | Beginning of clip | Use ctx.time directly (real seconds). For a combined intro + outro, use mt.timeline.intro_outro_seconds (§10.9) |
| Outro (exit) | End of clip | mt.timeline.remaining(ctx) (real seconds remaining until the end; §10.9) |
| Typewriter, stagger, spring, etc. | Beginning of clip | ctx.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 clip | progress (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.
-- 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.
| Field | Type | Unit | Description |
|---|---|---|---|
em_size | number | canvas height ratio | Size of one em before transforms |
ascent | number | canvas height ratio | Ascent upward from the baseline |
descent | number | canvas height ratio | Descent downward from the baseline (positive value, expressed as a downward amount) |
line_height | number | canvas height ratio | Line height |
units_per_em | number | font design unit | Number 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
| Field | Type | Unit | Description |
|---|---|---|---|
ctx.meta.api_level | integer | - | Lua API compatibility level |
ctx.meta.plugin_version | string | - | Running plugin version (for display) |
ctx.meta.capabilities | table | - | Information about currently available features. Read-only |
ctx.meta.capabilities.has_3d | boolean | - | Whether 3D Projection is enabled for the current preset |
ctx.meta.limits | table | - | Limits applied to this instance. Read-only |
ctx.meta.limits.max_characters | integer | - | Maximum number of characters this instance can handle |
ctx.meta.limits.max_parts | integer | - | Maximum number of parts this instance can handle |
ctx.meta.limits.max_shapes | integer | - | 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_seed | integer | - | 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
| Field | Type | Unit | Description |
|---|---|---|---|
text | string | - | UTF-8 text. Changing it reshapes the text and regenerates the character and part counts |
font_name | string | - | Font family name (one of ctx.fonts). Changing it changes the font in use and reshapes the text |
tracking | number | em | Character spacing added to the trailing advance of each character. 1.0 is 1 em |
line_spacing | number | multiplier | Advance between lines / vertical-writing columns |
vertical | boolean | - | When true, enables vertical writing and changes the shaping and placement direction |
single_part_glyph | boolean | - | 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_align | string | - | Horizontal alignment. left / center / right |
v_align | string | - | Vertical alignment. top / center / bottom / baseline |
margins | number[] | em | Trailing advance for each character. Uses the same unit as tracking |
emoji | table | - | Emoji placement and font settings (table below) |
position_x / position_y | number | canvas normalized coordinate | Global position. 0.5 is the center; positive X is right and positive Y is up |
rotation | number | degree | Global 2D rotation. Positive (+) rotates right (clockwise); negative (-) rotates left (counterclockwise) |
yaw / pitch | number | degree | Global 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 |
scale | number | multiplier | Global uniform scale. Clamped to a minimum of 0.0001 when written |
stretch_x / stretch_y | number | multiplier | Global nonuniform scale. Unlike scale, it has no positive lower bound; 0 and negative values are accepted |
pivot_x / pivot_y | number | canvas normalized displacement | Pivot for Global transformations. 0.5 means displacement 0; larger values move it right/up from global.position |
opacity | number | 0-1 | Global 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 |
fill | table | - | Global fill settings (table below) |
gradient | table | - | Gradient settings (table below) |
stroke | table | - | Global stroke settings (table below) |
shadow | table | - | Global shadow settings (table below) |
texture | table | - | Global texture settings (table below) |
blur | table | - | 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.
(margins[i] + tracking) * designUnitsPerEmArray 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.
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.
| Field | Type | Unit | Description |
|---|---|---|---|
color | color | RGBA 0-1 | Global fill color. Also serves as the gradient start color |
transparent | boolean | - | 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
| Field | Type | Unit | Description |
|---|---|---|---|
color_space | string | - | Gradient / Crayon mode. The values are the enumeration below |
end_color | color | RGBA 0-1 | Gradient end color |
midpoint | number | 0-1 | Midpoint of color interpolation |
angle | number | degree | Gradient direction. At 0 degrees it progresses from left to right, and positive (+) rotates right (clockwise) |
start_position / end_position | number | Position relative to the transformed Global bounds | Gradient interval. The standard range is 0.0-1.0. For details, see Gradient Range and Direction |
Values of gradient.color_space:
noneokhsvokhsloklablinear_rgboklch_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.
| Field | Type | Unit | Description |
|---|---|---|---|
use | boolean | - | 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] |
type | string | - | Material kind. The values are the enumeration below |
p1 | number | material-dependent | First control |
p2 | number | material-dependent | Second control |
p3 | number | material-dependent | Third control |
p4 | number | material-dependent | Fourth control |
p5 | number | material-dependent | Fifth 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) |
color | color | RGBA 0-1 | Colors 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:
type | p1 | p2 | p3 | p4 |
|---|---|---|---|---|
none | - | - | - | - |
water_droplet | Thickness | Roundness | Gloss | Light angle |
air_cushion | Inflation | Seal width | Wrinkles | Light angle |
glitter | Flake size | Highlight ratio | Flake amount | Light angle |
iridescent | Film thickness | Hue range | Strength | Light angle |
faceted | Facet count | Bevel depth | Polish | Light angle |
stained_glass | Piece size | Lead width | Hue range | Glow |
metallic | Polish | Relief | Reflection contrast | Light angle |
metallic_2 | Polish | Relief | Exposure | Light angle |
stripe | Stripe density (count) | Stripe width | Strength | Stripe angle (degrees) |
colored_pencil | Scratch density | Scratch length | Light amount | Stroke angle (degrees) |
halftone, halftone_diamond | Dot density (count) | Dot size | Fade amount | Grid angle (degrees) |
prism_foil | Tile size | Hue range | Strength | Light 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.
| Field | Type | Unit | Description |
|---|---|---|---|
use | boolean | - | 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_x | number | em | Horizontal blur, as a Gaussian sigma. 1.0 = 1 em; 0 leaves the horizontal edges sharp. Clamped to 0-10 when written |
amount_y | number | em | Vertical blur, with the same unit and range as amount_x |
glow | number | - | 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
| Field | Type | Unit | Description |
|---|---|---|---|
width | number | em (1.0 = font size 1 em. Multiplied by fontSize during rendering) | Stroke width |
gap | number | em (1.0 = font size 1 em) | Gap between the fill and stroke |
color | color | RGBA 0-1 | Stroke color |
outer_width | number | em (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_color | color | RGBA 0-1 | Outer Stroke color |
order | string | - | Stacking order of fill and stroke. fill_over_stroke / stroke_over_fill / fill_over_stroke_per_character |
join | string | - | Join style for stroke corners. miter_clip / round / bevel / miter_round / miter_bevel |
miter_limit | number | multiplier | Maximum 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).
| Field | Type | Unit | Description |
|---|---|---|---|
enabled | boolean | - | Whether the Global shadow is enabled |
distance | number | em | Shadow 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 |
angle | number | degree | Shadow direction. 0 degrees points right, and positive (+) rotates right (clockwise) (90 degrees points straight down) |
blur_x / blur_y | number | em | Blur 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 |
glow | number | - | 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 |
color | color | RGBA 0-1 | Shadow 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.
| Field | Type | Unit | Description |
|---|---|---|---|
offset_x / offset_y | number | em (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 |
scale | number | multiplier | Emoji 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_font | boolean | - | true uses the font_name font; false uses the system emoji font (Windows: Segoe UI Emoji, macOS: Apple Color Emoji) |
font_name | string | - | 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_outline | boolean | - | 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.
| Field | Type | Unit | Description |
|---|---|---|---|
target_x / target_y | number | canvas normalized coordinate | Camera 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 / pitch | number | degree | Camera orbit. Positive yaw moves the camera around to the right; positive pitch moves it upward (looking down) |
perspective | number | - | 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 |
zoom | number | multiplier | Projection magnification. Clamped to a minimum of 0.001 when written |
lens_shift_x / lens_shift_y | number | canvas ratio | Principal 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".
| Field | Type | Unit | Description |
|---|---|---|---|
index | integer | - | 1-based character index. Read-only |
text | string | - | Original text of this character cluster. Read-only |
part_start / part_count | integer | - | 1-based starting global index and count of owned parts. Read-only |
line_index | integer | - | 1-based index of the line it belongs to. Read-only |
geometry | table | - | Placement information before transformations (below). Read-only |
offset_x / offset_y | number | canvas normalized displacement | Character displacement. 0.5 means displacement 0; larger values move right/up |
pivot_x / pivot_y | number | canvas normalized displacement | Pivot for character transformations. At 0.5, the pivot equals the natural center; larger values move it right/up |
scale | number | multiplier | Uniform scale. Clamped to a minimum of 0.001 when written |
stretch_x / stretch_y | number | multiplier | Nonuniform scale. Unlike scale, it has no positive lower bound; 0 and negative values are accepted |
rotation | number | degree | 2D rotation. Positive (+) is clockwise (right rotation); negative (-) is counterclockwise (left rotation) |
z | number | canvas height ratio | Depth. Positive (+) moves deeper (away from the camera); negative (-) moves forward (toward the camera). Affects rendering only in 3D presets |
yaw / pitch | number | degree | Individual 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 |
opacity | number | 0-1 | Opacity 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_index | number | - | Paint order key for shapes (ctx.shapes) only. A text character ignores it (a write is accepted but does not affect the paint order) |
fill | table | - | Individual fill (table below) |
stroke | table | - | Individual stroke (table below) |
shadow | table | - | Individual shadow color (table below) |
texture | table | - | Individual texture. Same structure as ctx.global.texture, plus use |
blur | table | - | Individual blur of the text. Same structure as ctx.global.blur, plus use |
chars[index].fill
| Field | Type | Unit | Description |
|---|---|---|---|
use | boolean | - | When true, uses fill.color |
color | color | RGBA 0-1 | Individual color used when use is true |
chars[index].stroke
| Field | Type | Unit | Description |
|---|---|---|---|
use | boolean | - | When true, uses the individual stroke settings, including Inner and Outer |
width | number | em (1.0 = font size 1 em) | Used when use is true |
gap | number | em (1.0 = font size 1 em) | Used when use is true |
color | color | RGBA 0-1 | Used when use is true |
outer_width | number | em (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_color | color | RGBA 0-1 | Outer Stroke color |
chars[index].shadow
| Field | Type | Unit | Description |
|---|---|---|---|
use | boolean | - | When true, uses shadow.color, blur_x, blur_y and glow |
color | color | RGBA 0-1 | Individual color used when use is true |
blur_x / blur_y | number | em | Individual shadow blur used when use is true. Reads the inherited blur until set. Clamped to 0-10 when written |
glow | number | - | 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.
| Field | Type | Unit | Description |
|---|---|---|---|
canvas_origin_x / canvas_origin_y | number | canvas normalized coordinate | Typesetting origin (horizontal writing: placement origin corresponding to the cell's left edge × baseline) |
vertical_origin_x / vertical_origin_y | number | canvas normalized coordinate | Vertical-typesetting origin (column center axis × cell top) |
bounds_center_x / bounds_center_y | number | canvas normalized coordinate | Center of the area occupied by the character outline before transformations (ink bounds) |
bounds_width | number | canvas width ratio | Ink bounds width / canvas width |
bounds_height | number | canvas height ratio | Ink bounds height / canvas height |
advance_x | number | canvas width ratio | Font horizontal advance (px) / canvas width. Does not include tracking / margins |
advance_y | number | canvas height ratio | Font 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.
| Field | Type | Unit | Description |
|---|---|---|---|
index | integer | - | 1-based global part index. Read-only |
character_index | integer | - | 1-based index of the owning character. Read-only |
index_in_character | integer | - | 1-based part index within the character. Read-only |
geometry | table | - | Placement information before transformations (table below). Read-only |
offset_x / offset_y | number | canvas normalized displacement | Part displacement. 0.5 means displacement 0; larger values move right/up |
pivot_x / pivot_y | number | canvas normalized displacement | Pivot for part transformations. At 0.5, the pivot equals the natural position; larger values move it right/up |
scale | number | multiplier | Uniform scale. Clamped to a minimum of 0.001 when written |
rotation | number | degree | 2D rotation. Positive (+) is clockwise (right rotation); negative (-) is counterclockwise (left rotation) |
stretch_x / stretch_y | number | multiplier | Nonuniform scale. Unlike scale, it has no positive lower bound; 0 and negative values are accepted |
z | number | canvas height ratio | Depth. Positive (+) moves deeper (away from the camera); negative (-) moves forward (toward the camera). Affects rendering only in 3D presets |
yaw / pitch | number | degree | Individual 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 |
opacity | number | 0-1 | Opacity 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) |
fill | table | - | Individual fill with use / color. Has the same structure as the character's fill; when use is true, uses color |
stroke | table | - | 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 |
shadow | table | - | Individual shadow color with use / color. Has the same structure as the character's shadow; when use is true, uses color |
texture | table | - | Individual texture. Has the same structure as the character's texture; when use is true, uses that material |
blur | table | - | 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 Setting | use = false | use = true |
|---|---|---|
fill | Inherits the fill selected at the owning character level | Uses the part's fill.color |
stroke | Inherits the Inner / Outer settings selected at the owning character level | Uses the part's stroke settings |
shadow | Inherits the shadow.color selected at the owning character level | Uses 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.
| Field | Type | Unit | Description |
|---|---|---|---|
local_center_x / local_center_y | number | displacement in canvas-axis ratios | Part 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_y | number | canvas normalized coordinate | Canvas coordinates of the part's natural position (center after layout) |
bounds_center_x / bounds_center_y | number | canvas normalized coordinate | Center of the area occupied by the part outline before transformations (ink bounds) |
bounds_width / bounds_height | number | respective canvas-axis ratios | Width 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).
| Field | Type | Unit | Description |
|---|---|---|---|
enabled | boolean | - | Enables drawing the frame |
draw_top / draw_bottom / draw_left / draw_right | boolean | - | Whether to draw the stroke on each side |
color | color | RGBA 0-1 | Frame line color |
width | number | fontSize percentage | Frame line thickness |
fill_enabled | boolean | - | Enables filling inside the frame |
fill_color | color | RGBA 0-1 | Interior fill color (its alpha is multiplied by fill_opacity) |
fill_opacity | number | 0-1 | Opacity multiplier for the interior fill |
start_cap / end_cap | string | enumeration below | Caps for open path endpoints |
margin_x / margin_y | number | fontSize percentage | Margin outside the text bounds (added to each side) |
corner_radius | number | fontSize percentage | Corner radius when all four sides are enabled (capped at half the rectangle size) |
fill_offset_x / fill_offset_y | number | fontSize percentage | Translation of the fill rectangle. |
stroke_offset_x / stroke_offset_y | number | fontSize percentage | Translation 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.
| Field | Type | Unit | Description |
|---|---|---|---|
separation_enabled | boolean | - | Whether Separation output is enabled. Read-only |
render_mode | string | - | Current output slot. default / separation_1 / separation_2. Read-only |
targets_1 / targets_2 | string | - | Target specification string assigned to each Separation output. Read-only |
write_on_mode | string | - | Write-on unit. character or part |
write_on_start | number | 0-1 | Start of the visible interval (normalized progress) |
write_on_end | number | 0-1 | End of the visible interval. No filter (everything visible) when start = 0 and end = 1 |
reorder_parts | boolean | - | When true, uses writing order (stroke order) for part drawing order and reveal order. Has no visual effect on single-part characters |
manual_order_enabled | boolean | - | When true, overrides reveal / drawing order with manual_order_text |
manual_order_text | string | - | Manual order specification (syntax below) |
force_disable_3d_projection | boolean | - | 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.
function OnLayout(ctx)
-- This effect intentionally guarantees 2D output.
ctx.output.force_disable_3d_projection = true
endWrite-on
Visible elements are limited to those whose 1-based rank within the unit lies in [startLimit, endLimit].
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:
| mode | Default Rank | When manual_order_enabled | reorder_parts Only |
|---|---|---|---|
character | Character index in layout order (1..N) | Character-level manual reveal order | Character rank remains in layout order |
part | ctx.parts[index].index (1..N) | Manual reveal order of parts | Reveal 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.
| Token | Meaning |
|---|---|
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.
| Field | Type | Unit | Description |
|---|---|---|---|
units_per_em | number | path unit/em | Path 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.
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
| Field | Type | Unit | Description |
|---|---|---|---|
index | integer | - | 1-based global part number across the frame |
character_index | integer | - | 1-based number of the character it belongs to |
index_in_character | integer | - | 1-based part number within the character it belongs to |
line_index | integer | - | 1-based number of the line it belongs to |
text | string | - | Text represented by the character it belongs to |
path | path | - | 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.
| Field | Type | Default | Unit and description |
|---|---|---|---|
path | MtDrawingPath | Empty path | Initial shape. Part-center origin, Y-down, 1000 units = 1 em |
advance_x | number | 0.0 | Horizontal advance. Positive is right, 1000 units = 1 em |
advance_y | number | 0.0 | Vertical advance. Positive is up, 1000 units = 1 em; downward vertical flow uses a negative value |
bounds | number[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.
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"
endThe 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.
| Field | Type | Length | Description |
|---|---|---|---|
numbers | number[] | 14 | Unitless numeric inputs |
colors | MtColor[] | 8 | RGBA color inputs |
texts | string[] | 4 | Single-line text inputs |
booleans | boolean[] | 8 | Checkbox boolean inputs |
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.
| field | type | Default | Description |
|---|---|---|---|
path | MtDrawingPath | empty path | The 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) |
layer | string | "back" | "back" draws behind the text, "front" in front of it |
parent | integer | none | The 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 |
camera | integer | the parent's camera, else none | The 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_yat0.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_*andpivot_*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,blurandtextureare 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 = 0can 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.
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" })
endShape 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].
| Field | Type | Description |
|---|---|---|
index | integer | The shape number, the same value ctx.shapes:declare returned |
offset_x / offset_y | number | Position 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 / pitch | number | The 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 |
opacity | number | Opacity multiplier (0 to 1) |
parent | integer | Read-only. The parent shape number given to ctx.shapes:declare, 0 when there is no parent |
camera | integer | Read-only. The number of the camera that projects this shape (taken from its parent when it names none), 0 when it has none |
z_index | number | Paint 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 / blur | table | Individual appearance, the same fields as ctx.chars[index] |
path | path | The 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.
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 }
endctx.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.
| Field | Type | Default | Description |
|---|---|---|---|
target_x / target_y | number | 0.5 / 0.5 | The camera's look-at point (canvas fractions). target_y is positive upward |
yaw / pitch | number | 0 | The camera's orbit angles in degrees, in the same sense as ctx.camera |
perspective | number | 0.5 | Perspective strength; held between 0 and 1 |
zoom | number | 1 | Zoom multiplier |
lens_shift_x / lens_shift_y | number | 0 | Lens 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
camerauses its parent's camera, and naming a different one is an error. - On a shape with no camera,
yaw,pitchandzhave no visible effect. scaleacts 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, leavescaleat 1 and put the size in the path's dimensions and in the child'soffset(the parent's length).- The paint order is decided by the layer and then
z_index; inside one layer and onez_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.
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
endHow 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_camerasis projected in 3D, by that camera. - A shape's number cannot be written in the manual order (
manual_order_text). The paint order followslayer,z_indexand 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_2dandmt.layout.canvas_to_offset_2dtake a shape like a character: its anchor is its position. For a shape with a parent they work through the parent's transform, andplace_2dandcanvas_to_offset_2dreturn theoffsetin the parent's space (an error when the parent's scale is0).- 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]inOnLayout.