1

I'm working with canvases and i can't think of any solution, or find answer on line to my problem.

I have a font that contains symbols\characters from varies sizes - heights and width.

I want to draw some characters (Symbols) from the font and some on top\down of the symbol. The problem is that I can't figure out a way to have the exact height in pixels of the character that i'm drawing, and it's causes unwanted spaces between the center symbol to the one on top\down (for getting the width of a string there is the function context.measureText(theText) ).

for ex. lets say that i want 'X' to be my center symbol. and '-' to be on top. It's looks like this

-
x

but now there is space between the 'X' and the '-' that i don't want.

Can anyone help me with this ?

Thanks

Raziza O
  • 1,560
  • 1
  • 17
  • 37

3 Answers3

8

Width is easy: canvas's context has a built in metric for measuring text width.

// this will measure text width
context.font = '14pt Verdana';
var m=context.measureText(yourText);
var theWidth=m.width;

Height is more difficult because measureText doesn't compute height.

You can often use the font size to approximate the height--that's what I do.

But if you really need more accuracy here is a function that examines the text's pixels to calculate its height:

function measureTextHeight(fontSizeFace) {

    // create a temp canvas
    var width=1000;
    var height=60;
    var canvas=document.createElement("canvas");
    canvas.width=width;
    canvas.height=height;
    var ctx=canvas.getContext("2d");

    // Draw the entire a-z/A-Z alphabet in the canvas
    var text="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    ctx.save();
    ctx.font=fontSizeFace;
    ctx.clearRect(0,0,width,height);
    ctx.fillText(text, 0, 40);
    ctx.restore();

    // Get the pixel data from the canvas
    var data = ctx.getImageData(0,0,width,height).data,
        first = false, 
        last = false,
        r = height,
        c = 0;

    // 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;
            }
        }
    }

    // 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;
            }
        }

        // If we've got it then return the height
        if(first != r) return last - first;
    }

    // error condition if we get here
    return 0;
}
markE
  • 102,905
  • 11
  • 164
  • 176
  • First of all, Thanks very much for the reply. I Thought about this solution also, but this solution is less efficient, and does not match my "efficient" website :) Isn't there a better way ? – Raziza O May 30 '13 at 08:59
  • 3
    This code is extremely efficient if you use it efficiently! Remember that you **just need to run this code once** against your various fonts. Then you have you heights forever! If you set your font size in pixels, you can even do it before releasing your website and store the heights in JSON for use on your website. Easy+Efficient! BTW, this code runs at hundreds of times per second so you really could do it dynamically also. ;) – markE May 30 '13 at 16:33
  • Of course I would do it this way for saving time. But i'm drawing character which represent symbol, different size for each character. and such on.. But i guess there is no other way. Thanks allot. – Raziza O Jun 03 '13 at 11:09
  • 1
    This is so nice. Thanks for sharing. – Jonatas Walker Mar 23 '16 at 22:38
  • [Altered the code](https://jsperf.com/getting-font-height) slightly because I saw this was unoptimised... @markE's suggestion is super good as well, and you can also generate the heights on the fly and store them if you have a changing font! – TheBrenny Jun 30 '18 at 08:00
1

Just to build upon the previous good answers here. This will give the height and width (I had some problems with some of the unicode characters and measureText, this was the only solution for some characters).

function measureTextHeight(fontSizeFace, text) {
  var width = 1500;
  var height = 500;

  var canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  var ctx=canvas.getContext("2d");
  ctx.save();
  ctx.font=fontSizeFace;
  ctx.clearRect(0,0,width,height);
  ctx.fillText(text, parseInt(width * 0.1, 10), parseInt(height / 2, 10));
  ctx.restore();
  document.body.appendChild(canvas);
  var data = ctx.getImageData(0,0,width,height).data;


  var topMost = false;
  var bottomMost = false;
  var leftMost = false;
  var rightMost = false;
  for(var x=0; x<width; x++) {
    for(var y=0; (y<height) && (!leftMost); y++) {
      //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
      if(data[getAlphaIndexForCoordinates(x,y,width,height)] != 0) {
        leftMost = x;
      }
    }
  }
  for(var y=0; y<height; y++) {
    for(var x=0; (x<width) && (!topMost); x++) {
      //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
      if(data[getAlphaIndexForCoordinates(x,y,width,height)] != 0) {
        topMost = y;
      }
    }
  }
  for(var x=width-1; x>=0; x--) {
    for(var y=height-1; (y>=0) && (!rightMost); y--) {
      //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
      if(data[getAlphaIndexForCoordinates(x,y,width,height)] != 0) {
        rightMost = x;
      }
    }
  }
  for(var y=height-1; y>=0; y--) {
    for(var x=width-1; (x>=0) && (!bottomMost); x--) {
      //console.log("x: %s y: %s index: %s", x,y, getAlphaIndexForCoordinates(x,y,width,height).toString() );
      if(data[getAlphaIndexForCoordinates(x,y,width,height)] != 0) {
        bottomMost = y;
      }
    }
  }
  return ({
     width: (rightMost - leftMost) + 1
    ,height: (bottomMost - topMost) + 1
  });
}
function getAlphaIndexForCoordinates(x,y,width,height) {
  return (((width*4*y)+4*x)+3);
}
John Smith
  • 591
  • 4
  • 15
0

Ok, well tried this code and i had to change it a bit for make it work.

this is the code after we found the last line with non-transparent pixel.

so In case someone needs it. just replace Enjoy.

    var blnFound = false;
    var intCurrRow = 0;

    // Find the first line with a non-transparent pixel
    while(!blnFound && intCurrRow < last) 
    {
      for(intCurrCol = 0; intCurrCol < width; intCurrCol++) 
      {
        var intCurrDataIdx = intCurrRow * width * 4 + intCurrCol * 4 + 3;
        if(data[intCurrDataIdx]) 
        {
          first = intCurrRow;
          blnFound = true;
          break;
        }
     }

     // If we've got it then return the height
     if (blnFound)
     {
       return last - first;
     }

     intCurrRow++;
   }
Raziza O
  • 1,560
  • 1
  • 17
  • 37