1

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

RyanUK
  • 67
  • 2
  • 12
  • **See Also**: [HTML5 Canvas API - formatting individual words with italics](https://stackoverflow.com/q/24163211/1366033) – KyleMit Nov 19 '20 at 19:55

1 Answers1

4

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 : https://stackoverflow.com/a/17631567/3702797
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!!');
      return;
      }

  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){
               continue;
            }
            // ...and it doesn't fit in the line
            else{
             // Does a single char fit with the ellipsis ?
                if(lineWidth(txtLines[i][0]+'...')<maxWidth){
                    // remove a char until we can put our ellipsis
     while (lineWidth(txtLines[i]+'...') > maxWidth){
      txtLines[i] = txtLines[i].substring(0,txtLines[i].length-1)
     }
    }else{
     return;
     }
               txtLines[i] += '...';
                // remove the surplus from the array
               txtLines = txtLines.slice(0,i+1);
            }
        }
      // stop looping here since we don't have space anymore
      break;
      }
      // Go to next line
   i++;
  }
  }
  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" />
Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • 1
    A quick rough estimate of line height is 1.286 * font-size. – markE Apr 08 '15 at 17:06
  • 1
    It's quick and rough, but unless your font is "wacky" with outrageous ascenders or descenders you should be fine. The estimate does get wonky at extra small and extra large font sizes, but that doesn't seem to apply for the questioners 130x70 card size. ;-) – markE Apr 08 '15 at 17:09
  • Thanks so much, exactly what I needed, worked a treat! – RyanUK Apr 14 '15 at 19:44