# Lesson06 – Counter and Text Formatting

Learn how to create a counter with auto-formatting

REMINDER
If you are an absolute beginner, please start your learning by checking the essential resources below:
http://www.jjgifford.com/expressions/basics/index.html
http://www.motionscript.com

# 1. Counter Setup

• Create a new Text Layer
• Add an expression to Text Layer> Text> Source Text
var num= linear(time,0,1.2,0,125000)
num
This expression animates in 1.2 seconds the counter from 0 to 125000.

# 2. Auto-formatting

To create a counter using the formatting \$125,000.00, a few changes needs to be done.
• Modify the variable to keep only 2 decimals
.tofixed() converts a number into a string,
keeping a specified number of decimals.
var num= linear(time,0,1.2,0,125000).toFixed(2);
num
• Constrain the counter to always display 8 digits

while (condition) {result}
executes & accumulates a block of code
until a specified condition is satisfied.

The Meyers’ Deeper modes of expression – while:
http://provideocoalition.com/f/story/dmoe7_decisions/P3

var num= linear(time,0,1.2,0,125000).toFixed(2);
while (num.length < 9) {num = “0” + num} {num}
Our string contains 9 characters ( 8 digits, 2 decimals and a point); the condition then must be that if the string is composed of less than 9 characters, run the code until there is enough.
For example, if the value is 36.00, it will run 6 times like this:
“0”+”36.00″ = “036.00”
“0”+”036.00″ = “0036.00”

“0”+”00036.00″ = “000036.00”Remember that we are adding strings and not numbers, when
0 + 0 + 0 + 0 + 1 = 1
“0” + “0” + “0” + “0” + “1” = “00001”
• Split the number in two with a comma
.slice(start,end: up to, but not including)
extracts part of a string
negative values select from the end of the string.
var num= linear(time,0,1.2,0,125000).toFixed(2);
while (num.length < 9) {num = “0” + num} {num};
num.slice(0,3) +”,” + num.slice(3,9)
This new result splits the string in 2 parts and glue them back together with a comma in between.
“\$ “+ num.slice(0,3) +”,” + num.slice(3,9)
CHECK OUT!
Dan Ebbert’s universal counter:
http://www.motionscript.com/design-guide/counter.html
DON’T FORGET

# Lesson05 – Versioning Info Bar

Learn how to format text, get system infos, make a version info bar

REMINDER
If you are an absolute beginner, please start your learning by checking the essential resources below:
http://www.jjgifford.com/expressions/basics/index.html
http://www.motionscript.com

# 1. Setup

• Create a new Shape Layer, adjust the X size to ~100px, and reposition the Shape Layer at the bottom of the composition
• (Optional) Change opacity to make it semi-transparent

# 2. System Time & Date

• Create a new Paragraph Text Layer, aligned to the bottom bar; type in the string “date” to use as a template to format your text using the character panel
• Add an expression to Text Layer(“Date”)> Text> Text Source
Date(0)
This function will display today’s date with default formatting. To change the formatting, a few more steps are required
• Store Date(0) in a new variable and create a new set of date() objects
Date(0) is an AE specific object to access system clock
new Date() declares a new date instance
.getDate / .getMonth / .getFullYear get the instance day / month / year
.getHours / .getMinutes / .getSeconds get the instance time

http://www.w3schools.com/jsref/jsref_obj_date.asp

var today = new Date(Date(0));
today.getDate() + “/” + (today.getMonth()+1) + “/” + today.getFullYear()
Note that months are indexed starting from 0 (jan=0, dec=11), hence +1 on the .getMonth object
• (optional) Duplicate the Text Layer and modify the expression to display current time
var today = new Date(Date(0));
today.getHours() + “:” + today.getMinutes() + “:” + today.getSeconds()

# 3. Timecode

You could be tempted to use Effect> Text> Timecode to display the current cursor position, but this option offers very limited customization in terms of formatting. By using a text layer and expression, you can modify it in any ways you may think about.
• Duplicate the Text Layer(“Date”), change paragraph alignment to right and reposition to the extreme right side of the bar
• Reveal the expression (type ‘ee’) and replace the expression
timeToCurrentFormat()
timeToCurrentFormat() can take several arguments, that won’t be of any use 99% of the time. See your Expression Reference in AE Help dropdown menu

# 4. Text Formatting

• Add a new Text Layer to display the project’s name
• Add a new Text Layer to display the version number
• To force the version number to always display 2 digits, add an expression to Text Layer(“version”)> Text> Text Source
var myversion = text.sourceText;
if ( myversion.length < 2 ) { “V.0” + myversion } else {“V.”+myversion}
This condition check the string length, and add a different prefix to the source text if it is empty or only 1 character

# 5. Compile

I want to save my work in a unique preset and get a cleaner timeline. Let’s make a unique Text Layer to display all the informations above!
• Create a new Paragraph Text Layer and resize the bounding box to match the size of the bar
• Add an expression on SourceText to store individual expressions above in variables
var today = new Date(Date(0));
var mydate = today.getDate() + “/” + (today.getMonth()+1) + “/” + today.getFullYear();
var mytimecode=timeToCurrentFormat();
To get all informations from the same text layer, let’s use “!” as a delimiter/separator
.split(“separator”, limit)[index]
creates an array from a single string, using the specified character as a delimiter.
A limit of sub-string can also be specified in the second argument
• Modify the text string using this format “ProjectName!versionNumber”
for example: “Mag04_fashion!2”,
• Add 2 more variables using .split
var myproject = text.sourceText.split(“!”)[0];
var myversion = text.sourceText.split(“!”)[1];
Following our example, var myproject = Mag04_fashion and var myversion = 2
• Write the result string
vmydate +” “+ myproject + ” ” + “V.”+ myversion + ” ” + mytimecode
• Modify the result to adopt the number formatting expression from § 4.
if ( myversion.length < 2 ) {
mydate +” “+ myproject + ” ” + “V.0″ + myversion + ” ” + mytimecode
} else {
mydate +” “+ myproject + ” ” + “V.”+ myversion + ” ” + mytimecode
}
• Change paragraph alignment to justify all in the Paragraph panel

DON’T FORGET

# Lesson04 – Graphic Charts from Text File

Learn how to generate graphic charts from an external data sheet
REMINDER
If you are an absolute beginner, please start your learning by checking the essential resources below:
http://www.jjgifford.com/expressions/basics/index.html
http://www.motionscript.com

• Create in an empty text file series of variables using this format
bar1 = [var1, var2, var3,…];
In this example I will store [Date, Income, Volume]
bar1 = [“2000”, 153, 344]
bar2 = [“2001”, 407, 512]
bar3 = [“2002”, 241, 723]
bar4 = [“2003”, 179, 811]
bar5 = [“2004”, 226, 869]
bar6 = [“2005”, 87, 778]

# 2. Bar Chart

• Back in After Effects, create a new Rectangle Shape Layer, and change the Size of the rectangle path to [100,100]
• Rename “Group 1” to “bar1” (name of your 1st variable in the spreadsheet)
• Add an expression to Shape Layer> Bar1> Transform> Scale
thisProperty.propertyGroup()
allows to access properties values from within a group hierarchy.
• To access the name of the group, go back upwards 2 levels Size property> Rectangle Path1> Bar1
var myIndex = thisProperty.propertyGroup(2).name;
\$.evalFile(“Your_absolute_path”);
• Use the 2nd data series as the source value
var myVal = eval(myIndex)[1];
[myVal, value[1]]
• To animate the bar, modify myVal with a linear interpolation
var myVal = linear(time, 0,1,0,eval(myIndex)[1]);
• Add an expression to shift the position of the bar, relatively to its index number
var myIndex = thisProperty.propertyGroup(2).name;
var shift = (myIndex.substr(3,2)-1) * 110;
[value[0], shift]
The second line extracts the incrementing number from the group’s name and multiply it by the vertical size of the bar(100) + gap (10) to shift the bar position vertically.
• To align the bars to the left, add an expression to Shape Layer> bar1> Transform> Anchor point
[-thisProperty.propertyGroup(2).content(“Rectangle Path 1”).size[0]/2,value[1]]
This expression retrieves the x size of the bar (go up 2 levels, then browses using .content objects), and shifts the anchor point negatively of half of its value
• Duplicate “bar1” group as many times as you have created variables in the text file; your graph should build itself.

# 3. Delays

This expression is a simple solution to shift in time properties that follows a proportional evolution, but introducing a delay. If you apply an Effect> Time> Echo, that is the basic principle, except that our solution will bring much more control to the animation.
• Edit the expression on Shape Layer> Bar1> Transform> Scale and replace var myVal by
var myVal = linear(time-(myIndex.substr(3,2)/5), 0,1,0,eval(myIndex)[1]);
Each iteration (bar) of the group will be shifted in time of 0.2 seconds (1/5 = 5/25 frames)

# 4. Labels (years)

• Create a new Point Text Layer
• Add an expression to Text Layer>Text> Source Text
\$.evalFile(“Your_absolute_path”);
Let’s search for all the eval(myIndex)[0] (= the years stored in the text file), and add them together to create a unique string to be used as a label for the chart.
for ( starting point, how many times to run, what to do after each run)
For loop creates and processes iterations of the same expression.
MORE OPERATORS
Increment ++
Decrement
adds a value to a variable +=
subtracts a value from a variable -=
• Add up eval(bar1)[0]+eval(bar2)[0]+…+eval(bar6)[0] = SUM eval(“bar”+i)[0].
By running this code from value i=1 to 6 ( i<7) and after each run incrementing i (i++).
for( i=1 ; i < 7 ; i++ ) {
eval(“bar”+i)[0];
}
If we leave it as it is, it will only display the last entry; the code runs normally, but it erase each entry before displaying the new one.
• Add strings together using the += operator to display all entries
for( i=1 ; i < 7 ; i++ ) {
myyears += eval(“bar”+i)[0]+ “\r” ;
}
myyears

Using “\r”, we also insert a line break after each entry to create a column of text instead of a line.

The “undefined” entry appears because the first time the code runs, when i = 1, the operator += tries to add to the first entry the previous entry, but there is nothing before that, hence the undefined message.

• Use your prefered .slice .replace or .substr to delete the entry
myyears.replace(“undefined”,””)
• Adjust font size and the paragraph spacing in the Parargraph Panel to align vertically the text with the bar spacing

# 5. Curve Chart

Unfortunately, we can not access vertex coordinates shape path or a mask in After Effects yet; instead, let’s draw over time the curve using the write-on effect.
• Create a new Adjustment Layer
\$.evalFile(“your_file_path”);
var t = time;
var i = Math.floor(t+1);
Math.floor() rounds a number DOWNWARDS to the nearest integer
Math.ceil() rounds it DOWNWARDS
Variable t defines the speed of the animation
Variable i, evolve over time (t) by steps, thanks to the object Math.floor(). To synchonize time (starting at 0) and iterations (starting at 1), we’ll floor value (t+1)
• Create 2 variables to store start position and end position
mypoint1 = [eval(“bar”+i)[1],i*110];
mypoint2 = [eval(“bar”+(i+1))[1],(i+1)*110];

X coordinates are the values stored in the text file, bar1[1] paired with bar2[1], bar2[1] with bar3[1],…
Y coordinates are multiplied by the size of a bar in the bar chart, so that both charts can be overlayed proportionnally.

• Write a linear interpolation to drive the whole animation
linear(t,i-1,i,mypoint1, mypoint2)
An error message will appear: “property or method ‘bar7’ missing…”
The last variable stored in the text file is bar6, we need to give boundaries to the animation by giving a maximum value to i
Math.max(val1, val2,…) outputs the highest value
Math.min(val1, val2,…) outputs the lowest value
• Modify var i
var i = Math.min(5,Math.floor(t+1));
When i reaches 5, it will stop to increment
Let’s offset the curve position and overlay it nicely to the bar chart
• Add Effect> Expression Controls> Point Control and rename it “offset”
• Create a new variable to store the offset point value and add it to both point variables
var offset = effect(“offset”)(“Point”);
mypoint1 = [eval(“bar”+i)[1],i*110] + offset;
mypoint2 = [eval(“bar”+(i+1))[1],(i+1)*110] + offset;
• Move the Point Control to align the graph with the bar chart.
• Modify var t to speed up the global animation
var t = time*5/2;
Each of the 5 point to point animation lasts for 1 second, I want to bring back this 5 seconds animation down to 2, hence *5/2
DON’T FORGET

# Lesson03 – Subtitle track from an external file

Learn how create a subtitles track from an external text file or using a markers track

REMINDER
If you are an absolute beginner, please start your learning by checking the essential resources below: http://www.jjgifford.com/expressions/basics/index.html
http://www.motionscript.com

# 1. Setup

• Create in an empty text file a serie of variables using this format
sub1 = [inPoint (frame), outPoint (frame), “subtitle”];

For example

sub1 = [0,45,”this is where it all began”];
sub2 = [50,75,”a simple text file”];
sub3 = [80,135,”that will change the world, \r forever”];
• Save the text file, and get its absolute path (drag&drop in a Terminal window)
• In After Effects, create a new comp and a new text layer
• Rename the layer to sub1 (name of the 1st variable in your text file)

# 2. Expression writing

• Add an expression to Text Layer> Text> Source Text
 var myIndex = thisLayer.name; \$.evalFile(“your_file_path”); eval(myIndex)[2] stores the layer’s name in a variable myIndex use your absolute path to point to your text file use array position 2 as the text source
• Add an expression to Text Layer> Opacity
var myIndex = thisLayer.name;
\$.evalFile(“your_file_path”);
• Store the in-point and out-point in 2 variables, and convert them in seconds (25fps)
var start = eval(myIndex)[0]/25;
var end = eval(myIndex)[1]/25;
• Finally create a condition to toggle on/off the opacity
if ( time > start && time < end ) {100} else {0}
if (conditions) { result } else { result if false }
CONDITION OPERATORS
equal to ==
not equal to !=
greater than or equal to >=
lesser than or equal to <=
LOGICAL OPERATORS
and &&
or ||
not !
• (Optional) Apply the textBox preset from the previous lesson, add an expression to the opacity property and use the pick whip to link it to the Text Layer Opacity property; parent the Shape Layer to the Text Layer
• Reposition the Text layer to the bottom of the screen
• Duplicate the layer(s) as many times as you have created variables

This setup has the huge advantage to update the subtitles if you modify the external text file (remember to purge memory to force to update after editing) But if you work with a .SRT/.AAS/…, then you need to convert using a powerful text editor to match the formatting.

# 3. Using Markers

You can import a markers track from Premiere (dynamic link) or FCP (XML); these markers have the ability to store both a timecode and a text input. In After Effects, you can add a marker to the selected layer with shortcut Ctrl+8, if you want to manually create the markers track, and access markers comment by double-clicking on the marker itself.

marker.numKeys() counts markers on the layer
marker.nearestKey() points to the nearest marker
marker.key() points to the specified keyframe
• Use .nearestKey() to read the comment stored in the nearest keyframe
marker.nearestKey(time).comment

The subtitle is not synchronized with the markers. nearestKey() behaves like a ceil operation, it rounds the number upwards to the nearest integer. To correct this, we can create a condition that triggers the subtitles only after the current time is greater than the marker position. For argumentation sake, let’s first pretend there is only 1 marker and 2 states, before and after the marker.

• Using an if condition, compare the marker “1” time position to the current time
var m=1;
if (marker.key(m).time <= time) {
marker.key(m).comment;
} else {“”}

If the current time is before the marker, then it will display nothing; if after, then it will display the marker “1” comment Now all what we need is to increment m each time we reach a new state. .nearestkey() does just that!

• redefine m variable using .nearestKey()
var m = marker.nearestKey(time).index;

Subtitles start at the right time but it does not play until the out-point marker

• Change the else condition to the marker comment at position m-1
var m = marker.nearestKey(time).index;
if (marker.key(m).time <= time) {
marker.key(m).comment;
} else {marker.key(m-1).comment}

AE returns an error: “this property has no keyframe number 0” When m = 1 then (m-1) = 0; there is no marker index “0”, we need to get rid of this unique event.

• Add a new condition for m = 1, to cancel marker.key(0)
var m=marker.nearestKey(time).index;
if (marker.key(m).time <= time) {
marker.key(m).comment;
} else { if (m == 1) {“”} else {marker.key(m-1).comment} }
Script that convert SRT file to a markers track:
https://github.com/908video/DCSubToAE
DON’T FORGET

# Lesson 02 – Strings from Outside of the Box

Learn how to import strings from a text file and how to use layer names to duplicate expressions

REMINDER
If you are an absolute beginner,
http://www.jjgifford.com/expressions/basics/index.html
http://www.motionscript.com

# 1. Setup

• Create an empty text file using TextEdit for example
If you use Textedit, check that smart quotes/smart dashes/smart links are not toggled on.
• Create variables to store datas, it can be strings, values, boolean, formulas or arrays.
var text1 = “this is a string from outside of the box”;
var text2 = “split my text \r in two”;
var text3 = [“array strings”,”in a text file”];
var text4 = [1,134/255,33/255,1,”hello world”];
Note ‘\r’ can be used to add a line break in a string
• Save the file, and get the absolute path to the file: drag & drop the file on a new Terminal window and copy the text

# 2. Read a Text File

Reuse the setup from Lesson01, using one text layer, and one shape layer; you can use the preset “textbox” to recall the expressions to drive the box size. Make sure the shape layer is parented to the text layer.

• Rename both layers to “text1” and “box1”
When duplicating these layers, they will automatically increment to “text2” “box2”, “text3” “box3”,…
• Add an expression to Text Layer (text1)> text> Source Text
eval(thisLayer.name)
\$.evalFile(“absolute_path”) loads datas from an external file
eval(variable name) tells which variable to read

Replace absolute_path by the path pointing to your text file (copy&paste it from the Terminal window)
thisLayer.name retrieves the layer’s name “text1” and uses it as the name of the variable to look for.
If you change the layer’s name to text3 or text4, the text layer content will change to the corresponding variable.
If you duplicate both layers, the text automatically updates but the box still matches the size of the 1st text layer, even if you parented the layers before-hand.

# 3. Parenting Expressions

Our goal is to make the expressions content also increment when duplicating a group of layers.

• Modify the expressions on the shape layer, by using one of the following javascript command to extract part of the layer’s name

Truncate a string with .substr(start_index, new_string_length)
Extracts parts of a string with .slice(start_index, end_index)
Search & Replace a specified value .replace(search_value, new_value)
More strings methods: http://www.w3schools.com/jsref/jsref_obj_string.asp
var target = “text” + thisLayer.name.substr(3,2) ;
var target = thisLayer.name.replace(“box”,”text”) ;
var target = “text” + thisLayer.name.slice(3,4) ;

These 3 methods produce the same result, “box1” becomes “text1”

Reveal all expressions on the selected layer with ‘ee’ shortcut
• Replace all “text1” entries with the new variable target

On the size property

var mytext = thisComp.layer(target);
[ mytext.sourceRectAtTime().width, mytext.sourceRectAtTime().height]

On the position property

var mytextY= thisComp.layer(target).sourceRectAtTime().top;
var mytextX= thisComp.layer(target).sourceRectAtTime().left;
var boxsize=content(“Rectangle 1″).content(“Rectangle Path 1″).size;
[mytextX+boxsize[0]/2, mytextY+boxsize[1]/2]

Now the expression on the shape layer is linked to the correct text layer when duplicating.

# 4. Process Arrays

It is possible to store other properties in the text file using numerical values. The variable “text4″ have an RGBA array followed by the string: [1,134/255,33/255,1,”hello world”]

• Add a color animator to the text layer
• Add an expression to the fill color
\$.evalFile(“your_path”);
var target = thisLayer.name;
[eval(target)[0],eval(target)[1],eval(target)[2],eval(target)[3]]

The first line accesses the text file (remember to replace your_path with the absolute path to your text file)
The last line uses the values stored at position 0,1,2,3 as an RGBA array result.

In 8bpc mode, colors values range from 0 to 255 (256 colors);
divide a value by 255 to convert it to a [0 ; 1] range.
You can also write operators / * + – , even store formulas!

If you modify the values you stored in the text file, you will then change the color of the text!
You need to purge the memory to force the expression to refresh Edit> Purge

Another tutorial using composition names and arrays to create lower thirds
DON’T FORGET

# Lesson 01 – String in a Box

Learn how to add an auto-sized box being you text, create a chat bubble template

REMINDER
If you are an absolute beginner,
http://www.jjgifford.com/expressions/basics/index.html
http://www.motionscript.com

# 1. Setup

• Create a point Text Layer by double clicking on the text tool in the menu bar and make the paragraph justification centered
To switch between point/paragraph text,
use the type tool to right click on the text in the composition window
• Create a new Shape Layer by double clicking on the Rectangle tool in the tool bar
Layer selected in the timeline = create a mask
No Layer selected = new shape Layer
• Drag the shape layer under the text layer in the timeline, and adjust the fill color.

# 2. Setup Expression

