HTML5 Canvas text measuring with Konva

A quick run through of some of the principles of working with text and breaking away from relying on textOut and Text object from the Konva HTML5 canvas library. I’m assuming you’ll know enough JavaScript to follow along.

For dealing with text in the context of the HTML5 canvas there are two points to be aware of:

  1. In the land of the canvas life is all about rectangles, and
  2. When dealing with text output, what you get in the canvas is primitive, but usable.

So, life is a rectangle. And so is each and every character you output on the canvas. You might be seeing a Comic Sans letter e, or the kanji symbol for love 愛 – but right now it will help a lot if you imagine them as images. Like little photos that you are lining up together.

To cater for the overactive minds that can’t accept that on face value, a few words about fonts. Modern fonts in 2021+ are vector based. The opposite of that would be raster fonts which really are just a matrix of dots, and like any image, pixelate when scaled up. We’ve probably got Steve Jobs and the Apple Macintosh to thank for vector fonts, which instead of dots are defined as drawing instructions for lines, curves and fills. They were around before the first Mac but desktop publishing really pushed them forward. No more about raster fonts from here on as they are dead and gone. Read more about the origin of computer fonts on Wikipedia.

Mostly you will be using vector fonts in the form of TrueType (Adobe), Opentype (Adobe & MS), or the Web Open Font Format (or as my dog says) WOFF which is possibly the only acronym in this sentence that you might recognise. See more about web typography here – it’s really thrilling 😉

All these formats follow a similar construction. Within their files are vast amounts of metadata describing the glyphs (characters / letters / what you see) and how to generate the curves and lines to make them. Why so vast – become enlightened by reading this excellent article – The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

All good to know, but forget what’s actually in the rectangle – just think about the rectangle of space that every character is drawn within!

Right – walks away dusting hands – enough of the brain food, lets give the hands something to do!

So, here’s me doing manually what we are going to do in code.

Taking this apart, to lay out the text for any body of text, the steps are:

  1. Break the text into words
  2. Measure each word
  3. Place the words one after the other on the first line…until one will not fit
  4. Place that word at the start of the next line.
  5. Repeat 3 & 4 until all words are consumed.

Steps 1 is simple JavaScript. To achieve step 2 we we need to measure each word.

The HTML5 canvas has a native method measureText(), which returns a TextMetrics structure which holds the width of a string for a given font and point size. It doesn’t exactly give the height – it gives some other measurements called ascender and descender that maybe we can infer the height from. But I wrote a whole other blog on my journey to work out the ‘proper’ line height of text so that I could make text look as good as it does in a browser DIV. The proper height is definitely NOT the font pixels height, but I’ll use that for the purposes of this article for the sake of simplicity.

On with the code then – you can use a plain canvas and meaureText(), or you can let Konva do the heavy lifting for you – Konva delivers this capability via the width() and height() attributes of the Text object. In the code below we take a phrase, split it into words, and measure and display each word and its dimensions. Note that you do not have to add the Text object to the layer in order for the measurement process to work – meaning text measuring this way is high performance because we are not shovelling bytes from memory to the display.

let 
    scale = 2,  // we'll use scale so we can see the detail if needed.
    stage = new Konva.Stage({ // Set up a stage
      container: "container",
      width: window.innerWidth,
      height: window.innerHeight, 
      scaleX: scale, scaleY: scale,
      draggable: true
    }),
    // add a layer to draw on
    layer = new Konva.Layer(),  
    
    // make a text object to clone to keep the clutter down.
    wordText = new Konva.Text({
      fontFamily: "Arial",
      fontSize: "10",
      fontStyle : "",
      fontVariant : "",
      fill: 'black'
    }),
    
    // the phrase we will measure the words for.
    phrase = "Around the ragged rock the ragged wombat ran",
    
    // The phrase broken into an array of words.
    words = phrase.split(" "),
    
    // a variable to track the y-position of the text for this demo.
    lastHeight = 0;
    

// Add the layer to the stage.
stage.add(layer);

// Loop throught the phrase array, measure and display each word and its dimensions.
 for (let i = 0; i < words.length; i++){
   
   // Make a text object of the word from the phrase
   let txt = wordText.clone({
     x : 0,
     y: lastHeight, // use the lastHeight variable to vertically position the output.
     text: words[i]
   }),
            
   // And another to display the dimensions of the word
   szTxt = wordText.clone({
     text: "(" + txt.width() + "w x " + txt.height() + "h)",
     x: txt.width() + 10,
     y: txt.y()
   });

   // increment the height variable.
   lastHeight = lastHeight + txt.height() + 5;
   
   // add the text objects to the stage so we can see them
   layer.add(txt, szTxt)
      
 }

// refresh the canvas.
layer.batchDraw();
stage.draw();

And here’s the result and the CodePen.

Summary

What we learned from this is that it is very easy to measure words using Konva. Once we have the rectangular measurements of the text we can do all kinds of interesting things such as:

  • Alignment
  • Character spacing
  • Justification
  • Change font name or decoration for any word leaving the others unchanged.
  • Fitting text into a fixed box by proportional scaling.

On that last point regarding proportional scaling – you can scale these word or character rects. Put another way, for any given word or character the rect you get measuring at 10px is half the height and width you get at 20px. So it’s proportional. However, if you do create a fitting algorithm, do remember that you need to recalculate the words on each line after each scale adjustment because smaller words means more words on the line.

See the next blog articles for all that other stuff.

Thanks for reading.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: