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.
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)
endIf 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.
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.0andend_position = 1.0. - To transition colors inside the text, increase
start_positionand decreaseend_position. With the standard settings of0.2and0.8, the gradient interval begins 20% inward from each edge. - With
angle = 0, colors change from left to right; withangle = 90, from top to bottom; and withangle = -90or270, 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.
proj_i = (P_i - C) · DIf 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.
bbP0 = C + D * minProj
bbP1 = C + D * maxProjIf 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.
P0 = bbP0 + (bbP1 - bbP0) * t_s
P1 = bbP0 + (bbP1 - bbP0) * t_e3. 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.
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
endUse 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.