Formatting text on HTML canvas

I'm making a game using HTML Canvas and vanilla javascript. I'm new to javascript so this may be easier than I think. I have two array's of objects that represent cards, each object has a 'text' property of between 40-100 characters that's drawn onto the screen dynamically onto a card that is 130 x 70px.

I need to format the text to fit the width restriction of the card (130px) and create a new line whenever necessary.

Any help is appreciated

Edited to make clearer



You can use the measureText() method from the canvas API.
As noted by Ken Fyrstenberg in this awesome answer,

canvas' measureText doesn't currently support measuring height (ascent + descent).

Below attempt uses an hardcoded lineHeight value that you'd have to find before rendering text. Ken's answer does provide a way to programmatically find it.

[ Edit: thanks to markE's comment below, it now uses an approximation of 1.286*the font-size. ]

So here it is, dirty and there must be better ways to do so but anyway...

var input = document.querySelector('input');
input.addEventListener('keyup', write, false);
var c = document.createElement('canvas'),ctx = c.getContext('2d');
c.width = 400, c.height = 150; document.body.appendChild(c);

// simple box object for the card
var card = {x: 25, y: 25, w: 130, h: 70};
ctx.fillStyle = "#CCCCCC";
ctx.fillRect(card.x, card.y, card.w, card.h);

var fontSize = 12;
ctx.font=fontSize+'px arial';

// Margins multipliers are chosen arbitrarly here, just because they fit well to my eyes
var margins = {t: 1.25*fontSize, l: .7*fontSize, b: 2*fontSize, r: .7*fontSize},
marginsX = margins.l+margins.r,
marginsY = margins.t+margins.b;

// As suggested by markE, lineHeight is set to 1.286* the fontSize, 
// for a calculated way, see Ken's answer here :
var lineHeight = 1.286*fontSize;

// just a shortcut
var lineWidth = function(text) {
  return ctx.measureText(text).width;

function write() {
  var txt = this.value;
  // Redraw our card
  ctx.fillStyle = "#CCCCCC";
  ctx.fillRect(card.x, card.y, card.w, card.h);
  // Split the input at any white space, you might want to change it
  var txtLines = txt.split(/\s/);
  var i = 0, maxWidth = card.w - marginsX;

  if(lineWidth(txt[0])>card.w || lineHeight>card.h-(margins.t/4) ){
      console.warn('TOO BIG FONT!!');

  while(txtLines[i]) {
    // If our current line merged with the next line width isn't greater than the card one
    if (txtLines[i+1] && lineWidth(txtLines[i] + ' ' + txtLines[i+1]) < maxWidth) {
      // Merge them
      txtLines[i] += ' ' + txtLines.splice(i + 1, 1);
    else {
      // Is the one word too big? --> Dirtyphenation !
      if (lineWidth(txtLines[i]) > maxWidth) {
        // Add a new index with the two last chars since we'll add a dash
        txtLines.splice(i+1, 0, "");
        // If it's still too big
        while (lineWidth(txtLines[i]) > maxWidth) {
          var lastChars = txtLines[i].length - 2;
          // Append those two last chars to our new array index
          txtLines[i+1] = txtLines[i].substring(lastChars) + txtLines[i+1];
          // Remove them from our current index
          txtLines[i] = txtLines[i].substring(0, lastChars);
        // Add the final dash
        txtLines[i] += "-";
      // Is our text taller than the card height?
	  if (lineHeight*i > card.h-marginsY){
        // If there is more text coming after...
        if (txtLines[i+1]){
            // ...and it fits in the line
            if(lineWidth(txtLines[i]+' '+txtLines[i+1])<maxWidth){
            // ...and it doesn't fit in the line
	            // Does a single char fit with the ellipsis ?
                    // remove a char until we can put our ellipsis
					while (lineWidth(txtLines[i]+'...') > maxWidth){
						txtLines[i] = txtLines[i].substring(0,txtLines[i].length-1)
               txtLines[i] += '...';
                // remove the surplus from the array
               txtLines = txtLines.slice(0,i+1);
      // stop looping here since we don't have space anymore
   	  // Go to next line
  ctx.fillStyle = "#000";
  // Where to draw
  var x = card.x + (margins.l);
  var y = card.y + (margins.t);
  // Iterate through our lines
  for (var i = 0; i < txtLines.length; i++) {
    ctx.fillText(txtLines[i], x, y + (i * lineHeight));
canvas {border: 1px solid}
<input type="text" />


Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.