• Add an expression to shape layer(box)> rectangle 1> rectangle path 1>  size
• Declare a new variable to store the text string
Variables are containers for storing data values.
To declare a variable, type var variable_name =
Note that variable_name can be almost anything, as long as it is unique, and does not interfere with expressions and javascript commands.
• Use the pick whip to retrieve the text string from text layer(text)> sourceText
A string is a series of characters, to put it simply, it is text.
To store a text input as a string, type it in between quotation marks.
• End statement with ;
var mytext = thisComp.layer(“text”).text.sourceText;
• Enter the default array result
[value[0], value[1]]
More about variable and string :
http://www.w3schools.com/js

At this stage, the expression does not modify the position property.
Click the constrain proportion toggle to release it.

• Change position values 0.0 , 0.0 to 1.0 , your font size (approx)

# 3. .length attribute

.length attribute counts characters in a string.

• Edit the expression
[value[0] * mytext.length, value[1]]

Validate, X size property displays the characters count (including spaces)

• Adjust manually X size property until the box size is bigger than the text size
• Add 2 characters to the string length to add left/right margins
[value[0] * (mytext.length+2), value[1]]

Now edit the text, and as you can see, the box automatically match the text layer.
Note this method creates a box of an approximative size: ‘l’,’i’,’t’ take less space than ‘e’,’q’,’a’ so 2 texts of the same length will generate a box of exactly the same size, independing of the size in pixels of the text layer.

# 4. Animate text in

• Add an animation preset, like typewriter, and reveal keyframes (shortcut ‘u’)

Problem: The animation plays over the same period of time, independently of the string length.
Let’s replace keyframes with an expression, so that the animation plays at a constant speed, adapting to the string length.

• Create a variable to store the speed, in characters per second; in this example, every second, 20 characters will be animated.
var cps = 20 ;
• In a second variable, evaluate how long the animation should last by dividing the string length by the speed.
var tmax = sourcetext.length / cps ;
• Declare the result as a linear interpolation, starting at frame 0, using tmax variable as the ending frame, and animating the property from 0 to 100.
linear (time, 0, tmax, 0 , 100)
linear (time, starting frame, ending frame, starting value, ending value)

# 5. .sourceRectAtTime (CC 2014.2+)

This method determine the size of any layer in pixels

.width/.height get the size of the layer
.top/.left attribute gets the distance from the center of the composition to the top/left edge of the layer
• Edit the expression on shape layer(box)> rectangle 1> rectangle path 1> size
var mytext = thisComp.layer(“text”);
[ mytext.sourceRectAtTime().width, mytext.sourceRectAtTime().height]
• To adjust the box position, add an expression on shape layer(box)> rectangle 1> rectangle path 1> position
• Store in variables the layer’s positions and the box size.
var mytextY= thisComp.layer(“text”).sourceRectAtTime().top;
var mytextX= thisComp.layer(“text”).sourceRectAtTime().left;
var boxsize=content(“Rectangle 1”).content(“Rectangle Path 1”).size;
[mytextX, mytextY]
• we need to shift the result by half the size of the box.
[mytextX+boxsize[0]/2, mytextY+boxsize[1]/2]

Personalize your box by adding different strokes, round corners or even a triangle to give in a chat bubble look!

# 6. Chat bubble tick

• Add a polystar shape to the same shape layer by double clicking on the star tool in the menu bar
• Change the type to polygon, adjust the size and rotate of -30° the shape
• To adjust the anchor point, add an expression to shape layer(box)> Polystar 1> Polystar Path 1> position
Reminder: Equilateral triangle geometry
• Store size, offset and position of the box in 3 variables s, o, p
s=content(“Rectangle 1”).content(“Rectangle Path 1”).size;
o=content(“Rectangle 1”).content(“Offset Paths 1”).amount;
p=content(“Rectangle 1”).content(“Rectangle Path 1”).position;
• To reposition the triangle on the x axis, add the anchor point (shift) + half of the box size + offset (optional)
x=r+s[0]/2+o;
• Use the new x variable in the result array; to reposition the triangle on the Y axis, use the rectangle Y position instead
[x,p[1]]

You can extract the Fill object and put it under the two shapes to easily edit the color.
To easily add a stroke around the whole box, add a Layer> Layer style> Stroke to the shape layer.
Parent using the pick whip the shape layer (box) to the text layer (text) to reposition the chat bubble in the frame.

DON’T FORGET