Text and type

← back to Creative Computing

by Allison Parrish

In this tutorial, I’m going to show you how to use p5.js to display text in your sketch, and give your sketch different behaviors based on the shape of text in different sizes and fonts. We’ll also dive into string values, which are Javascript’s data type for storing and manipulating text. Finally, I’ll demonstrate how to load text from external files.

Text: the basics

You can display text to the screen in your sketch using the text() function. The text() function takes at least three parameters: a string of characters to display, and the X and Y coordinates where you want the text to be displayed.

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
  noLoop();
}
function draw() {
  background(50);
  noStroke();
  fill(255);
  text("Attention, please.", 50, 200);
}

The color of the text is controlled with the fill() function. You can also use the stroke() function to set the color of the text’s outline.

The default size of the font is 12 pixels. You can control the size of the font with the textSize() function. Here’s an example that shows all of this in action. (Hold down the mouse button to turn the stroke on.)

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  strokeWeight(2);
  fill(128 + sin(frameCount*0.1) * 128);
  if (mouseIsPressed) {
    stroke(255);
  }
  else {
    noStroke();
  }
  textSize(12 + (mouseX / width)*72);
  text("Attention, please.", 50, 200);
}

You can change the text inside of the quotes (") to change the text that the sketch will display. Try it out!

Text alignment

By default, the coordinate that you specify as the second and third parameters of the text() function specify where the baseline of the text should begin: that is, the lower-left corner of the first character in the string. You can change this behavior with the textAlign() function

The textAlign() function, when called with a single parameter, controls the horizontal alignment of the text, meaning where the text will br drawn horizontally in relation to the coordinates you specify with the text() function. The following sketch demonstrates the three possible values for horizontal alignment. (The green circles are drawn at the same coordinates as those in the text() function calls, as a visual reminder.)

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
  noLoop();
}
function draw() {
  background(50);
  textSize(32);
  
  // left
  fill(0, 255, 0);
  ellipse(200, 100, 10, 10);
  fill(255);
  textAlign(LEFT);
  text("Tremendous!", 200, 100);
  
  // center
  fill(0, 255, 0);
  ellipse(200, 200, 10, 10);
  fill(255);
  textAlign(CENTER);
  text("Tremendous!", 200, 200);

  // center
  fill(0, 255, 0);
  ellipse(200, 300, 10, 10);
  fill(255);
  textAlign(RIGHT);
  text("Tremendous!", 200, 300);
}

If you pass a second parameter to textAlign(), it controls the vertical alignment of the text, meaning where the text will be drawn vertically in relation to the coordinates you specify. As mentioned above, the default is to use the text’s baseline. Other values include TOP (upper left-hand corner of the text), BOTTOM (lower left-hand corner, including any descenders), and CENTER (the middle of the text.) The following sketch demonstrates:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
  noLoop();
}
function draw() {
  background(50);
  textSize(32);
  
  // baseline (default)
  fill(0, 255, 0);
  ellipse(200, 100, 10, 10);
  fill(255);
  textAlign(CENTER, BASELINE);
  text("Stupendous!", 200, 100);
  
  // bottom
  fill(0, 255, 0);
  ellipse(200, 166, 10, 10);
  fill(255);
  textAlign(CENTER, BOTTOM);
  text("Stupendous!", 200, 166);

  // center
  fill(0, 255, 0);
  ellipse(200, 233, 10, 10);
  fill(255);
  textAlign(CENTER, CENTER);
  text("Stupendous!", 200, 233);

  // top
  fill(0, 255, 0);
  ellipse(200, 300, 10, 10);
  fill(255);
  textAlign(CENTER, TOP);
  text("Stupendous!", 200, 300);
}

(The difference between BASELINE and BOTTOM is subtle, but important!)

Text… in a box

By default, the text you specify in a call to text() is drawn in one long line. If you want the text to wrap instead, you can include two extra parameters to the text(). This will cause p5.js to draw the text inside the rectangle specified by the four coordinates. For example, in the example below, the text is displayed within a 200x200 box drawn at the current mouse position. (Hold down the mouse button to see a visual representation of the box.)

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  fill(255);
  textSize(32);
  text("It was the best of times. Such good times.",
    mouseX, mouseY, 200, 200);
  if (mouseIsPressed) {
    noFill();
    stroke(0, 255, 0);
    rect(mouseX, mouseY, 200, 200);
  }
}

When you’re displaying text that is being automatically wrapped (or text that has newline characters in it; see below), you can change the line spacing of the text with the textLeading() function, which sets the number of pixels to display between each line:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  fill(255);
  textSize(32);
  textLeading((mouseX / width) * 64);
  text("It was the best of times. Such good times.",
    100, 100, 200, 200);
}

With a value of zero, there’s no spacing between the lines (i.e., the lines are drawn on top of one another).

Custom fonts

The default font in p5.js is pretty boring. You can specify a different font using the loadFont() function, like so:

► run sketch ◼ stop sketch
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  strokeWeight(2);
  fill(255);
  textFont("Comic Sans MS");
  textSize(12 + (mouseX / width)*72);
  text("Attention, please.", 50, 200);
}

When you call the textFont() like this, the font with the specified name must exist on the computer of the person using your sketch. This is probably fine for a few common fonts, but if you want to guarantee that the user will see exactly what you intended, you should include the font you want to use with your sketch.

To do this, upload the .ttf or .otf file for the font, and load it in preload() with the loadFont() function. This sketch uses the Knewave font downloaded from The League of Moveable Type.

► run sketch ◼ stop sketch
let myFont;
function preload() {
  myFont = loadFont("knewave.otf");
}
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  strokeWeight(2);
  fill(255);
  textFont(myFont);
  textSize(12 + (mouseX / width)*72);
  text("Attention, please.", 50, 200);
}

As you can see, the loadFont() takes either a string as a parameter, or a font value (as returned from the loadFont() function).

Note that font licensing is complicated, and just because you have a font on your computer doesn’t mean that you have a legal right to use it for any purpose. (Awful and unreasonable, I know.) For freely usable fonts, you might try the League of Moveable Type (mentioned above), or Open Font Library.

Strings

This is all well and good, but what exactly are these quote marks? What do they mean? Where do they come from. Should we be afraid? I can answer some of these questions for you.

In Javascript, there’s a special data type called a string. A string is kind of like an array, except instead of storing a sequence of values, it stores a sequence (or “string”) of characters. Values of the string type have special methods associated with them that allow you to get smaller parts of the string (“substrings”), for making bigger strings out of multiple smaller strings, for breaking strings up into arrays of tokens (like words) and for locating the indices of a given substring. (Among many other things!)

To create a string value, enclose a sequence of characters inside of single- or double-quotes. You can then assign this value to a variable. (If you don’t put anything between the quotes, you’ve made an empty string, which can be useful if you’re planning on building the string up gradually as your program runs.)

let someText = "this is a test";
let someMoreText = 'This is a test'; // single quotes are OK too!
let oneChar = "a"; // string with a single character
let nothingYet = ""; // empty string

Just as with an array, you can get the length of a string (i.e., the number of characters in it) with its .length property:

let someText = "this is a test";
console.log(someText.length); // prints 14

Substrings

As mentioned above, string values have a variety of helpful methods. Let’s start with the .substring() method. If you call this method with two parameters, it returns the portion of the string starting at the index given by the first parameter and ending at (but not including) the index given by the second parameter. (Like array indices, string indices are zero-based.) When called with a single parameter, this method returns the portion of the string starting with the given index up to the end of the string. Try it out by running this code in an empty p5.js sketch:

let someText = "it was the best of times";
console.log(someText.substring(0, 1)); // prints "i"
console.log(someText.substring(7, 15)); // prints "the best"
console.log(someText.substring(19)); // prints "times"

Let’s use the .substring() method to write a sketch that goes through a string and displays it to the screen one character at a time:

► run sketch ◼ stop sketch
let sourceText = "Life is short and art long";
let curIndex = 0;
function setup() {
  createCanvas(400, 400);
  frameRate(10);
}
function draw() {
  background(50);
  fill(255);
  textSize(144);
  textAlign(CENTER, CENTER);
  text(
    sourceText.substring(curIndex, curIndex+1),
    width/2, height/2);
  curIndex++;
  if (curIndex > sourceText.length) {
    curIndex = 0;
  }
}

A sketch to display a portion of the string, based on the mouse position:

► run sketch ◼ stop sketch
let sourceText = "Life is short and art long";
function setup() {
  createCanvas(400, 400);
  frameRate(10);
}
function draw() {
  background(50);
  fill(255);
  textSize(32);
  textAlign(CENTER, CENTER);
  let middle = sourceText.length / 2;
  let left = middle - ((mouseX / width) * middle);
  let right = middle + ((mouseX / width) * middle);
  text(
    sourceText.substring(left, right+1),
    width/2, height/2);
}

EXERCISE: Make a sketch that draws every character from a string at a random position on the screen. (Try using a for loop!)

Appending and combining strings

Say you have two strings from different sources and you want to combine them into a single string. In Javascript, the easiest way to do this is with the + operator. If the thing on the left and the thing on the right are both strings, you end up with a combination of the two. Example:

let beginning = "it was the raddest of times, ";
let ending = "it was the baddest of times";
// prints "it was the raddest of times, it was the baddest of times"
console.log(beginning + ending);

To demonstrate this fun fact about strings, we’ll use Processing’s built-in key variable. We previously discussed the key variable when I briefly showed you how to use the keyTyped() function to react to user input. The key variable contains the character the user most recently typed, and it’s just a regular string value that you can use like any other string. In the following example, the sketch begins with an empty string. Each time the user types a key, the key they typed is appended to the end of the empty string. In draw(), the string is displayed. It’s a very simple text entry box!

► run sketch ◼ stop sketch
let contents = "";
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  fill(255);
  textSize(24);
  text(contents, 50, 50, 300, 300);
}
function keyTyped() {
  contents += key;
}

Parsing strings into arrays

The examples we’ve worked with so far have been acting on individual characters of strings. In English, we might find ourselves wanting occasionally to work with text using a little-known unit called the “word.” Parsing arbitrary text into words isn’t an easy task, but there is an easy approximation, which I intend to show you here. In order to accomplish this, we’re going to use the string value’s .split() method.

The .split() method returns an array composed of parts of the original string. It takes a single parameter, a separator, which determines what the boundary for those parts will be. For example:

let commaSeparated = "parrish,allison,college professor,surly";
let parts = commaSeparated.split(",");
console.log(parts[2]); // displays "college professor"

If you use " " as the separator (i.e., a single space character), the resulting array will (roughly) have one element per word found in the original string:

let someText = "it was the best of times";
let words = someText.split(" "); // evaluates to an array of strings
for (let i = 0; i < words.length; i++) {
    console.log(words[i]);
}

The following sketch is an example of the .split() method used in this manner. It takes the string and splits it up into words, and displays those random words spread around the sketch at random:

► run sketch ◼ stop sketch
let sourceText = "Life is short and art long";
let words = sourceText.split(" ");
function setup() {
  createCanvas(400, 400);
  noLoop();
}
function draw() {
  background(50);
  textSize(72);
  textAlign(CENTER, CENTER);
  for (let i = 0; i < words.length; i++) {
    fill(random(255));
    text(words[i], random(width), random(height));
  }
}

EXERCISE: Adapt the “one character per frame” example sketch above such that it displays one word per frame instead. Use the .split() method.

Escape notation

You’ll occasionally find yourself in a situation where you need to include an unusual character in a string. For example, if you’re using single quotes, you can’t include an actual single quote (or apostrophe) inside of the string; Javascript interprets it as an attempt to close the string, and it causes a syntax error:

let someText = 'the emperor's new clothes'; // syntax error!

In this case, you could just use double quotes to quote the string instead of single quotes. But then sometimes you have a string that contains both single and double quotes, like:

// still a syntax error!
let someText = '"Which clothes?" he asked. "The emperor's new, natch!"';

In order to tell Javascript that you mean a quote character not as a part of your Javascript code, but as a literal part of the string, you can use an escape character. An escape character is a character preceded by a backslash (\), which has a special meaning. For example, the escape character for a single quote is \':

// at last: not a syntax error
let someText = '"Which clothes?" he asked. "The emperor\'s new, natch!"';

Likewise, you can include a double quote character inside of a double-quoted string with the \" escape character:

let someText = "She frowned. \"I don't know what you mean.\"";

Another common escape character is \n, which tells Javascript to put a line break in the string:

► run sketch ◼ stop sketch
let hd = "more precious\nthan a wet rose\nsingle on a stem";
function setup() {
  createCanvas(400, 400);
  noLoop();
}
function draw() {
  background(50);
  fill(255);
  textSize(32);
  text(hd, 50, 80);
}

The Mozilla Developer Network has a full list of escape characters.

Loading external text

There isn’t really an easy way to include a lot of text in your p5.js sketch’s code. Instead, you might choose to use text stored in an external file, and then load it into your sketch at run time. Processing has a function called loadStrings() to facilitate this. The loadStrings() function loads in a text file and puts its content into an array of strings, with one array item per line in the source file.

The loadStrings() function only works with plain text files (i.e., you can’t load in, e.g., Word documents or HTML files). (See here for a more in-depth discussion of plain text.)

For the next few examples, I’ll be using the text of H.D.’s Sea Rose. To use this text file in your sketch, download the file and copy it to your sketch folder (just like you did for image and sound files). As with loadImage() and loadFont(), the loadStrings() function must be called from within preload().

Here’s a simple example that loads in the entire file and displays it to the screen, one line at a time:

► run sketch ◼ stop sketch
let seaRoseLines;
function preload() {
  seaRoseLines = loadStrings('sea_rose.txt');
}
function setup() {
  createCanvas(400, 400);
  noLoop();
}
function draw() {
  background(50);
  textSize(16);
  for (let i = 0; i < seaRoseLines.length; i++) {
    fill(128+(i*10));
    text(seaRoseLines[i], 50, 50+i*20);
  }
}

In this sketch, I use the built-in shuffle() function to reorder the array containing the lines at random:

► run sketch ◼ stop sketch
let seaRoseLines;
function preload() {
  seaRoseLines = loadStrings('sea_rose.txt');
}
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  textSize(16);
  for (let i = 0; i < seaRoseLines.length; i++) {
    fill(128+(i*10));
    text(seaRoseLines[i], 50, 50+i*20);
  }
}
function mousePressed() {
  shuffle(seaRoseLines, true);
}

Text metrics

When working with most fonts, it’s difficult to tell how many pixels wide a particular string will be when displayed to the screen. Processing provides a textWidth() function to help out with this. The textWidth() function takes a string as a parameter, and returns the width in pixels for that string, given the current font settings.

The following sketch uses textWidth() to display the text of Sea Rose. But instead of showing the actual word, it shows a rectangle whose width is equal to the size of that word. (Hold the mouse button to display the word as well.)

► run sketch ◼ stop sketch
let seaRoseLines;
function preload() {
  seaRoseLines = loadStrings('sea_rose.txt');
}
function setup() {
  createCanvas(400, 400);
}
function draw() {
  background(50);
  textSize(16);
  noStroke();
  textAlign(LEFT, TOP);
  for (let i = 0; i < seaRoseLines.length; i++) {
    let words = seaRoseLines[i].split(" ");
    let currentOffset = 0;
    for (let j = 0; j < words.length; j++) {
      let wordWidth = textWidth(words[j]);
      fill(128+(i*10));
      rect(25+currentOffset, 25+i*20,
        wordWidth, 16);
      if (mouseIsPressed) {
        fill(0);
        text(words[j], 25+currentOffset, 25+i*20);
      }
      // four pixels between words
      currentOffset += wordWidth + 4; 
    }
  }
}

Further examples TK!

Further reading