Search code examples
javascripthtml5-canvas

Get exact size of text on a canvas in JavaScript


I hope there is someone out there to help me with this: I need to get the exact size of a text. Just measuring a span or so not precise enough for my purposes.

Right now, I am using a canvas to find the non-transparent pixels in the canvas. This is my code:

// a function to draw the text on the canvas

let text = "Hello World";
let canvas = document.getElementById('happy-canvas');
let width = 1000
let height = 100
canvas.width = width
canvas.height = height

let ctx = canvas.getContext('2d');
ctx.save();
ctx.font = "30px cursive";
ctx.clearRect(0, 0, width, height);
ctx.fillText(text, 0, 60);

  // get the image data
  let data = ctx.getImageData(0, 0, width, height).data,
    first = false,
    last = false,
    r = height,
    c = 0

  // get the width of the text and convert it to an integer
  const canvWidth = parseInt(ctx.measureText(text).width)

  //Find the last line with a non-transparent pixel
  while (!last && r) {
    r--
    for (c = 0; c < width; c++) {
      if (data[r * width * 4 + c * 4 + 3]) {
        last = r
        break
      }
    }
  }

  let canvasHeight = 0
  // Find the first line with a non-transparent pixel
  while (r) {
    r--
    for (c = 0; c < width; c++) {
      if (data[r * width * 4 + c * 4 + 3]) {
        first = r
        break
      }
    }

    canvasHeight = last - first
  }

  //draw a rectangle around the text
  ctx.strokeRect(0, first, canvWidth, canvasHeight)
<div> The last "d" is not completely inside of the the box
  <canvas id="happy-canvas" width="150" height="150"> I wonder what is here</canvas>
</div>

This works to get the exact height of the text, but not the width. So I use "measureText" right now, but that function gets different sizes depending on the browser and on the font I use. If I use a reagular font, it works quite well. But if I use a more playful font, it does not work at all.

Here is an example:

https://i.sstatic.net/9Nz5h.png

The black box is the measured size. And as you can see "measureText" does not get the correct width. Right now I am out of any idea, what else I could do.


Solution

  • Ok, so I just got it working.

    What am I doing? Well, in my case I know, that the text will always start at a x-value of 0. The length of the text is therefore the non-transparent pixel with the highest x-value in the array given by getImageData(). So I am looping through the getImageData()-array. If I find a pixel that has a higher alpha-value than 0, I will save its x and y value into highestPixel. The next time I find a pixel, I will check if its x-value is higher as the one that is currently in highestPixel. If so, I will overwrite highestPixel with the new values. At the end, I return highestPixel and its x-value will be the exact length of the text.

    Here is the code:

    // a function to draw the text on the canvas
    
    let text = "Hello World";
    let canvas = document.getElementById('happy-canvas');
    let width = 1000
    let height = 100
    canvas.width = width
    canvas.height = height
    
    let ctx = canvas.getContext('2d');
    ctx.save();
    ctx.font = "30px cursive";
    ctx.clearRect(0, 0, width, height);
    ctx.fillText(text, 0, 60);
    
    // get the image data
    let data = ctx.getImageData(0, 0, width, height).data,
      first = false,
      last = false,
      r = height,
      c = 0
    
    // get the width of the text and convert it to an integer
    let getPixelwithHighestX = () => {
      let xOfPixel = 0
      let yOfPixel = 0
      let highestPixel = {
        x: 0,
        y: 0
      }
      for (let i = 3; i < data.length; i += 4) {
        if (data[i] !== 0) {
          yOfPixel = Math.floor(i / 4 / width)
          xOfPixel = Math.floor(i / 4) - yOfPixel * width
          if (xOfPixel > highestPixel.x) {
            highestPixel.x = xOfPixel
            highestPixel.y = yOfPixel
          }
        }
      }
      return highestPixel
    }
    
    let hightestPixel = getPixelwithHighestX()
    
    //Find the last line with a non-transparent pixel
    while (!last && r) {
      r--
      for (c = 0; c < width; c++) {
        if (data[r * width * 4 + c * 4 + 3]) {
          last = r
          break
        }
      }
    }
    
    let canvasHeight = 0
    // Find the first line with a non-transparent pixel
    while (r) {
      r--
      for (c = 0; c < width; c++) {
        if (data[r * width * 4 + c * 4 + 3]) {
          first = r
          break
        }
      }
    
      canvasHeight = last - first
    }
    
    //draw a rectangle around the text
    ctx.strokeRect(0, first, hightestPixel.x, canvasHeight)
    <div> The text is now completely inside the box
      <canvas id="happy-canvas" width="150" height="150"> I wonder what is here</canvas>
    </div>