All our expressions are written for the newer JavaScript Expressions Engine

Blog

Dealing with text descenders in After Effects templates

Photo of Tim Haywood
Tim Haywood

|

July 31, 2020

You can download the After Effects project for the examples in this post:

It's common when creating After Effects templates (such as lower thirds) to set the size of a rectangle based on a text layer. This is so the box will grow to match the size of the layer, no matter the text content.

You do this by linking the size of the box to the width and height of the text layer, with the sourceRectAtTime() method .

The problem

You can also watch our tutorial on dealing with descenders below:

Play Youtube video

Linking the box height to the text causes issues as letters with descenders ("y, g, j") or ascenders ("h, f, l") will cause the height to fluctuate as the copy changes.

Sometimes this is what the design requires, but in most cases it's preferable to set the rectangle height from the x-height rather than the height of the text layer.

X-height is the height of flat topped lowercase letters, such as "x", "u" and "z".

Using x-height

Since the x-height is a consistent value across all letters, setting the height of the rectangle based on the x-height means the size won't change when the text contains descenders or ascenders.

Rather than hard-coding the x-height into the expression you can instead get the x-height from the text style properties.

Getting the size

As of After Effects v17, you can now use expressions to access text style properties, such as the font, tracking and size.

The two style properties needed to calculate the height are:

  • fontSize: used to calculate the x-height, which is the fontSize / 2
  • leading: the space between baselines
3 lines of text, with annotations showing the x-height and leading

You can destructure these two properties from the style object:

js
const { fontSize, leading } = thisLayer.text.sourceText.style;

And then use them to get the total height:

js
const { fontSize, leading } = thisLayer.text.sourceText.style;
const xHeight = fontSize / 2;
const totalHeight = xHeight + leading * (numLines - 1);

Here we're using fontSize / 2 to get the height of the first line, and then the leading to get the height of the remaining lines with leading * (numLines - 1).

You can use the textCount() function from our library aeFunctions to get the number of lines.

Then you can get the layer's width with sourceRectAtTime(). The complete expression for getting the descender-less size of the text layer is below.

js
const layer = thisLayer;
const { fontSize, leading } = layer.text.sourceText.style;
const { width } = layer.sourceRectAtTime();
const xHeight = fontSize / 2;
// Getting numLines is omitted for brevity
const height = xHeight + leading * (numLines - 1);
// The descender-less text size
const textSize = [width, height];

You then can use this textSize value to set the size of the box shape layer, while ignoring descenders and ascenders.

Getting the anchor point

You often need to set the anchor point dynamically as well, so the layer stays in a consistent position as you change the content.

This involves setting the anchor point to the corner of the text, offset by it's size, making sure to set the y value from the text style rather than sourceRectAtTime().

The anchor point origin for a text layer is at the baseline of the first line, so to set the anchor point to the top left you set each dimension to:

  • x: the left value of sourceRectAtTime
  • y: -xHeight, offsetting the default value by the x-height
the default anchor point of a text layer in the bottom left

For example, to center the anchor point to the text layer, ignoring descenders:

js
const { left } = layer.sourceRectAtTime();
const topLeft = [left, -xHeight];
topLeft + size / 2;

How we use it

To save writing out this code on each text (and box) layer, we've implemented these ideas in expression libraries aeFunctions and eBox.

Implementing these ideas in libraries avoids duplicating code.

On the text layer

The function layerRect() handles getting the descender-less position value, for any corner (or center) of the layer.

js
// textLayer.transform.anchorPoint
layerRect({ anchor: "center" }).position;

layerRect() automatically gets the descender-less size and position when used on a Text layer.

Box Path

There are two parts to setting the box size (which we do as an expression on a Path property):

  • Getting the correct width and height of the text layer
  • Setting the value for the Path property

To get the size and position of the text layer without descenders, we use again uselayerRect(). This gives us an object with size and position properties which we can use to create the rectangle.

js
const layer = thisComp.layer("TXT_Copy");
const textRect = layerRect({ layer, anchor: "center" });

We then use the library eBox to create a rectangular path based on the textRect object.

js
// Destructure functions from libraries
const { createBox } = footage("eBox.jsx").sourceData;
const { layerRect } = footage("aefunctions.jsx").sourceData.getFunctions();
// Padding around the text
const padding = 36;
// Get the text layer size and position
const textLayer = thisComp.layer("TXT_Copy");
const textRect = layerRect({
layer: textLayer,
anchor: "center",
});
// Create the box path
const myBox = createBox({
size: add(textRect.size, [padding, padding]),
position: textRect.position,
anchor: "center",
});
myBox.getPath();

This expression creates a path that stays centered to the text layer, and matches it size, while ignoring descenders and ascenders automatically.

Blog

For enquiries contact us at hey@motiondeveloper.com