Skip to content

This appendix explains issues that can be confusing when creating scripts and how to address them.

1. Adjusting the advance of individually scaled characters ​

1.1 When this is needed ​

This is needed when you change scale or stretch for individual characters in OnLayout and want to rearrange the characters according to their resulting sizes.

1.2 The problem ​

Character advances are determined before individual transformations are applied in OnLayout. Therefore, simply changing scale or stretch for each character can cause the following problems.

  • Enlarged characters overlap adjacent characters.
  • Extra spacing remains on both sides of reduced characters.
  • Character reference positions shift away from the baseline.

This occurs because characters are scaled up or down around geometry.bounds_center while their original advances remain unchanged.

1.3 Solution ​

After changing character size, rotation, and pivot, call mt.layout.reflow at the end of OnLayout.

lua
function OnLayout(ctx)
    for index = 1, ctx.char_count do
        local character = ctx.chars[index]
        character.scale = 0.8 + index * 0.05
    end

    -- Adjust advances to match the character sizes after transformation
    mt.layout.reflow(ctx)
end

If you need additional space between characters, specify the spacing in the second argument. To limit the affected characters or the characters that remain fixed, specify config. For argument details, see mt.layout.reflow.

1.4 How it works ​

mt.layout.reflow uses the horizontal and vertical layout origins to rearrange characters according to their current scale, stretch, rotation, and pivot while preserving the spacing produced by shaping, kerning, tracking, and margins. It does not make the spacing between individual ink outlines uniform.

When the pivot is 0.5 and only scale is changed, a point P in natural coordinates moves according to the following formula. C is geometry.bounds_center, s is scale × stretch, and d is offset - 0.5.

text
P' = C + s × (P - C) + d
d  = P'(target) - C - s × (P - C)

You do not need to calculate this formula directly in the script. mt.layout.reflow aligns the layout origins while accounting for the complete 2D character transformation, including rotation, pivot, vertical writing, and non-square canvases.

2. Adjusting the gradient range and direction ​

2.1 When this is needed ​

Check this section when the gradient start position, end position, or color direction does not match your expectations after changing start_position, end_position, or angle.

2.2 The problem ​

start_position and end_position are not coordinates relative to the entire canvas. The gradient angle is also not calculated using only the width or height of the bounding box.

If you treat these values as canvas coordinates, the position where the colors transition may appear to shift away from the expected location when the text is moved, rotated, or enlarged, or when a diagonal gradient is set.

2.3 Solution ​

Specify start_position and end_position as ratios of the range of the entire transformed text as viewed along the angle direction.

  • To apply the gradient from one edge of the text to the other, set start_position = 0.0 and end_position = 1.0.
  • To transition colors inside the text, increase start_position and decrease end_position. With the standard settings of 0.2 and 0.8, the gradient interval begins 20% inward from each edge.
  • With angle = 0, colors change from left to right; with angle = 90, from top to bottom; and with angle = -90 or 270, from bottom to top.

The fill color is assigned to the start_position side, and end_color is assigned to the end_position side. The direction of angle follows the same definition as coordinates.

2.4 Reference range ​

The gradient uses the bounding box of the entire text after scale, rotation, offset, and other transformations have been applied to characters and parts in OnLayout.

  • During 2D rendering: This is the 2D bounding box on the canvas that encloses the transformed paths of all characters and parts.
  • During 3D projection: This is the bounding box on the 3D layout plane, unaffected by camera projection. Because projected coordinates are calculated for each character, the gradient position does not shift relative to the characters when the camera moves.

2.5 How the reference line is calculated ​

The gradient reference line from 0.0 to 1.0 is the line segment connecting the nearest and farthest extents when the bounding box is projected in the angle direction. For diagonal directions, the boundaries are not based on the rectangle's width or height, but on the position that extends farthest in the specified direction and the position on the opposite side.

Let the center of the bounding box be C, the direction vector of angle be D, and the four vertices be P_i. The projected value of each vertex is calculated using the following formula.

text
proj_i = (P_i - C) · D

If the minimum projected value is minProj and the maximum projected value is maxProj, the start point bbP0 and end point bbP1 of the reference line are as follows.

text
bbP0 = C + D * minProj
bbP1 = C + D * maxProj

If start_position is t_s and end_position is t_e, the actual start point P0 and end point P1 are the following positions on the reference line.

text
P0 = bbP0 + (bbP1 - bbP0) * t_s
P1 = bbP0 + (bbP1 - bbP0) * t_e

3. Passing values between callbacks ​

3.1 When this is needed ​

This is needed when you want to use a value from before it is overridden in OnPreLayout later in OnLayout during the same frame. For example, this applies when using the host's Scale as a factor that determines animation travel rather than character size.

3.2 The problem ​

If you change ctx.global.scale to 1.0 in OnPreLayout, OnLayout can only access the modified value. Because the original host value cannot be restored, you can no longer perform calculations using that value.

In contrast, values that are not overridden in OnPreLayout can be read directly from ctx.global in OnLayout. For example, reference tracking and line_spacing directly where they are used instead of saving them.

3.3 Solution ​

Save the value before it is overridden in a local variable declared outside the functions, and reference it from OnLayout. One Lua execution environment is created for each instance and persists until the script is reloaded, so the same variable can be referenced across callbacks.

lua
local scaleKnob = 1.0

function OnPreLayout(ctx)
    -- Save the host's Scale, then cancel its effect on character size
    scaleKnob = math.max(ctx.global.scale, 0.05)
    ctx.global.scale = 1.0
end

function OnLayout(ctx)
    local travel = 0.07 * scaleKnob
    for index = 1, ctx.char_count do
        ctx.chars[index].offset_y = ctx.chars[index].offset_y + travel
    end
end

Use this method only when you need a value from before it was overridden. Saving unnecessary values increases the script's state and makes the flow of processing harder to understand.

3.4 Do not use this to preserve state between frames ​

Variables declared outside functions retain their values not only between callbacks but also between frames. However, due to seeking, changes in rendering order, parallel rendering, and other factors, the host does not necessarily evaluate frames in chronological order.

If you perform delta calculations or accumulation using values from the previous frame, the results may differ between playback and rendering. Ensure that the result of each frame is determined uniquely from that frame's ctx.time. Outside functions, retain only constants and values passed between callbacks within the same frame.