Chapter 3. The HTML5 Canvas Text API

The HTML5 Canvas Text API allows developers to render text on an HTML page in ways that were either tricky or next to impossible before its invention.

We are providing an in-depth analysis of the HTML5 Canvas Text API because it is one of the most basic ways to interact with the canvas. However, that does not mean it was the first Canvas API feature developed. In fact, for many browsers, it was one of the last parts implemented.

There was a time in the recent past when HTML5 Canvas Text API support in browsers was spotty at best. Back then, using modernizr.js to test for text support would have been a good idea. However, at this historic moment, all modern browser versions (besides IE) support the HTML5 Canvas Text API in some way.

This chapter will create an application named “Text Arranger” to demonstrate the features and interdependencies of the HTML5 Canvas Text API. This application will display a single line of text in an almost infinite number of ways. This is also a useful tool to see whether support for text is common among web browsers. Later in this chapter you will see that some text features are incompatible when drawn on the canvas at the same time.

Displaying Basic Text

Displaying text on HTML5 Canvas is simple. In fact, we covered the very basics in Chapter 1. First, we will review these basics, and then we will show you how to make them work with the Text Arranger application.

Basic Text Display

The simplest way to define text to be displayed on the canvas is to set the context.font style using standard values for CSS font style attributes: font-style, font-weight, font-size, and font-face.

We will discuss each of these attributes in detail in the upcoming section Setting the Text Font. All you need to know now is that a font designation of some type is required. Here is a simple example of setting a 50-point serif font:

context.font = "50px serif";

You also need to set the color of the text. For filled text, you would use the context.fillStyle attribute and set it using a standard CSS color, or with a CanvasGradient or CanvasPattern object. We will discuss the latter two options later in the chapter.

Finally, you call the context.fillText() method, passing the text to be displayed and the x and y positions of the text on the canvas.

Below is an example of all three basic lines of code required to display filled text on HTML5 Canvas:

context.font = "50px serif"
context.fillStyle = "#FF0000";
context.fillText ("Hello World", 100, 80);

If you do not specify a font, the default 10px sans-serif will be used automatically.

Handling Basic Text in Text Arranger

For Text Arranger, we are going to allow the user to set the text displayed by the call to context.fillText(). To do this, we will create a variable named message where we will store the user-supplied text. We will later use that variable in our call to context.fillText(), inside the standard drawScreen() method that we introduced in Chapter 1 and will continue to use throughout this book:

var message = "your text";
...

function drawScreen() {
  ...
  context.fillStyle = "#FF0000";
  context.fillText  (message, 100, 80);
}

To change the text displayed on the canvas to the text entered by the user, we need to create an event handler for the text box keyup event. This means that whenever someone changes text in the box, the event handler function will be called.

To make this work, we are going to name our text box in our HTML <form> using an <input> form element. Notice that the id is set to the value textBox. Also notice that we have set the placeholder="" attribute. This attribute is new to HTML5, so it might not work in every browser. You can also substitute it with the value="" attribute, which will not affect the execution of this application:

<form>
  Text: <input id="textBox" placeholder="your text"/>
  <br>
</form>

Communicating Between HTML Forms and the Canvas

Back in our JavaScript code, we need to create an event handler for the keyup event of textBox. We do this by finding the form element using the document.getElementById() function of the DOM document object, and storing it in the formElement variable. Then we call the addEventListener() method of formElement, setting the event to keyup and the event handler to the function textBoxChanged, which we have yet to define:

var formElement = document.getElementById("textBox");
formElement.addEventListener('keyup', textBoxChanged, false);

The final piece of the puzzle is to define the textBoxChanged() event handler. This function works like the event handlers we created in Chapter 1. It is passed one parameter when it is called, an event object that we universally name e because it’s easy to remember.

The event object contains a property named target that holds a reference to the HTML form element that created the change event. In turn, the target contains a property named value that holds the newly changed value of the form element that caused the event to occur (i.e., textBox). We retrieve this value, and store it in the message variable we created in JavaScript. It is the very same message variable we use inside the drawScreen() method to paint the canvas. Now, all we have to do is call drawScreen(), and the new value of message will appear “automagically” on the canvas:

function textBoxChanged(e) {
      var target = e.target;
      message = target.value;
      drawScreen();
   }

We just spent a lot of time describing how we will handle changes in HTML form controls with event handlers in JavaScript, and then display the results on an HTML5 Canvas. We will repeat this type of code several more times while creating Text Arranger. However, we will refrain from explaining it in depth again, instead focusing on different ways to render and capture form data and use it with Canvas.

Using measureText

The HTML5 Canvas context object includes a useful method, measureText(). When supplied with a text string, it will return some properties about that text based on the current context settings (font face, size, etc.) in the form of a TextMetrics object. Right now the TextMetrics object has only a single property: width. The width property of a TextMetrics object gives you the exact width in pixels of the text when rendered on the canvas. This can be very useful when attempting to center text.

Centering text using width

For the Text Arranger application, we will use the TextMetrics object to center the text the user has entered in the textBox form control on the canvas. First, we retrieve an instance of TextMetrics by passing the message variable (which holds the text we are going to display) to the measureText() method of the 2D context, and storing it in a variable named metrics:

var metrics = context.measureText(message);

Then, from the width property of metrics, we get the width of the text in pixels and store it in a variable named textWidth:

var textWidth = metrics.width;

Next, we calculate the center of the screen by taking the width of the canvas and dividing it in half (theCanvas.width/2). From that, we subtract half the width of the text (textWidth/2). We do this because text on the canvas is vertically aligned to the left when it is displayed without any alignment designation (more on this a bit later). So, to center the text, we need to move it half its own width to the left, and place the center of the text in the absolute center of the canvas. We will update this in the next section when we allow the user to select the text’s vertical alignment:

var xPosition = (theCanvas.width/2) - (textWidth/2);

What about the height of the text?

So, what about finding the height of the text so you can break text that is longer than the width of the canvas into multiple lines, or center it on the screen? Well, this poses a problem. The TextMetrics object does not contain a height property. The text font size does not give the full picture either, as it does not take into account font glyphs that drop below the baseline of the font. While the font size will help you estimate how to center a font vertically on the screen, it does not offer much if you need to break text onto two or more lines. This is because the spacing would also need to be taken into account, which could be very tricky.

For our demonstration, instead of trying to use the font size to vertically center the text on the canvas, we will create the yPosition variable for the text by simply placing it at one-half the height of the canvas. The default baseline for a font is middle, so this works great for centering on the screen. We will talk more about baseline in the next section:

var yPosition = (theCanvas.height/2);

Note

In the chat example in Chapter 11, we will show you an example of breaking up text onto multiple lines.

fillText and strokeText

The context.fillText() function (as shown in Figure 3-1) will render solid colored text to the canvas. The color used is set in the context.fillColor property. The font used is set in the context.font property. The function call looks like this:

fillText([text],[x],[y],[maxWidth]);

where:

text

The text to render on the canvas.

x

The x position of the text on the canvas.

y

The y position of the text on the canvas.

maxWidth

The maximum width of the text as rendered on the canvas. At the time of this writing, support for this property was just being added to browsers.

fillText in action
Figure 3-1. fillText in action

The context.strokeText() function (as shown in Figure 3-2) is similar, but it specifies the outline of text strokes to the canvas. The color used to render the stroke is set in the context.strokeColor property; the font used is set in the context.font property. The function call looks like:

strokeText([text],[x],[y],[maxWidth])

where:

text

The text to render on the canvas.

x

The x position of the text on the canvas.

y

The y position of the text on the canvas.

maxWidth

The maximum width of the text as rendered on the canvas. At the time of this writing, this property does not appear to be implemented in any browsers.

strokeText setting outline properties
Figure 3-2. strokeText setting outline properties

The next iteration of Text Arranger adds the ability for the user to select fillText, strokeText, or both. Selecting both will give the fillText text a black border (the strokeText). In the HTML <form>, we will add a <select> box with the id fillOrStroke, which will allow the user to make the selections:

Fill Or Stroke:
<select id = "fillOrStroke">
  <option value = "fill">fill</option>
  <option value = "stroke">stroke</option>
   <option value = "both">both</option>
</select>

In the canvasApp() function, we will define a variable named fillOrStroke that we will use to hold the value selected by the user on the HTML <form>. The default value will be fill, which means Text Arranger will always show fillText first:

var fillOrStroke = "fill";

We will also create the event listener for a change in the fillOrStroke form element…

formElement = document.getElementById("fillOrStroke");
formElement.addEventListener('change', fillOrStrokeChanged, false);

…and create the function fillOrStrokeChanged() to handle the event:

function fillOrStrokeChanged(e) {
      var target = e.target;
      fillOrStroke = target.value;
      drawScreen();
   }

In the drawScreen() function, we test the fillOrStroke variable to see whether it contains the value fill. Since we have three states (fill, stroke, or both), we use a switch statement to handle the choices. If the choice is both, we set the strokeStyle to black (#000000) as the highlight for the colored fillText.

If we use the xPosition and yPosition calculated using the width and height of the canvas, the message variable that contains the default or user-input text, and the fillOrStroke variable to determine how to render the text, we can display the text as configured by the user in drawScreen():

var metrics = context.measureText(message);
var textWidth = metrics.width;
var xPosition = (theCanvas.width/2) - (textWidth/2);
var yPosition = (theCanvas.height/2);

switch(fillOrStroke) {
   case "fill":
      context.fillStyle = "#FF0000";
      context.fillText  (message,  xPosition,yPosition);
      break;
   case "stroke":
      context.strokeStyle = "#FF0000";
      context.strokeText  (message, xPosition,yPosition);
      break;
   case "both":
      context.fillStyle = "#FF0000";
      context.fillText  (message, xPosition,yPosition);
      context.strokeStyle = "#000000";
         context.strokeText (message, xPosition,yPosition);
         break;
      }

Example 3-1 shows the full code for Text Arranger. Test it out to see how the user controls in HTML affect the canvas. There are not many ways to change the text here, but you can see the difference between fillText and strokeText. In the next section, we will update this application to configure and render the text in multiple ways.

Example 3-1. Text Arranger 1.0
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH3EX1: Text Arranger Version 1.0</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript">

window.addEventListener("load", eventWindowLoaded, false);
function eventWindowLoaded() {

   canvasApp();
}

function canvasSupport () {
    return Modernizr.canvas;
}

function eventWindowLoaded() {

   canvasApp();
}

function canvasApp() {

   var message = "your text";
   var fillOrStroke ="fill";

   if (!canvasSupport()) {
          return;
        }

   var theCanvas = document.getElementById("canvasOne");
   var context = theCanvas.getContext("2d");

   var formElement = document.getElementById("textBox");
   formElement.addEventListener("keyup", textBoxChanged, false);

   formElement = document.getElementById("fillOrStroke");
   formElement.addEventListener("change", fillOrStrokeChanged, false);

   drawScreen();

   function drawScreen() {
      
      //Background
      context.fillStyle = "#ffffaa";
      context.fillRect(0, 0, theCanvas.width, theCanvas.height);
      
      //Box
      context.strokeStyle = "#000000";
      context.strokeRect(5,  5, theCanvas.width−10, theCanvas.height−10);

      //Text
      context.font = "50px serif"

      var metrics = context.measureText(message);
      var textWidth = metrics.width;
      var xPosition = (theCanvas.width/2) - (textWidth/2);
      var yPosition = (theCanvas.height/2);

      switch(fillOrStroke) {
         case "fill":
            context.fillStyle = "#FF0000";
                context.fillText (message, xPosition,yPosition);
            break;
         case "stroke":
            context.strokeStyle = "#FF0000";
            context.strokeText (message, xPosition,yPosition);
            break;
         case "both":
            context.fillStyle = "#FF0000";
                context.fillText (message, xPosition ,yPosition);
            context.strokeStyle = "#000000";
            context.strokeText (message, xPosition,yPosition);
            break;
      }

   }

   function textBoxChanged(e) {
      var target = e.target;
      message = target.value;
      drawScreen();
   }

   function fillOrStrokeChanged(e) {
      var target = e.target;
      fillOrStroke = target.value;
      drawScreen();
   }

}

</script>
</head>
<body>
<div style="position: absolute; top: 50px; left: 50px;">
<canvas id="canvasOne" width="500" height="300">
 Your browser does not support HTML5 Canvas.
</canvas>

<form>

  Text: <input id="textBox" placeholder="your text" />
  <br>

  Fill Or Stroke :
  <select id="fillOrStroke">
  <option value="fill">fill</option>
  <option value="stroke">stroke</option>
  <option value="both">both</option>
  </select>
  <br>

</form>

</div>
</body>
</html>

Setting the Text Font

Now that we have placed text on the canvas, it’s time to explore some of the basics of setting the context.font property. As you will see, specifying the font for displaying basic text on Canvas is really no different from doing the same thing in HTML and CSS.

Font Size, Face Weight, and Style Basics

It is very easy to style text that will be rendered on the canvas. It requires you to set the size, weight, style, and font face in a CSS-compliant text string that is applied to the context.font property. The basic format looks like this:

[font style] [font weight] [font size] [font face]

An example might be:

context.font = "italic bold 24px serif";

or:

context.font = "normal lighter 50px cursive";

Once the context.font property is set, it will apply to all text that is rendered afterward—until the context.font is set to another CSS-compliant string.

Handling Font Size and Face in Text Arranger

In Text Arranger, we have implemented only a subset of the available font options for displaying text. We have chosen these to make the application work in as many browsers as possible. Here is a short rundown of the options we will implement.

Available font styles

CSS defines the valid font styles as:

normal | italic | oblique | inherit

In Text Arranger, we have implemented all but inherit.

Here is the markup we used to create the font style <select> box in HTML. We made the id of the form control equal to fontStyle. We will use this id when we listen for a change event, which is dispatched when the user updates the value of this control. We will do this for all the controls in this version of Text Arranger:

<select id="fontStyle">
 <option value="normal">normal</option>
 <option value="italic">italic</option>
 <option value="oblique">oblique</option>
</select>

Available font weights

CSS defines the valid font weights as:

normal | bold | bolder | lighter | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | inherit | auto

We have used only normal, bold, bolder, and lighter in Text Arranger. You can add the other values as you see fit.

Here is the markup we used to create the font weight <select> box in HTML:

<select id="fontWeight">
 <option value="normal">normal</option>
 <option value="bold">bold</option>
 <option value="bolder">bolder</option>
 <option value="lighter">lighter</option>
</select>

Generic font faces

Because we cannot be sure which font will be available in the browser at any time, we have limited the font face choices in Text Arranger to those that are defined as “generic” in the CSS specification: serif, sans-serif, cursive, fantasy, and monospace.

Here is the markup we used to create the font face <select> box in HTML:

<select id="textFont">
 <option value="serif">serif</option>
 <option value="sans-serif">sans-serif</option>
 <option value="cursive">cursive</option>
 <option value="fantasy">fantasy</option>
 <option value="monospace">monospace</option>
</select>

Font size and HTML5 range control

To specify the size of the font, we have implemented the new HTML5 range form control. range is an <input> type that creates a slider on the HTML page to limit the numerical input to that specified in the range. A range is created by specifying range as the type of a form input control. range has four properties that can be set:

min

The minimum value in the range

max

The maximum value in the range

step

The number of units to step when the range slider is moved

value

The default value of the range

Here is the markup we used to specify the range in the Text Arranger HTML:

<input type="range" id="textSize"
 min="0"
 max="200"
 step="1"
 value="50"/>

If the browser does not support this range control, it will be rendered as a text box.

Note

At the time of this writing, range did not render in Firefox.

Creating the necessary variables in the canvasApp() function

In the canvasApp() container function, we need to create four variables—fontSize, fontFace, fontWeight, and fontStyle—that will hold the values set by the HTML form controls for Text Arranger. We create a default value for each so that the canvas can render text the first time the drawScreen() function is called. After that, drawScreen() will be called only when a change event is handled by one of the event handler functions we will create for each form control:

var fontSize = "50";
var fontFace = "serif";
var fontWeight = "normal";
var fontStyle = "normal";

Setting event handlers in canvasApp()

Just like we did in version 1.0 of Text Arranger, we need to create event listeners and the associated event handler functions so changes on the HTML page form controls can interact with HTML5 Canvas. All of the event listeners below listen for a change event on the form control:

formElement = document.getElementById("textSize");
formElement.addEventListener('change', textSizeChanged, false);

formElement = document.getElementById("textFont");
formElement.addEventListener('change', textFontChanged, false);

formElement = document.getElementById("fontWeight");
formElement.addEventListener('change', fontWeightChanged, false);

formElement = document.getElementById("fontStyle");
formElement.addEventListener('change', fontStyleChanged, false);

Defining event handler functions in canvasApp()

Below are the event handlers we need to create for each form control. Notice that each handler updates the variable associated with part of the valid CSS font string, and then calls drawScreen() so the new text can be painted onto the canvas:

function textSizeChanged(e) {
   var target = e.target;
   fontSize = target.value;
   drawScreen();
}

function textFontChanged(e) {
   var target = e.target;
   fontFace = target.value;
   drawScreen();
}

function fontWeightChanged(e) {
   var target = e.target;
   fontWeight = target.value;
   drawScreen();
}

function fontStyleChanged(e) {
   var target = e.target;
   fontStyle = target.value;
   drawScreen();
}

Setting the font in the drawScreen() function

Finally, in the drawScreen() function, we put all of this together to create a valid CSS font string that we apply to the context.font property:

context.font = fontWeight + " " + fontStyle + " " + fontSize + "px " + fontFace;

Figures 3-3 and 3-4 show the results.

Setting the font size and face
Figure 3-3. Setting the font size and face
Setting the font as bold and italic
Figure 3-4. Setting the font as bold and italic

Font Color

Setting the font color for text rendered on HTML5 Canvas is as simple as setting the context.fillStyle or context.strokeStyle property to a valid CSS RGB color. Use the format “#RRGGBB”, where RR is the red component hexadecimal value, GG is the green component hexadecimal value, and BB is the blue component hexadecimal value. Here are some examples:

context.fillStyle = "#FF0000";

Sets the text fill to red.

context.strokeStyle = "#FF00FF";

Sets the text stroke to purple.

context.fillStyle = "#FFFF00";

Sets the text fill to yellow.

Handling font color with JSColor

For Text Arranger, we will allow the user to select the text color. We could have made this a drop-down or a text box, but instead, we want to use the new HTML5 <input> type of color. This handy new form control works directly in the web browser, allowing users to visually choose a color from a beautifully designed color picker. At the time of this writing, only Opera has implemented the color <input> object of the HTML5 specification.

However, since we could really use a nice color picker for Text Arranger, we will implement a third-party color picker, JSColor (http://jscolor.com/). The jsColor control creates a nice color picker in JavaScript (see Figure 3-5), similar to the one that will someday grace browsers supporting HTML5.

To implement jsColor and the color picker for Text Arranger, first download the jscolor.js library and put it in the same folder as Text Arranger. Then, add this line of code in the <head> to include jsColor in the HTML page:

<script type="text/javascript" src="jscolor/jscolor.js"></script>

Then add a new <input> element to the ever-growing HTML <form> on the Text Arranger HTML page, and give it the CSS class designation color:

<input class="color" id="textFillColor" value="FF0000"/>

When you pick a color with jsColor, it creates a text value that looks like “FF0000”, representing the color value chosen. However, we already know that we need to append the pound (#) sign to the front of that value to work with HTML5 Canvas. The textFillColorChanged event handler does this by appending “#” to the value of the textFillColor form control:

function textFillColorChanged(e) {
      var target = e.target;
      textFillColor = "#" + target.value;
      drawScreen();
   }

Oh yes, and let’s not forget the event listener we must create so that we can direct and “change” events from the textFillColor <input> element to the textFillColorChanged() event handler:

formElement = document.getElementById("textFillColor");
formElement.addEventListener('change', textFillColorChanged, false);

Finally, in the canvasApp() function, we need to create the textFillColor variable:

var textFillColor = "#ff0000";

We do this so that the variable can be updated by the aforementioned event handler, and then implemented when that event handler calls the drawScreen() function:

switch(fillOrStroke) {
   case "fill":
      context.fillStyle = textFillColor;
      context.fillText (message, xPosition,yPosition);
      break;
   case "stroke":
      context.strokeStyle = textFillColor;
      context.strokeText (message, xPosition,yPosition);
      break;
   case "both":
      context.fillStyle = textFillColor;
      context.fillText (message, xPosition ,yPosition);
      context.strokeStyle = "#000000";
      context.strokeText (message, xPosition,yPosition);
      break;
 }

Notice that we needed to update the switch() statement created for Text Arranger version 1.0 so that it used textFillColor instead of hardcoded values. However, when both a stroke and a fill are chosen, we still render the stroke as black (“#000000”). We could have added an additional color picker for the strokeColor, but that is something you can do if you want to start expanding the application. Figure 3-5 illustrates what it looks like now.

Setting the font color
Figure 3-5. Setting the font color

Font Baseline and Alignment

You have options to align text on HTML5 Canvas both vertically and horizontally. These alignments affect the text in relation to Canvas itself, but only to the invisible bounding box that would surround the text’s topmost, bottommost, rightmost, and leftmost sides. This is an important distinction because it means these alignments affect the text in ways that might be unfamiliar to you.

Vertical alignment

The font baseline is the vertical alignment of the font glyphs based on predefined horizontal locations in a font’s em square (the grid used to design font outlines) in relation to font descenders. Basically, font glyphs, like lowercase p and y that traditionally extend “below the line,” have descenders. The baseline tells the canvas where to render the font based on how those descenders relate to other glyphs in the font face.

The HTML5 Canvas API online has a neat graphic that attempts to explain baseline. We could copy it here, but in reality, we think it’s easier to understand by doing, which is one of the main reasons we wrote the Text Arranger application.

The options for the context.textBaseline property are:

top

The top of the text em square and the top of the highest glyph in the font face. Selecting this baseline will push the text the farthest down (highest y position) the canvas of all the baselines.

hanging

This is a bit lower than the top baseline. It is the horizontal line from which many glyphs appear to “hang” from near the top of their face.

middle

The dead vertical center baseline. We will use middle to help us vertically center the text in Text Arranger.

alphabetic

The bottom of vertical writing script glyphs such as Arabic, Latin, and Hebrew.

ideographic

The bottom of horizontal writing script glyphs such as Han Ideographs, Katakana, Hiragana, and Hangul.

bottom

The bottom of the em square of the font glyphs. Choosing this baseline will push the font the farthest up (lowest y position) the canvas.

So, for example, if you want to place your text with a top baseline, you would use the following code:

context.textBaseline = "top";

All text displayed on the canvas afterward would have this baseline. To change the baseline, you would change the property:

context.textBaseline = "middle";

In reality, you will probably choose a single baseline for your app and stick with it, unless you are creating a word-processing or design application that requires more precise text handling.

Horizontal alignment

The context.textAlign property represents the horizontal alignment of the text based on its x position. These are the available textAlign values:

center

The dead horizontal center of the text. We can use this alignment to help center our text in Text Arranger.

start

Text is displayed directly after the text y position.

end

All text is displayed before the text y position.

left

Text is displayed starting with the y position of the text in the leftmost position (just like start).

right

Text is displayed with the y position in the rightmost position of the text (just like end).

For example, to set the text alignment to center, you would use the code:

context.textAlign = "center";

After this property is set, all text would be displayed with the y value of the text as the center point. However, this does not mean the text will be “centered” on the canvas. To do that, you need to find the center of the canvas, and use that location as the y value for the text position. We will do this in Text Arranger.

These values can also be modified by the dir attribute of the Canvas object (inherited from the DOM document object). dir changes the direction of how text is displayed; the valid values for dir are rtl (“right to left”) and ltr (“left to right”).

Handling text baseline and alignment

We are going to handle the text baseline and alignment much like we handled the other text properties in Text Arranger. First, we will add some variables to the canvasApp() function in which Text Arranger operates that will hold the alignment values. Notice that we have set the textAlign variable to center, helping us simplify centering the text on the canvas:

var textBaseline = "middle";
var textAlign = "center";

Next, we add the <select> form elements for each new attribute to the HTML portion of the page:

Text Baseline <select id="textBaseline">
  <option value="middle">middle</option>
  <option value="top">top</option>
  <option value="hanging">hanging</option>
  <option value="alphabetic">alphabetic</option>
  <option value="ideographic">ideographic</option>
  <option value="bottom">bottom</option>
  </select>
  <br>
  Text Align <select id="textAlign">
  <option value="center">center</option>
  <option value="start">start</option>
  <option value="end">end</option>
  <option value="left">left</option>
  <option value="right">right</option>

  </select>

We then add event listeners and event handler functions so we can connect the user interaction with the HTML form elements to the canvas display. We register the event listeners in the canvasApp() function:

formElement = document.getElementById("textBaseline");
formElement.addEventListener('change', textBaselineChanged, false);

formElement = document.getElementById("textAlign");
formElement.addEventListener('change', textAlignChanged, false);

Next, we need to create the event handler functions inside canvasApp():

function textBaselineChanged(e) {
   var target = e.target;
   textBaseline = target.value;
   drawScreen();
}

function textAlignChanged(e) {
   var target = e.target;
   textAlign = target.value;
   drawScreen();
}

We then apply the new values in the drawScreen() function:

context.textBaseline = textBaseline;
context.textAlign = textAlign;

Finally, we change the code that centers the text horizontally on the screen. Because we used the center alignment for context.textAlign, we no longer need to subtract half the width of the text that we retrieved through context.measureText() like we did previously in Text Arranger 1.0:

var metrics = context.measureText(message);
var textWidth = metrics.width;
var xPosition = (theCanvas.width/2) - (textWidth/2);

Instead, we can simply use the center point of the canvas:

var xPosition = (theCanvas.width/2);

Remember, center is only the default alignment for the text. Because you can change this with Text Arranger, the text can still be aligned in different ways while you are using the application.

Figure 3-6 shows how a font set to start alignment with a middle baseline might appear on the canvas.

Font with start alignment and middle baseline
Figure 3-6. Font with start alignment and middle baseline

Text Arranger Version 2.0

Now, try the new version of Text Arranger, shown in Example 3-2. You can see that we have added a ton of new options that did not exist in version 1.0. One of the most striking things is how fluidly the text grows and shrinks as the font size is updated. Now, imagine scripting the font size to create animations. How would you do that? Could you create an application to record the manipulations the user makes with Text Arranger, and then play them back in real time?

Also, notice how all the alignment options affect one another. Experiment with how changing the text direction affects the vertical alignment. Choose different font faces and see how they affect the baseline. Do you see how an application like Text Arranger can help you understand the complex relationships of all the text properties on HTML5 Canvas in an interactive and—dare we say—fun way?

Example 3-2. Text Arranger 2.0
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH3EX2: Text Arranger Version 2.0</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript" src="jscolor/jscolor.js"></script>
<script type="text/javascript">

window.addEventListener("load", eventWindowLoaded, false);
function eventWindowLoaded() {

   canvasApp();
}

function canvasSupport () {
     return Modernizr.canvas;
}

function eventWindowLoaded() {

   canvasApp();
}

function canvasApp() {

   var message = "your text";
   var fillOrStroke = "fill";

   var fontSize = "50";
   var fontFace = "serif";
   var textFillColor = "#ff0000";
   var textBaseline = "middle";
   var textAlign = "center";
   var fontWeight = "normal";
   var fontStyle = "normal";

   if (!canvasSupport()) {
          return;
        }

   var theCanvas = document.getElementById("canvasOne");
   var context = theCanvas.getContext("2d");

   var formElement = document.getElementById("textBox");
   formElement.addEventListener('keyup', textBoxChanged, false);

   formElement = document.getElementById("fillOrStroke");
   formElement.addEventListener('change', fillOrStrokeChanged, false);

   formElement = document.getElementById("textSize");
   formElement.addEventListener('change', textSizeChanged, false);

   formElement = document.getElementById("textFillColor");
   formElement.addEventListener('change', textFillColorChanged, false);

   formElement = document.getElementById("textFont");
   formElement.addEventListener('change', textFontChanged, false);

   formElement = document.getElementById("textBaseline");
   formElement.addEventListener('change', textBaselineChanged, false);

   formElement = document.getElementById("textAlign");
   formElement.addEventListener('change', textAlignChanged, false);

   formElement = document.getElementById("fontWeight");
   formElement.addEventListener('change', fontWeightChanged, false);

   formElement = document.getElementById("fontStyle");
   formElement.addEventListener('change', fontStyleChanged, false);

   drawScreen();

   function drawScreen() {
      
      //Background
      context.fillStyle = "#ffffaa";
      context.fillRect(0, 0, theCanvas.width, theCanvas.height);
      
      //Box
      context.strokeStyle = "#000000";
      context.strokeRect(5,  5, theCanvas.width−10, theCanvas.height−10);

      //Text
      context.textBaseline = textBaseline;
      context.textAlign = textAlign;
      context.font = fontWeight + " " + fontStyle + " " + fontSize + "px " + fontFace;

      var xPosition = (theCanvas.width/2);
      var yPosition = (theCanvas.height/2);

      switch(fillOrStroke) {
         case "fill":
            context.fillStyle = textFillColor;
                context.fillText (message, xPosition,yPosition);
            break;
         case "stroke":
            context.strokeStyle = textFillColor;
            context.strokeText (message, xPosition,yPosition);
            break;
         case "both":
            context.fillStyle = textFillColor;
                context.fillText (message, xPosition ,yPosition);
            context.strokeStyle = "#000000";
            context.strokeText (message, xPosition,yPosition);
            break;
      }

   }

   function textBoxChanged(e) {
      var target = e.target;
      message = target.value;
      drawScreen();
   }

   function fillOrStrokeChanged(e) {
      var target = e.target;
      fillOrStroke = target.value;
      drawScreen();
   }

   function textSizeChanged(e) {
      var target = e.target;
      fontSize = target.value;
      drawScreen();
   }

   function textFillColorChanged(e) {
      var target = e.target;
      textFillColor = "#" + target.value;
      drawScreen();
   }

   function textFontChanged(e) {
      var target = e.target;
      fontFace = target.value;
      drawScreen();
   }

   function textBaselineChanged(e) {
      var target = e.target;
      textBaseline = target.value;
      drawScreen();
   }

   function textAlignChanged(e) {
      var target = e.target;
      textAlign = target.value;
      drawScreen();
   }

   function fontWeightChanged(e) {
      var target = e.target;
      fontWeight = target.value;
      drawScreen();
   }

   function fontStyleChanged(e) {
      var target = e.target;
      fontStyle = target.value;
      drawScreen();
   }

}

</script>
</head>
<body>
<div style="position: absolute; top: 50px; left: 50px;">
<canvas id="canvasOne" width="500" height="300">
 Your browser does not support HTML5 Canvas.
</canvas>

<form>
  Text: <input id="textBox" placeholder="your text" />
  <br>

  Fill Or Stroke :
  <select id="fillOrStroke">
  <option value="fill">fill</option>
  <option value="stroke">stroke</option>
  <option value="both">both</option>
  </select>
  <br>
  Text Font: <select id="textFont">
  <option value="serif">serif</option>
  <option value="sans-serif">sans-serif</option>
  <option value="cursive">cursive</option>
  <option value="fantasy">fantasy</option>
  <option value="monospace">monospace</option>
  </select>
  <br>
  Text Size: <input type="range" id="textSize"
       min="0"
       max="200"
       step="1"
       value="50"/>
  <br>
  Text Color: <input class="color" id="textFillColor" value="FF0000"/>
  <br>
  Font Weight:
  <select id="fontWeight">
  <option value="normal">normal</option>
  <option value="bold">bold</option>
  <option value="bolder">bolder</option>
  <option value="lighter">lighter</option>
  </select>
  <br>
  Font Style:
  <select id="fontStyle">
  <option value="normal">normal</option>
  <option value="italic">italic</option>
  <option value="oblique">oblique</option>
  </select>
  <br>
  Text Baseline <select id="textBaseline">
  <option value="middle">middle</option>
  <option value="top">top</option>
  <option value="hanging">hanging</option>
  <option value="alphabetic">alphabetic</option>
  <option value="ideographic">ideographic</option>
  <option value="bottom">bottom</option>
  </select>
  <br>
  Text Align <select id="textAlign">
  <option value="center">center</option>
  <option value="start">start</option>
  <option value="end">end</option>
  <option value="left">left</option>
  <option value="right">right</option>

  </select>

</div>
</body>
</html>

Text and the Canvas Context

We’ve already discussed a couple Canvas context properties that affect the canvas in a global fashion: fillStyle and strokeStyle. However, there are two areas that visually demonstrate how changes to the properties of the context can affect the entire HTML5 Canvas: alpha transparencies and shadows.

Global Alpha and Text

Using alpha is a cool way to make objects seem to be partially or fully transparent on HTML5 Canvas. The globalAlpha property of the Canvas context is used for this purpose. After globalAlpha is applied, it affects all drawing on the canvas, so you need to be careful when setting it.

The valid values for context.globalAlpha are numbers between 0.0 (transparent) and 1.0 (opaque), and they act as a percentage for the alpha value. For example, a 50% alpha value would be coded like this:

context.globalAlpha = 0.5;

A 100% alpha (no transparency) would be coded like this:

context.globalAlpha = 1.0;

Handling globalAlpha transparencies

Besides the now-familiar elements that we included for most of the other configurable options in Text Arranger, the globalAlpha property requires us to think a bit more about when we use it and how it will affect the rest of the canvas.

First, we create a variable named textAlpha in the canvasApp() function and initialize it with 1, which means the text will have no transparency when it is first displayed:

var textAlpha = 1;

Next, in the drawImage() function, we need to set the globalAlpha property twice: once before we draw the background and the bounding box frame…

function drawScreen() {
      //Background

      context.globalAlpha = 1;

…and then again to the value stored in textAlpha, just before rendering the text to the canvas:

      context.globalAlpha = textAlpha;

This will reset globalAlpha so we can draw the background, but it will still allow us to use a configurable alpha value for the displayed text.

We will use another HTML5 range control in our form, but this time we set the value range with a min of 0.0 and a max of 1.0, stepping 0.01 every time the range is moved:

Alpha: <input type="range" id="textAlpha"
       min="0.0"
       max="1.0"
       step="0.01"
       value="1.0"/>

The textAlphaChanged() function works just like the other event handler functions we created in this chapter:

function textAlphaChanged(e) {
      var target = e.target;
      textAlpha = (target.value);
      drawScreen();
   }

Also, don’t forget the event listener for the textAlpha range control:

formElement = document.getElementById("textAlpha");
formElement.addEventListener('change', textAlphaChanged, false);

The results will look like Figure 3-7.

Text with globalAlpha applied
Figure 3-7. Text with globalAlpha applied

Global Shadows and Text

HTML5 Canvas includes a unique set of properties for creating a shadow for drawings. The context.shadow functions are not unique to text, but they can make some very good text effects with very little effort.

To create a shadowEffect, there are four properties of the Canvas context that need to be manipulated:

context.shadowColor

The color of the shadow. This uses the same “#RRGGBB” format of the fillStyle and strokeStyle properties.

context.shadowOffsetX

The x offset of shadow. This can be a positive or negative number.

context.shadowOffsetY

The y offset of shadow. This can be a positive or negative number.

context.shadowBlur

The blur filter diffusion of the shadow. The higher the number, the more diffusion.

For example, if you want to create a red shadow that is 5 pixels to the right and 5 pixels down from your text, with a blur of 2 pixels, you would set the properties like this:

context.shadowColor = "#FF0000";
context.shadowOffsetX = 5;
context.shadowOffsetY = 5;
context.shadowBlur = 2;

Handling global shadows

Just like we saw with globalAlpha, we must reset the shadow properties before we draw the background for textArranger; otherwise, the shadow will apply to the entire image. First, in the canvasApp() function, we create a set of variables to hold the shadow values:

var textAlpha = 1;
var shadowX = 1;
var shadowY = 1;
var shadowBlur = 1;
var shadowColor = "#707070";

We then make sure to turn off the shadow before we render the background for textArranger in the drawScreen(). We don’t have to reset the shadowColor, but we think it is good practice to update all the relative properties relating to any global change to the Canvas context:

context.shadowColor = "#707070";
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.shadowBlur = 0;

Later in drawScreen(), we render the shadow based on the settings in the four variables we created:

context.shadowColor = shadowColor;
context.shadowOffsetX = shadowX;
context.shadowOffsetY = shadowY;
context.shadowBlur = shadowBlur;

We also need to create the HTML to allow the user to update the shadow settings. We do this with three range controls, as well as another color picker using jsColor:

Shadow X:<input type="range" id="shadowX"
       min="−100"
       max="100"
       step="1"
       value="1"/>
<br>
Shadow Y:<input type="range" id="shadowY"
       min="−100"
       max="100"
       step="1"
       value="1"/>
<br>
Shadow Blur: <input type="range" id="shadowBlur"
       min="1"
       max="100"
       step="1"
       value="1" />
<br>
Shadow Color: <input class="color" id="shadowColor" value="707070"/>

Finally, we need to add the event listeners and event handler functions so the HTML form elements can communicate with the canvas. See the results in Figure 3-8:

formElement = document.getElementById("shadowX");
formElement.addEventListener('change', shadowXChanged, false);

formElement = document.getElementById("shadowY");
formElement.addEventListener('change', shadowYChanged, false);

formElement = document.getElementById("shadowBlur");
formElement.addEventListener('change', shadowBlurChanged, false);

formElement = document.getElementById("shadowColor");
formElement.addEventListener('change', shadowColorChanged, false);
function shadowXChanged(e) {
      var target = e.target;
      shadowX = target.value;
      drawScreen();
   }

   function shadowYChanged(e) {
      var target = e.target;
      shadowY = target.value;
      drawScreen();
   }

   function shadowBlurChanged(e) {
      var target = e.target;
      shadowBlur = target.value;
      drawScreen();
   }

   function shadowColorChanged(e) {
      var target = e.target;
      shadowColor = target.value;
      drawScreen();
   }
Text with global shadow applied
Figure 3-8. Text with global shadow applied

Text with Gradients and Patterns

We’ve already explored the fillColor and strokeColor properties of the Canvas context by setting those value to CSS-compliant colors. However, those very same properties can be set to refer to a few other objects defined in the Canvas API to create some stunning text effects. The objects are:

Linear gradient

A linear color gradient with two or more colors

Radial gradient

A circular color gradient with two or more colors

Image pattern

An Image object used as a fill pattern

Linear Gradients and Text

To create a linear gradient, make a call to the context’s createLinearGradient() method to create a Gradient object. The createLinearGradient() method accepts four parameters that all define the line of the linear gradient. The x0 and y0 parameters are the starting point of the line, and x1 and y1 represent the ending point of the line:

var gradient = context.createLinearGradient( [x0],[y0],[x1],[y1]);

For example, if you want to create a linear gradient that starts at the beginning of the text (located at 100,100), and has an endpoint that is the width of your text as displayed on the canvas, you might write the following code:

var metrics = context.measureText(message);
var textWidth = metrics.width;
var gradient = context.createLinearGradient(100, 100, textWidth, 100);

After you have created the line that represents the gradient, you need to add colors that will form the gradations of the gradient fill. This is done with the addColorStop() method, which requires two arguments: offset and color:

gradient.addColorStop([offset],[color]);
offset

This is the offset on the gradient line to start the color gradation. The entire gradient is represented by the numbers between 0.0 and 1.0. The offset will be a decimal that represents a percentage.

color

A valid CSS color in the format “#RRGGBB”.

So, if you want black to be the first color in the gradient, and then red to be the second color that starts halfway down the gradient line, you would create two calls to addColorStop():

gradient.addColorStop(0, "#000000");
gradient.addColorStop(.5, "#FF0000");

Note

If you fail to add colors with addColorStop(), the text will be rendered invisible.

The results are shown in Figure 3-9.

Text with linear gradient applied
Figure 3-9. Text with linear gradient applied

Radial Gradients and Text

A radial gradient is created much like a linear gradient, except that it represents a cone—not a line. The cone is created by defining the center points and the radii of two different circles when calling the createRadialGradient() function of the Canvas context:

var gradient = context.createRadialGradient([x0],[y0],[radius0],[x1],[y1],[radius1]);

Let’s say you want to create a radial gradient based on a cone. It starts with a circle that has its center point at 100,100 and a radius of 20, and ends at a circle with its center point at 200,100 and a radius of 5. The code would look like this:

var gradient = context.createRadialGradient(100,100,20,200,100,5);

Adding color stops to a radial gradient works the same as with a linear gradient, except the color moves along the cone instead of the line:

gradient.addColorStop(0, "#000000 ");
gradient.addColorStop(.5, "#FF0000");

Image Patterns and Text

Another option for filling text on HTML5 Canvas is to use an Image object. We will devote all of Chapter 4 to using the Image API, so here we will only discuss the basics of how to use one as a pattern for a text fill.

To create an image pattern, call the createPattern() method of the Canvas context, passing a reference to an Image object, and an option for repetition:

var pattern = context.createPattern([image], [repetition]);
image

A valid Image object that has been loaded with an image by setting the pattern.src property and waiting for the image to load by setting an event listener for the Image onload event. The Canvas specification also allows for a video element or another <canvas> to be used here as well.

repetition

The “tiling” of the image. This can have one of four values:

repeat

The image is tiled on both the x and y axes.

repeat-x

The image is tiled only on the x-axis (horizontally).

repeat-y

The image is tiled only on the y-axis (vertically).

no-repeat

The image is not tiled.

To use the image pattern, apply it to the fillColor and strokeColor properties of the context, just as you would apply a color:

context.fillStyle = pattern;

or:

context.strokeStyle = pattern;

For example, to load an image named texture.jpg and apply it to the fillStyle so that it tiles on both the x and y axes, you would write code like this:

var pattern = new Image();
pattern.src = "texture.jpg"
pattern.onload = function() {
 var pattern = context.createPattern("texture.jpg", "repeat");
 context.fillStyle = pattern;
...
}

Handling Gradients and Patterns in Text Arranger

Text Arranger 3.0 includes many changes that were implemented to support using gradients and image patterns with text on HTML5 Canvas. To see these changes in action, we first need to make sure that we have preloaded the texture.jpg image, which we will use for the context.createPattern() functionality. To do this, we will create a new function named eventAssetsLoaded() that we will set as the event handler for the onload event of the Image object that will hold the pattern. When that image has loaded, we will call canvasApp() in the same way we called it from eventWindowLoaded():

function eventWindowLoaded() {
   var pattern = new Image();
   pattern.src = "texture.jpg";
   pattern.onload = eventAssetsLoaded;
}

function eventAssetsLoaded() {

   canvasApp();
}

Note

We are not going to use the pattern variable we created in this function, as it does not have scope in the canvasApp() function. We are merely using it to make sure that the image is available before we use it.

In the canvasApp() function, we will create three variables to support this new functionality. fillType describes how the text will be filled (a regular color fill, a linear gradient, a radial gradient, or a pattern). The textColorFill2 variable is the second color we will use for the gradient color stop. Finally, the pattern variable holds the Image object we preloaded, which we now need to create an instance of in canvasApp():

var fillType = "colorFill";
var textFillColor2 = "#000000";
var pattern = new Image();
...
pattern.src = "texture.jpg";

Now, let’s jump to the HTML of our <form>. Since we have created different ways to fill the text we are displaying, we need to build a selection that allows for this choice. We will create a <select> box with the id of fillType for this purpose:

Fill Type: <select id="fillType">
  <option value="colorFill">Color Fill</option>
  <option value="linearGradient">Linear Gradient</option>
  <option value="radialGradient">Radial Gradient</option>
  <option value="pattern">pattern</option>
  </select>

We need to add a second color selection that we can use for the gradient fills. We will use the jsColor picker and the id textColorFill2:

Text Color 2: <input class="color" id="textFillColor2" value ="000000"/>
  <br>

Back in canvasApp(), we need to create the event listeners for our two new form elements:

   formElement = document.getElementById("textFillColor2");
   formElement.addEventListener('change', textFillColor2Changed, false);

   formElement = document.getElementById("fillType");
   formElement.addEventListener('change', fillTypeChanged, false);

We also need to create the associated event handler functions for the new form elements:

function textFillColor2Changed(e) {
      var target = e.target;
      textFillColor2 = "#" + target.value;
      drawScreen();
   }

   function fillTypeChanged(e) {
      var target = e.target;
      fillType = target.value;
      drawScreen();
   }

We need to add support to drawScreen() for this new functionality. First, we use the measureText() method of the context to get the width of the text, which we will use to create the gradients:

var metrics = context.measureText(message);
var textWidth = metrics.width;

Then, we need to decide how to format our “color” for the fillStyle or strokeStyle of the context. In this instance, it can be a CSS color, a gradient, or an image pattern; the list below provides more information.

Color fill

If we are doing a simple color fill, we operate just like in previous versions of Text Arranger. All we need to do is make tempColor equal to the value of y.

Linear gradient

For the linear gradient, we need to decide what line we are going to create for the gradient. Our line will start at the beginning of the text (xPosition-textWidth/2 because the text uses the center alignment), and runs horizontally to the end of the text (textWidth). We also add two color stops (at 0 and 60%)—the colors are textFillColor1 and textFillColor2.

Radial gradient

For the radial gradient, we are going to create a cone that starts at the center of the text (xPosition,yPosition) with a radius the size of the font (fontSize). The cone will extend horizontally the width of the text (textWidth) with a radius of 1.

Pattern

For this option, we create a pattern using the pattern image variable we previously created. We designate it to repeat so it will tile horizontally and vertically.

Here’s the code:

var tempColor;
if (fillType == "colorFill") {
   tempColor = textFillColor;
} else if (fillType == "linearGradient") {
   var gradient = context.createLinearGradient(xPosition-
       textWidth/2, yPosition, textWidth, yPosition);
   gradient.addColorStop(0,textFillColor);
   gradient.addColorStop(.6,textFillColor2);
   tempColor = gradient;
} else if (fillType == "radialGradient") {
   var gradient = context.createRadialGradient(xPosition, yPosition, 
       fontSize, xPosition+textWidth, yPosition, 1);
   gradient.addColorStop(0,textFillColor);
   gradient.addColorStop(.6,textFillColor2);
   tempColor = gradient;
} else if (fillType == "pattern") {
  var tempColor = context.createPattern(pattern,"repeat");
} else {
   tempColor = textFillColor;
}

Now, when we set our fillStyle or strokeStyle, we use tempColor instead of textFillColor. This will set the proper text fill choice that will be displayed on the canvas, as shown in Figure 3-10:

context.fillStyle = tempColor;
Text with image pattern applied
Figure 3-10. Text with image pattern applied

Width, Height, Scale, and toDataURL() Revisited

In Chapter 1, we briefly discussed that you can set the width and height of the canvas, as well as the scale (style width and height) of the canvas display area, dynamically in code. We also showed you an example of using the Canvas object’s toDataURL() method to export a “screenshot” of the Canvas application. In this section, we will revisit those functions as they relate to Text Arranger 3.0.

Dynamically Resizing the Canvas

In the code we developed in this chapter, we created a reference to the Canvas object on the HTML page—with the id canvasOne—and used it to retrieve the 2D context of the Canvas object:

var theCanvas = document.getElementById("canvasOne");
var context = theCanvas.getContext("2d");

While the 2D context is very important because we used it to draw directly onto the canvas, we did not spend any time discussing the Canvas object itself. In this chapter, we use the width property of the Canvas object to center text on the canvas. However, the Canvas object also includes another property named height, and both of these properties can be used to dynamically resize the Canvas object on demand. Why would you want to do this? There could be many uses, such as:

  • Updating the canvas to the exact size of a loaded video object

  • Dynamically animating the canvas after the page is loaded

  • Other, more creative uses like the one we will experiment with next

Resizing the canvas on the fly is quite easy. To do it, simply set the width and height properties of the Canvas object, and then redraw the canvas contents:

Canvas.width = 600;
Canvas.height = 500;
drawScreen();

The Canvas 2D API describes this function as a way to “scale” the canvas, but in practice, this does not appear to be true. Instead, the contents of the canvas are simply redrawn at the same size and same location on a larger canvas. Furthermore, if you don’t redraw the canvas content, it appears to be invalidated, blanking the canvas back to white. To properly scale the canvas, you need to use the CSS width and height attributes, as described in the next section. We discuss using a matrix transformation to scale the Canvas in both Chapters 2 and 4.

Dynamically resizing in Text Arranger

We will add the ability for the canvas to be resized at will, giving you a good example of how resizing works and what it does to your drawn content.

First, we will add a couple new range controls to the HTML <form>. As you might have already guessed, we really like this new HTML5 range control, so we’ve tried to find as many uses as possible for it—even though it’s only tangentially related to HTML5 Canvas.

We will give the controls the ids canvasWidth and canvasHeight:

Canvas Width:  <input type="range" id="canvasWidth"
       min="0"
       max="1000"
       step="1"
       value="500"/>
 <br>

  Canvas Height:
  <input type="range" id="canvasHeight"
       min="0"
       max="1000"
       step="1"
       value="300"/>
 <br>

Next, we add event listeners for the new form elements in the canvasApp() function:

formElement = document.getElementById("canvasWidth");
formElement.addEventListener('change', canvasWidthChanged, false);

formElement = document.getElementById("canvasHeight");
formElement.addEventListener('change', canvasHeightChanged, false);

Finally, we add the event handlers. Notice that we set the width and height of theCanvas (the variable we created that represents the Canvas object on screen) right inside these functions. We also need to make sure that we call drawScreen() in each function so that the canvas is redrawn on the newly resized area. If we did not do this, the canvas on the page would blank back to white:

function canvasWidthChanged(e) {
      var target = e.target;
      theCanvas.width = target.value;
      drawScreen();
   }

function canvasHeightChanged(e) {
      var target =  e.target;
      theCanvas.height =  target.value;
      drawScreen();
   }

We also need to change the way we draw the background for the application in the drawScreen() function so it supports a resized canvas. We do this by using the width and height attributes of theCanvas to create our background and bounding box:

context.fillStyle = '#ffffaa';
context.fillRect(0, 0, theCanvas.width, theCanvas.height);
//Box
context.strokeStyle = '#000000';
context.strokeRect(5,  5, theCanvas.width−10, theCanvas.height−10);

Dynamically Scaling the Canvas

Besides resizing the canvas using theCanvas.width and theCanvas.height attributes, you can also use CSS styles to change its scale. Unlike resizing, scaling takes the current canvas bitmapped area and resamples it to fit into the size specified by the width and height attributes of the CSS style. For example, to scale the canvas to a 400×400 area, you might use this CSS style:

style = "width: 400px; height:400px"

To update the style.width and style.height properties of the canvas in Text Arranger, we first create two more range controls in the HTML page:

Canvas Style Width:  <input type="range" id="canvasStyleWidth"
       min="0"
       max="1000"
       step="1"
       value="500"/>
 <br>

  Canvas Style Height:
  <input type="range" id="canvasStyleHeight"
       min="0"
       max="1000"
       step="1"
       value="300"/>
  <br>

Next, we set the event handler for each range control. However, this time we are using the same handler —canvasStyleSizeChanged()—for both:

formElement = document.getElementById("canvasStyleWidth");
formElement.addEventListener("change", canvasStyleSizeChanged, false);
formElement = document.getElementById("canvasStyleHeight");
formElement.addEventListener("change", canvasStyleSizeChanged, false);

In the event handler, we use the document.getElementById() method to get the values from both range controls. We then create a string that represents the style we want to set for the canvas:

"width:" + styleWidth.value + "px; height:" + styleHeight.value +"px;";

Finally, we use the setAttribute() method to set the “style”:

function canvasStyleSizeChanged(e) {

    var styleWidth = document.getElementById("canvasStyleWidth");
    var styleHeight = document.getElementById("canvasStyleHeight");
    var styleValue = "width:" + styleWidth.value + "px; height:" + 
        styleHeight.value +"px;";
    theCanvas.setAttribute("style", styleValue );
    drawScreen();
    }

Note

While trying to change theCanvas.width and theCanvas.height attributes, you might notice some oddities if you try to change the scale with CSS at the same time. It appears that once you change the scale with CSS, the width and height attributes update the canvas in relation to that scale, which might not be the effect you are expecting. Experiment with Text Arranger 3.0 to see how these different styles and attributes interact.

The toDataURL() Method of the Canvas Object

As we briefly explained in Chapter 1, the Canvas object also contains a method named toDataURL(), which returns a string representing the canvas’ image data. A call with no arguments will return a string of image data of MIME type image/png. If you supply the image/jpg as an argument, you can also supply a second argument between the numbers 0.0 and 1.0 that represents the quality/compression level of the image.

We are going to use toDataURL() to output the image data of the canvas into a <textarea> on our form, and then open a window to display the actual image. This is just a simple way to show that the function is working.

The first thing we do is create our last two form controls in HTML for Text Arranger. We start by creating a button with the id of createImageData that, when pressed, will create the image data with a call to an event handler named createImageDataPressed().

We also create a <textarea> named imageDataDisplay that will hold the text data of the image after the createImageData button is pressed:

<input type="button" id="createImageData" value="Create Image Data">
<br>

<br>
<textarea id="imageDataDisplay" rows=10 cols=30></textarea>

Next, we set up the event listener for the createImageData button:

formElement = document.getElementById("createImageData");
formElement.addEventListener('click', createImageDataPressed, false);

Then, in the createImageDataPressed() event handler, we call the toDataURL() method of the Canvas object (theCanvas), and set the value of the imageDataDisplay <textarea> to the data returned from toDataURL(). Finally, using the image data as the URL for the window, we call window.open(). When we do this, a window will pop open, displaying the actual image created from the canvas (see Figure 3-11). You can right-click and save this image, just like any other image displayed in an HTML page. Pretty cool, eh?

function createImageDataPressed(e) {

      var imageDataDisplay = document.getElementById('imageDataDisplay');
      imageDataDisplay.value = theCanvas.toDataURL();
      window.open(imageDataDisplay.value,"canvasImage","left=0,top=0,width=" + 
          theCanvas.width + ",height=" + theCanvas.height +
          ",toolbar=0,resizable=0");
   }
Canvas exported image with toDataURL()
Figure 3-11. Canvas exported image with toDataURL()

Final Version of Text Arranger

The final version of Text Arranger (3.0) brings together all the HTML5 Text API features we have discussed in this chapter (see Example 3-3). Play with the final app, and see how the different options interact with one another. Here are a couple things you might find interesting:

  • Shadows don’t work with patterns or gradients.

  • Increasing the text size with a pattern that is the size of the canvas changes the pattern on the text (it acts like a mask or window into the pattern itself).

  • Canvas width and height are affected by the style width and height (scaling).

Example 3-3. Text Arranger 3.0
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>CH3EX3: Text Arranger 3.0</title>
<script src="modernizr-1.6.min.js"></script>
<script type="text/javascript" src="jscolor/jscolor.js"></script>
<script type="text/javascript">

window.addEventListener("load", eventWindowLoaded, false);
function eventWindowLoaded() {

   canvasApp();
}

function canvasSupport () {
     return Modernizr.canvas;
}

function eventWindowLoaded() {
   var pattern = new Image();
   pattern.src = "texture.jpg";
   pattern.onload = eventAssetsLoaded;
}

function eventAssetsLoaded() {

   canvasApp();
}

function canvasApp() {

   var message = "your text";
   var fontSize = "50";
   var fontFace = "serif";
   var textFillColor = "#ff0000";
   var textAlpha = 1;
   var shadowX = 1;
   var shadowY = 1;
   var shadowBlur = 1;
   var shadowColor = "#707070";
   var textBaseline = "middle";
   var textAlign = "center";
   var fillOrStroke ="fill";
   var fontWeight = "normal";
   var fontStyle = "normal";
   var fillType = "colorFill";
   var textFillColor2 = "#000000";
   var pattern = new Image();

   if (!canvasSupport()) {
          return;
        }

   var theCanvas = document.getElementById("canvasOne");
   var context = theCanvas.getContext("2d");

   var formElement = document.getElementById("textBox");
   formElement.addEventListener("keyup", textBoxChanged, false);

   formElement = document.getElementById("fillOrStroke");
   formElement.addEventListener("change", fillOrStrokeChanged, false);

   formElement = document.getElementById("textSize");
   formElement.addEventListener("change", textSizeChanged, false);

   formElement = document.getElementById("textFillColor");
   formElement.addEventListener("change", textFillColorChanged, false);

   formElement = document.getElementById("textFont");
   formElement.addEventListener("change", textFontChanged, false);

   formElement = document.getElementById("textBaseline");
   formElement.addEventListener("change", textBaselineChanged, false);

   formElement = document.getElementById("textAlign");
   formElement.addEventListener("change", textAlignChanged, false);

   formElement = document.getElementById("fontWeight");
   formElement.addEventListener("change", fontWeightChanged, false);

   formElement = document.getElementById("fontStyle");
   formElement.addEventListener("change", fontStyleChanged, false);

   formElement = document.getElementById("shadowX");
   formElement.addEventListener("change", shadowXChanged, false);

   formElement = document.getElementById("shadowY");
   formElement.addEventListener("change", shadowYChanged, false);

   formElement = document.getElementById("shadowBlur");
   formElement.addEventListener("change", shadowBlurChanged, false);

   formElement = document.getElementById("shadowColor");
   formElement.addEventListener("change", shadowColorChanged, false);

   formElement = document.getElementById("textAlpha");
   formElement.addEventListener("change", textAlphaChanged, false);

   formElement = document.getElementById("textFillColor2");
   formElement.addEventListener("change", textFillColor2Changed, false);

   formElement = document.getElementById("fillType");
   formElement.addEventListener("change", fillTypeChanged, false);

   formElement = document.getElementById("canvasWidth");
   formElement.addEventListener("change", canvasWidthChanged, false);

   formElement = document.getElementById("canvasHeight");
   formElement.addEventListener("change", canvasHeightChanged, false);

   formElement = document.getElementById("canvasStyleWidth");
   formElement.addEventListener("change", canvasStyleSizeChanged, false);

   formElement = document.getElementById("canvasStyleHeight");
   formElement.addEventListener("change", canvasStyleSizeChanged, false);

   formElement = document.getElementById("createImageData");
   formElement.addEventListener("click", createImageDataPressed, false);

   pattern.src = "texture.jpg";

   drawScreen();

   function drawScreen() {
      
     //Background
      context.globalAlpha = 1;
      context.shadowColor = "#707070";
      context.shadowOffsetX = 0;
      context.shadowOffsetY = 0;
      context.shadowBlur = 0;
      context.fillStyle = "#ffffaa";
      context.fillRect(0, 0, theCanvas.width, theCanvas.height);   

      //Box
      context.strokeStyle = "#000000";
      context.strokeRect(5,  5, theCanvas.width-10, theCanvas.height-10);

      //Text
      context.textBaseline = textBaseline;
      context.textAlign = textAlign;
      context.font = fontWeight + " " + fontStyle + " " + fontSize + "px " + fontFace;
      context.shadowColor = shadowColor;
      context.shadowOffsetX = shadowX;
      context.shadowOffsetY = shadowY;
      context.shadowBlur = shadowBlur;
      context.globalAlpha = textAlpha;

      var xPosition = (theCanvas.width/2);
      var yPosition = (theCanvas.height/2);

      var metrics = context.measureText(message);
      var textWidth = metrics.width;

      var tempColor;
      if (fillType == "colorFill") {
         tempColor = textFillColor;
      } else if (fillType == "linearGradient") {

         var gradient = context.createLinearGradient(xPosition-
            textWidth/2, yPosition, textWidth, yPosition);
         gradient.addColorStop(0,textFillColor);
         gradient.addColorStop(.6,textFillColor2);
         tempColor = gradient;
      } else if (fillType == "radialGradient") {
         var gradient = context.createRadialGradient(xPosition, yPosition, 
             fontSize, xPosition+textWidth, yPosition, 1);
         gradient.addColorStop(0,textFillColor);
         gradient.addColorStop(.6,textFillColor2);
         tempColor = gradient;
      } else if (fillType == "pattern") {
         var tempColor = context.createPattern(pattern,"repeat")
      } else {
         tempColor = textFillColor;
      }

      switch(fillOrStroke) {
         case "fill":
            context.fillStyle = tempColor;
                context.fillText  (message, xPosition,yPosition);
            break;
         case "stroke":
            context.strokeStyle = tempColor;
            context.strokeText  (message, xPosition,yPosition);
            break;
         case "both":
            context.fillStyle = tempColor;
                context.fillText  (message, xPosition,yPosition);
            context.strokeStyle = "#000000";
            context.strokeText  (message, xPosition,yPosition);
            break;
      }


   }

   function textBoxChanged(e) {
      var target = e.target;
      message = target.value;
      drawScreen();
   }

   function textBaselineChanged(e) {
      var target = e.target;
      textBaseline = target.value;
      drawScreen();
   }

   function textAlignChanged(e) {
      var target = e.target;
      textAlign = target.value;
      drawScreen();
   }

   function fillOrStrokeChanged(e) {
      var target = e.target;
      fillOrStroke = target.value;
      drawScreen();
   }

   function textSizeChanged(e) {
      var target = e.target;
      fontSize = target.value;
      drawScreen();
   }

   function textFillColorChanged(e) {
      var target = e.target;
      textFillColor = "#" + target.value;
      drawScreen();
   }

   function textFontChanged(e) {
      var target = e.target;
      fontFace = target.value;
      drawScreen();
   }

   function fontWeightChanged(e) {
      var target = e.target;
      fontWeight = target.value;
      drawScreen();
   }

   function fontStyleChanged(e) {
      var target = e.target;
      fontStyle = target.value;
      drawScreen();
   }

   function shadowXChanged(e) {
      var target = e.target;
      shadowX = target.value;
      drawScreen();
   }

   function shadowYChanged(e) {
      var target = e.target;
      shadowY = target.value;
      drawScreen();
   }

   function shadowBlurChanged(e) {
      var target = e.target;
      shadowBlur = target.value;
      drawScreen();
   }

   function shadowColorChanged(e) {
      var target = e.target;
      shadowColor = target.value;
      drawScreen();
   }

   function textAlphaChanged(e) {
      var target = e.target;
      textAlpha = (target.value);
      drawScreen();
   }

   function textFillColor2Changed(e) {
      var target = e.target;
      textFillColor2 = "#" + target.value;
      drawScreen();
   }

   function fillTypeChanged(e) {
      var target = e.target;
      fillType = target.value;
      drawScreen();
   }

   function canvasWidthChanged(e) {
      var target = e.target;
      theCanvas.width = target.value;
      drawScreen();
   }

   function canvasHeightChanged(e) {
      var target = e.target;
      theCanvas.height = target.value;
      drawScreen();
   }

   function canvasStyleSizeChanged(e) {

      var styleWidth = document.getElementById("canvasStyleWidth");
      var styleHeight = document.getElementById("canvasStyleHeight");
      var styleValue = "width:" + styleWidth.value + "px; height:" + 
          styleHeight.value +"px;";
      theCanvas.setAttribute("style", styleValue );
      drawScreen();
   }


   function createImageDataPressed(e) {

      var imageDataDisplay = document.getElementById("imageDataDisplay");
      imageDataDisplay.value = theCanvas.toDataURL();
      window.open(imageDataDisplay.value,"canvasImage","left=0,top=0,width=" + 
         theCanvas.width + ",height=" + theCanvas.height +
         ",toolbar=0,resizable=0");

   }

}

</script>
</head>
<body>
<div style="display:none">
      <video id="theVideo" autoplay="true" loop="true">
         <source src="spaceeggs.ogg" type="video/ogg"/>
      </video>

</div>

<div style="position: absolute; top: 50px; left: 50px;">
<canvas id="canvasOne" width="500" height="300">
 Your browser does not support HTML5 Canvas.
</canvas>
<form>
  Text: <input id="textBox" placeholder="your text" />
  <br>
  Text Font: <select id="textFont">
  <option value="serif">serif</option>
  <option value="sans-serif">sans-serif</option>
  <option value="cursive">cursive</option>
  <option value="fantasy">fantasy</option>
  <option value="monospace">monospace</option>
  </select>
  <br> Font Weight:
 <select id="fontWeight">
 <option value="normal">normal</option>
 <option value="bold">bold</option>
 <option value="bolder">bolder</option>
 <option value="lighter">lighter</option>
 </select>
 <br>
 Font Style:
 <select id="fontStyle">
 <option value="normal">normal</option>
 <option value="italic">italic</option>
 <option value="oblique">oblique</option>
 </select>
 <br>
 Text Size: <input type="range" id="textSize"
       min="0"
       max="200"
       step="1"
       value="50"/>
  <br>
  Fill Type: <select id="fillType">
  <option value="colorFill">Color Fill</option>
  <option value="linearGradient">Linear Gradient</option>
  <option value="radialGradient">Radial Gradient</option>
  <option value="pattern">pattern</option>
  </select>
  <br>
  Text Color: <input class="color" id="textFillColor" value="FF0000"/>
  <br>
  Text Color 2: <input class="color" id="textFillColor2" value ="000000"/>
  <br>
  Fill Or Stroke: <select id="fillOrStroke">
  <option value="fill">fill</option>
  <option value="stroke">stroke</option>
  <option value="both">both</option>
  </select>
  <br>
  Text Baseline <select id="textBaseline">
  <option value="middle">middle</option>
  <option value="top">top</option>
  <option value="hanging">hanging</option>
  <option value="alphabetic">alphabetic</option>
  <option value="ideographic">ideographic</option>
  <option value="bottom">bottom</option>
  </select>
  <br>
  Text Align <select id="textAlign">
  <option value="center">center</option>
  <option value="start">start</option>
  <option value="end">end</option>
  <option value="left">left</option>
  <option value="right">right</option>
  </select>
 <br>
 Alpha: <input type="range" id="textAlpha"
       min="0.0"
       max="1.0"
       step="0.01"
       value="1.0"/>
 <br>
 Shadow X:<input type="range" id="shadowX"
       min="−100"
       max="100"
       step="1"
       value="1"/>
 <br>
 Shadow Y:<input type="range" id="shadowY"
       min="−100"
       max="100"
       step="1"
       value="1"/>
 <br>
 Shadow Blur: <input type="range" id="shadowBlur"
       min="1"
       max="100"
       step="1"
       value="1" />
 <br>
 Shadow Color: <input class="color" id="shadowColor" value="707070"/>
 <br>
 Canvas Width:  <input type="range" id="canvasWidth"
       min="0"
       max="1000"
       step="1"
       value="500"/>
 <br>
 Canvas Height:
  <input type="range" id="canvasHeight"
       min="0"
       max="1000"
       step="1"
       value="300"/>
 <br>
 Canvas Style Width:  <input type="range" id="canvasStyleWidth"
       min="0"
       max="1000"
       step="1"
       value="500"/>
 <br>
 Canvas Style Height:
  <input type="range" id="canvasStyleHeight"
       min="0"
       max="1000"
       step="1"
       value="300"/>
 <br>
 <input type="button" id="createImageData" value="Create Image Data">
 <br>

 <br>
 <textarea id="imageDataDisplay" rows=10 cols=30></textarea>
 </form>

</div>
</body>
</html>

What’s Next

In this chapter, we introduced you to the fundamentals of the HTML5 Canvas Text API, offered some general concepts relating to drawing on the canvas, and explained how to communicate with HTML form controls. As you can now see, the basic concept of writing text to HTML5 Canvas can be taken to very complex (and some might argue ludicrous) levels. The final application, Text Arranger 3.0, allows you to modify a single line of text in an almost infinite number of ways. In the next chapter, we move on to displaying and manipulating images on the canvas.

Get HTML5 Canvas now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.