javascripthtml5-canvasfont-sizetext-size

Javascript - get actual rendered font height


In my on-the-fly editor tool I would really appreciate to get actual rendered height of the text / font - (I do not mean just getting CSS font-size, neither computed nor preset).

Is this achieveable in javascript?

If not directly, is possible something as rendering font in canvas the same way as it is rendered as regular text - and then finding out?

EDIT - my "dev" solution: Based on suggested links I've built a little pure-javascript code, that goes through pixels in canvas and analyses whether the pixel is white or not and acts accordingly, it is hardly a developer version of a code - just outputs few useful info and shows how to access computed data - http://jsfiddle.net/DV9Bw/1325/

HTML:

<canvas id="exampleSomePrettyRandomness" width="200" height="60"></canvas>
<div id="statusSomePrettyRandomness"></div>

JS:

function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
    do {
        curleft += obj.offsetLeft;
        curtop += obj.offsetTop;
    } while (obj = obj.offsetParent);
    return { x: curleft, y: curtop };  
}
return undefined;
}


var status = document.getElementById('statusSomePrettyRandomness');
var example = document.getElementById('exampleSomePrettyRandomness');
var context = example.getContext('2d');
context.fillStyle = "rgb(255,255,255)";
context.fillRect(0, 0, 200, 200);
context.fillStyle = "rgb(0,0,0)";
context.font = "30px Arial";
context.fillText("Hello World",0,30);
var pos = findPos(example);
var x = example.pageX - pos.x;
var y = example.pageY - pos.y;
var foundTop = false;
xPos = 0;
yPos = 0;
topY = -1;
bottomY = -1;
var fuse = 1000;
while( fuse-- > 0 ){
//status.innerHTML += yPos+"<br>";
if( yPos == (example.offsetHeight - 2) ){
    xPos++;
    yPos = 0;
    continue;
}
    var data = context.getImageData(xPos, yPos, 1, 1).data;
if( ! foundTop ){
    if( (data[0] != 255) && (data[1] != 255) && (data[2] != 255) ){
        topY = yPos;
        status.innerHTML += "<br>Found top: "+topY+" X:"+xPos+" Color: rgba("+data[0]+","+data[1]+","+data[2]+")"+"<br>";
        foundTop = true;
    }
} else {
    if( (data[0] == 255) && (data[1] == 255) && (data[2] == 255) ){
        bottomY = yPos;
        status.innerHTML += "<br>Found bottom: "+bottomY+" X:"+xPos+"<br>";
        break;
    }
} 
yPos++;
if( yPos > example.offsetHeight ){
    status.innerHTML += ""
        +"Y overflow ("+yPos+">"+example.offsetHeight+")"
        +" - moving X to "+xPos
        +" - reseting Y to "+yPos 
        +"<br>"
    ;
        xPos++; 
    yPos = 0;
}
}
status.innerHTML += "Fuse:"+fuse+", Top:"+topY+", Bottom: "+bottomY+"<br>";
status.innerHTML += "Font height should be: "+(bottomY-topY)+"<br>";

EDIT 2: Why this is not a duplicate: My question is about really just real rendered height of a font or a letter, "possible duplicate" is about how much space do you need to print a text, answers provided there don't answer my exact problem anyways.


Solution

  • I am not aware of any method that would return the height of a text such as measureText (which does currently return the width).

    However, in theory you can simply draw your text in the canvas then trim the surrounding transparent pixels then measure the canvas height..

    Here is an example (the height will be logged in the console):

    // Create a blank canvas (by not filling a background color).
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    
    // Fill it with some coloured text.. (black is default)
    ctx.font = "48px serif";
    ctx.textBaseline = "hanging";
    ctx.fillText("Hello world", 0, 0);
    
    // Remove the surrounding transparent pixels
    // result is an actual canvas element
    var result = trim(canvas);
    
    // you could query it's width, draw it, etc..
    document.body.appendChild(result);
    
    // get the height of the trimmed area
    console.log(result.height);
    
    // Trim Canvas Pixels Method
    // https://gist.github.com/remy/784508
    function trim(c) {
    
      var ctx = c.getContext('2d'),
    
        // create a temporary canvas in which we will draw back the trimmed text
        copy = document.createElement('canvas').getContext('2d'),
    
        // Use the Canvas Image Data API, in order to get all the
        // underlying pixels data of that canvas. This will basically
        // return an array (Uint8ClampedArray) containing the data in the
        // RGBA order. Every 4 items represent one pixel.
        pixels = ctx.getImageData(0, 0, c.width, c.height),
    
        // total pixels
        l = pixels.data.length,
        
        // main loop counter and pixels coordinates
        i, x, y,
    
        // an object that will store the area that isn't transparent
        bound = { top: null, left: null, right: null, bottom: null };
    
      // for every pixel in there
      for (i = 0; i < l; i += 4) {
    
        // if the alpha value isn't ZERO (transparent pixel)
        if (pixels.data[i+3] !== 0) {
    
          // find it's coordinates
          x = (i / 4) % c.width;
          y = ~~((i / 4) / c.width);
      
          // store/update those coordinates
          // inside our bounding box Object
    
          if (bound.top === null) {
            bound.top = y;
          }
          
          if (bound.left === null) {
            bound.left = x; 
          } else if (x < bound.left) {
            bound.left = x;
          }
          
          if (bound.right === null) {
            bound.right = x; 
          } else if (bound.right < x) {
            bound.right = x;
          }
          
          if (bound.bottom === null) {
            bound.bottom = y;
          } else if (bound.bottom < y) {
            bound.bottom = y;
          }
        }
      }
      
      // actual height and width of the text
      // (the zone that is actually filled with pixels)
      var trimHeight = bound.bottom - bound.top,
          trimWidth = bound.right - bound.left,
    
          // get the zone (trimWidth x trimHeight) as an ImageData
          // (Uint8ClampedArray of pixels) from our canvas
          trimmed = ctx.getImageData(bound.left, bound.top, trimWidth, trimHeight);
      
      // Draw back the ImageData into the canvas
      copy.canvas.width = trimWidth;
      copy.canvas.height = trimHeight;
      copy.putImageData(trimmed, 0, 0);
    
      // return the canvas element
      return copy.canvas;
    }
    <canvas id="canvas"></canvas>

    Image Data API: https://developer.mozilla.org/en-US/docs/Web/API/ImageData