ChatGPT解决这个技术问题 Extra ChatGPT

How can you find the height of text on an HTML canvas?

The spec has a context.measureText(text) function that will tell you how much width it would require to print that text, but I can't find a way to find out how tall it is. I know it's based on the font, but I don't know to convert a font string to a text height.

I would love to know a better way than the top answer. If there's some algorithm to take arbitrary point font and find the max/min bounds on it, then I would be very happy to hear about it. =)
@tjameson - there seems to be. See answer from ellisbben (and my enhancement to it).
I'm wondering if the Unicode Character 'FULL BLOCK' (U+2588) could be used as an approximation by multiplying its width by two.
It's worth noting that the answer depends a little on your requirements. For example, the height required to render the character "a" is different to the height required to render the character "y", due to the descender that extends below the baseline of the font. The HTML based answers below do not account for this and will give you a general height appropriate for any text, whereas @Noitidart's answer gives a more exact height for specific text.
Remember that you can have characters that look like this M̶̢̹̝͖̦̖̭͕̭̣͆̃̀̅̒̊͌̿ͅ, so this is a really tricky problem so solve for the general case.

D
Daniel Earwicker

UPDATE - for an example of this working, I used this technique in the Carota editor.

Following on from ellisbben's answer, here is an enhanced version to get the ascent and descent from the baseline, i.e. same as tmAscent and tmDescent returned by Win32's GetTextMetric API. This is needed if you want to do a word-wrapped run of text with spans in different fonts/sizes.

https://i.stack.imgur.com/h7tRn.png

The above image was generated on a canvas in Safari, red being the top line where the canvas was told to draw the text, green being the baseline and blue being the bottom (so red to blue is the full height).

Using jQuery for succinctness:

var getTextHeight = function(font) {

  var text = $('<span>Hg</span>').css({ fontFamily: font });
  var block = $('<div style="display: inline-block; width: 1px; height: 0px;"></div>');

  var div = $('<div></div>');
  div.append(text, block);

  var body = $('body');
  body.append(div);

  try {

    var result = {};

    block.css({ verticalAlign: 'baseline' });
    result.ascent = block.offset().top - text.offset().top;

    block.css({ verticalAlign: 'bottom' });
    result.height = block.offset().top - text.offset().top;

    result.descent = result.height - result.ascent;

  } finally {
    div.remove();
  }

  return result;
};

In addition to a text element, I add a div with display: inline-block so I can set its vertical-align style, and then find out where the browser has put it.

So you get back an object with ascent, descent and height (which is just ascent + descent for convenience). To test it, it's worth having a function that draws a horizontal line:

var testLine = function(ctx, x, y, len, style) {
  ctx.strokeStyle = style; 
  ctx.beginPath();
  ctx.moveTo(x, y);
  ctx.lineTo(x + len, y);
  ctx.closePath();
  ctx.stroke();
};

Then you can see how the text is positioned on the canvas relative to the top, baseline and bottom:

var font = '36pt Times';
var message = 'Big Text';

ctx.fillStyle = 'black';
ctx.textAlign = 'left';
ctx.textBaseline = 'top'; // important!
ctx.font = font;
ctx.fillText(message, x, y);

// Canvas can tell us the width
var w = ctx.measureText(message).width;

// New function gets the other info we need
var h = getTextHeight(font);

testLine(ctx, x, y, w, 'red');
testLine(ctx, x, y + h.ascent, w, 'green');
testLine(ctx, x, y + h.height, w, 'blue');

Why not use this text to determine the height? abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 Depending on the font you may have characters that are much higher or lower than g and M
@ellisbben it's worth noting that the results of this differ slightly from yours, although I don't know why. For instance, yours says Courier New 8pt ==> 12 pixels high, while this says: Courier New 8pt ==> 13 pixels high. I added the "g" to your method, but that wasn't the difference. One wonders, which value would be most useful (not necessarily technically correct).
I could only get things working correctly when I changed the first line of getTextHeight() to var text = $('<span>Hg</span>').css({ 'font-family': fontName, 'font-size' : fontSize });, i.e. adding the size separately.
How to make it work for non-english text? see jsfiddle.net/siddjain/6vURk
thank you ! modify <div></div> to <div style="white-space : nowrap;"></div> to handle very long string
Z
ZachB

Browsers are beginning to support advanced text metrics, which will make this task trivial when it's widely supported:

let metrics = ctx.measureText(text);
let fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
let actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;

fontHeight gets you the bounding box height that is constant regardless of the string being rendered. actualHeight is specific to the string being rendered.

Spec: https://www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent and the sections just below it.

Support status (20-Aug-2017):

Chrome has it behind a flag (https://bugs.chromium.org/p/chromium/issues/detail?id=277215).

Firefox has it in development (https://bugzilla.mozilla.org/show_bug.cgi?id=1102584).

Edge has no support (https://wpdev.uservoice.com/forums/257854-microsoft-edge-developer/suggestions/30922861-advanced-canvas-textmetrics).

node-canvas (node.js module), mostly supported (https://github.com/Automattic/node-canvas/wiki/Compatibility-Status).


Everyone please vote on the bug pages to have these features implemented sooner
Sadly, it's 2021 and chromium has yet to support more than width on the TextMetrics object (running in Electron).
unfortunately it is not working in ie11, because metrics.fontBoundingBoxAscent is not supported
According to Mozilla this is now supported on every modern browser. developer.mozilla.org/en-US/docs/Web/API/TextMetrics/…
l
laike9m

You can get a very close approximation of the vertical height by checking the length of a capital M.

ctx.font = 'bold 10px Arial';

lineHeight = ctx.measureText('M').width;

How does the width give us an approximation of the line height?
They mean that the width of a single capital 'M' at a given font size is about the same as the line height. (Don't know if this is true, but thats what the answer is saying)
Interesting answer
p
pimvdb

The canvas spec doesn't give us a method for measuring the height of a string. However, you can set the size of your text in pixels and you can usually figure out what the vertical bounds are relatively easily.

If you need something more precise then you could throw text onto the canvas and then get pixel data and figure out how many pixels are used vertically. This would be relatively simple, but not very efficient. You could do something like this (it works, but draws some text onto your canvas that you would want to remove):

function measureTextHeight(ctx, left, top, width, height) {

    // Draw the text in the specified area
    ctx.save();
    ctx.translate(left, top + Math.round(height * 0.8));
    ctx.mozDrawText('gM'); // This seems like tall text...  Doesn't it?
    ctx.restore();

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

    // Find the last line with a non-white 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-white 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;
    }

    // We screwed something up...  What do you expect from free code?
    return 0;
}

// Set the font
context.mozTextStyle = '32px Arial';

// Specify a context and a rect that is safe to draw in when calling measureTextHeight
var height = measureTextHeight(context, 0, 0, 50, 50);
console.log(height);

For Bespin they do fake a height by measuring the width of a lowercase 'm'... I don't know how this is used, and I would not recommend this method. Here is the relevant Bespin method:

var fixCanvas = function(ctx) {
    // upgrade Firefox 3.0.x text rendering to HTML 5 standard
    if (!ctx.fillText && ctx.mozDrawText) {
        ctx.fillText = function(textToDraw, x, y, maxWidth) {
            ctx.translate(x, y);
            ctx.mozTextStyle = ctx.font;
            ctx.mozDrawText(textToDraw);
            ctx.translate(-x, -y);
        }
    }

    if (!ctx.measureText && ctx.mozMeasureText) {
        ctx.measureText = function(text) {
            ctx.mozTextStyle = ctx.font;
            var width = ctx.mozMeasureText(text);
            return { width: width };
        }
    }

    if (ctx.measureText && !ctx.html5MeasureText) {
        ctx.html5MeasureText = ctx.measureText;
        ctx.measureText = function(text) {
            var textMetrics = ctx.html5MeasureText(text);

            // fake it 'til you make it
            textMetrics.ascent = ctx.html5MeasureText("m").width;

            return textMetrics;
        }
    }

    // for other browsers
    if (!ctx.fillText) {
        ctx.fillText = function() {}
    }

    if (!ctx.measureText) {
        ctx.measureText = function() { return 10; }
    }
};

I doubt this is what the people that wrote the HTML5 spec had in mind.
This is a terrible terrible hack that I absolutely love. +1
I don't get it. Where is the connection between the font ascent and the width of the letter "m"?
em is a relative font measurement where one em is equal to the height of the letter M in the default font size.
Right, the height not the width... I'm still confused about the connection. Also, I think that ems are irrelevant given that we only care about the height in pixels.
e
ellisbben

EDIT: Are you using canvas transforms? If so, you'll have to track the transformation matrix. The following method should measure the height of text with the initial transform.

EDIT #2: Oddly the code below does not produce correct answers when I run it on this StackOverflow page; it's entirely possible that the presence of some style rules could break this function.

The canvas uses fonts as defined by CSS, so in theory we can just add an appropriately styled chunk of text to the document and measure its height. I think this is significantly easier than rendering text and then checking pixel data and it should also respect ascenders and descenders. Check out the following:

var determineFontHeight = function(fontStyle) {
  var body = document.getElementsByTagName("body")[0];
  var dummy = document.createElement("div");
  var dummyText = document.createTextNode("M");
  dummy.appendChild(dummyText);
  dummy.setAttribute("style", fontStyle);
  body.appendChild(dummy);
  var result = dummy.offsetHeight;
  body.removeChild(dummy);
  return result;
};

//A little test...
var exampleFamilies = ["Helvetica", "Verdana", "Times New Roman", "Courier New"];
var exampleSizes = [8, 10, 12, 16, 24, 36, 48, 96];
for(var i = 0; i < exampleFamilies.length; i++) {
  var family = exampleFamilies[i];
  for(var j = 0; j < exampleSizes.length; j++) {
    var size = exampleSizes[j] + "pt";
    var style = "font-family: " + family + "; font-size: " + size + ";";
    var pixelHeight = determineFontHeight(style);
    console.log(family + " " + size + " ==> " + pixelHeight + " pixels high.");
  }
}

You'll have to make sure you get the font style correct on the DOM element that you measure the height of but that's pretty straightforward; really you should use something like

var canvas = /* ... */
var context = canvas.getContext("2d");
var canvasFont = " ... ";
var fontHeight = determineFontHeight("font: " + canvasFont + ";");
context.font = canvasFont;
/*
  do your stuff with your font and its height here.
*/

+1 Way better solution IMO. It should be possible to get the position of the baseline as well.
Have added an answer that gets the baseline.
Does this work? I didn't even think about sticking it in a div. This probably doesn't even have to be added to the DOM, no?
I'm totally ignorant of which size and position fields of a node exist when it is not part of the document. I'd be super-interested to read a reference that addresses that, if you know of one.
+1 for a screenful of complicated code that would be just context.measureText(text).height in a parallel universe with a better Canvas API
B
Bachalo

Isn't the height of the text in pixels equal to the font size (in pts) if you define the font using context.font ?


This is what it is claimed by this source: html5canvastutorials.com/tutorials/html5-canvas-text-metrics
for simple cases: you can always parse out the height, from the font name: parseInt(ctx.font.split(' ')[0].replace('px', '')); //parsing string: "10px Verdana"
You can use px, pt, em, and % for font size. This is precisely why this answer is misleading.
@Jacksonkr, Yea but still you could parse them out and adjust accordingly right? Or is there an inherent limitation somewhere to this approach?
@Pacerier The limitation is that you may introduce some bugs that cause you to pull your hair out. Just remember that mixing unit types can lead to buggy / spaghetti code. That said, I'm not above the occasional hack as long as long as the risk for issues is low.
M
Matt Palmerlee

As JJ Stiff suggests, you can add your text to a span and then measure the offsetHeight of the span.

var d = document.createElement("span");
d.font = "20px arial";
d.textContent = "Hello world!";
document.body.appendChild(d);
var emHeight = d.offsetHeight;
document.body.removeChild(d);

As shown on HTML5Rocks


This is so nice solution, Thanks... but I don't know why if this span was not added to the page and visible before I get its offsetHeight ! it always return height as ZERO in Chrome and Firefox !
You are right, I guess it has to be added to the dom for it to take up space. Here is a JS Fiddle of this working: jsfiddle.net/mpalmerlee/4NfVR/4 I've also updated the code above.
Using clientHeight is also a possiblitity. Altough this answer is a solution to the problem, it is an ugly workaround. +1'd it nevertheless.
This does not consider the actual height of the visible text and usually comes across with additional margin on top of the text…
S
Siniša

Just to add to Daniel's answer (which is great! and absolutely right!), version without JQuery:

function objOff(obj)
{
    var currleft = currtop = 0;
    if( obj.offsetParent )
    { do { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
      while( obj = obj.offsetParent ); }
    else { currleft += obj.offsetLeft; currtop += obj.offsetTop; }
    return [currleft,currtop];
}
function FontMetric(fontName,fontSize) 
{
    var text = document.createElement("span");
    text.style.fontFamily = fontName;
    text.style.fontSize = fontSize + "px";
    text.innerHTML = "ABCjgq|"; 
    // if you will use some weird fonts, like handwriting or symbols, then you need to edit this test string for chars that will have most extreme accend/descend values

    var block = document.createElement("div");
    block.style.display = "inline-block";
    block.style.width = "1px";
    block.style.height = "0px";

    var div = document.createElement("div");
    div.appendChild(text);
    div.appendChild(block);

    // this test div must be visible otherwise offsetLeft/offsetTop will return 0
    // but still let's try to avoid any potential glitches in various browsers
    // by making it's height 0px, and overflow hidden
    div.style.height = "0px";
    div.style.overflow = "hidden";

    // I tried without adding it to body - won't work. So we gotta do this one.
    document.body.appendChild(div);

    block.style.verticalAlign = "baseline";
    var bp = objOff(block);
    var tp = objOff(text);
    var taccent = bp[1] - tp[1];
    block.style.verticalAlign = "bottom";
    bp = objOff(block);
    tp = objOff(text);
    var theight = bp[1] - tp[1];
    var tdescent = theight - taccent;

    // now take it off :-)
    document.body.removeChild(div);

    // return text accent, descent and total height
    return [taccent,theight,tdescent];
}

I've just tested the code above and works great on latest Chrome, FF and Safari on Mac.

EDIT: I have added font size as well and tested with webfont instead of system font - works awesome.


A
Anton Putov

I solved this problem straitforward - using pixel manipulation.

Here is graphical answer:

https://i.stack.imgur.com/mSH1c.png

Here is code:

    function textHeight (text, font) {

    var fontDraw = document.createElement("canvas");

    var height = 100;
    var width = 100;

    // here we expect that font size will be less canvas geometry
    fontDraw.setAttribute("height", height);
    fontDraw.setAttribute("width", width);

    var ctx = fontDraw.getContext('2d');
    // black is default
    ctx.fillRect(0, 0, width, height);
    ctx.textBaseline = 'top';
    ctx.fillStyle = 'white';
    ctx.font = font;
    ctx.fillText(text/*'Eg'*/, 0, 0);

    var pixels = ctx.getImageData(0, 0, width, height).data;

    // row numbers where we first find letter end where it ends 
    var start = -1;
    var end = -1;

    for (var row = 0; row < height; row++) {
        for (var column = 0; column < width; column++) {

            var index = (row * width + column) * 4;

            // if pixel is not white (background color)
            if (pixels[index] == 0) {
                // we havent met white (font color) pixel
                // on the row and the letters was detected
                if (column == width - 1 && start != -1) {
                    end = row;
                    row = height;
                    break;
                }
                continue;
            }
            else {
                // we find top of letter
                if (start == -1) {
                    start = row;
                }
                // ..letters body
                break;
            }

        }

    }
   /*
    document.body.appendChild(fontDraw);
    fontDraw.style.pixelLeft = 400;
    fontDraw.style.pixelTop = 400;
    fontDraw.style.position = "absolute";
   */

    return end - start;

}

I believe this solution does not take into account dotted letters like lowercase i and j
D
Durgpal Singh

one line answer

var height = parseInt(ctx.font) * 1.2; 

CSS "line-height: normal" is between 1 and 1.2

read here for more info


I
I wrestled a bear once.

This is what I did based on some of the other answers here:

function measureText(text, font) { const span = document.createElement('span'); span.appendChild(document.createTextNode(text)); Object.assign(span.style, { font: font, margin: '0', padding: '0', border: '0', whiteSpace: 'nowrap' }); document.body.appendChild(span); const {width, height} = span.getBoundingClientRect(); span.remove(); return {width, height}; } var font = "italic 100px Georgia"; var text = "abc this is a test"; console.log(measureText(text, font));


佚名

I'm writing a terminal emulator so I needed to draw rectangles around characters.

var size = 10
var lineHeight = 1.2 // CSS "line-height: normal" is between 1 and 1.2
context.font = size+'px/'+lineHeight+'em monospace'
width = context.measureText('m').width
height = size * lineHeight

Obviously if you want the exact amount of space the character takes up, it won't help. But it'll give you a good approximation for certain uses.


s
samanime

I'm kind of shocked that there are no correct answers here. There is no need to make an estimate or a guess. Also, the font-size is not the actual size of the bounding box of the font. The font height depends on whether you have ascenders and descenders.

To calculate it, use ctx.measureText() and add together the actualBoundingBoxAscent and the actualBoundingBoxDescent. That'll give you the actual size. You can also add together the font* versions to get the size that is used to calculate things like element height, but isn't strictly the height of the actual used space for the font.

const text = 'Hello World'; const canvas = document.querySelector('canvas'); canvas.width = 500; canvas.height = 200; const ctx = canvas.getContext('2d'); ctx.font = '100px Arial, Helvetica, sans-serif'; // top is critical to the fillText() calculation // you can use other positions, but you need to adjust the calculation ctx.textBaseline = 'top'; ctx.textAlign = 'center'; const metrics = ctx.measureText(text); const width = metrics.width; const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent; const fontHeight = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent; ctx.fillStyle = '#00F'; // blue ctx.fillRect((canvas.width / 2) - (width / 2), (canvas.height / 2) - (fontHeight / 2), width, fontHeight); ctx.fillStyle = '#0F0'; // green ctx.fillRect((canvas.width / 2) - (width / 2), (canvas.height / 2) - (actualHeight / 2), width, actualHeight); // canvas.height / 2 - actualHeight / 2 gets you to the top of // the green box. You have to add actualBoundingBoxAscent to shift // it just right ctx.fillStyle = '#F00'; // red ctx.fillText(text, canvas.width / 2, canvas.height / 2 - actualHeight / 2 + metrics.actualBoundingBoxAscent);


C
ChrisBellew

I have implemented a nice library for measuring the exact height and width of text using HTML canvas. This should do what you want.

https://github.com/ChrisBellew/text-measurer.js


You probably should read How to offer personal open-source libraries? before posting your library to more answers.
t
trinalbadger587

Here is a simple function. No library needed.

I wrote this function to get the top and bottom bounds relative to baseline. If textBaseline is set to alphabetic. What it does is it creates another canvas, and then draws there, and then finds the top most and bottom most non blank pixel. And that is the top and bottom bounds. It returns it as relative, so if height is 20px, and there is nothing below the baseline, then the top bound is -20.

You must supply characters to it. Otherwise it will give you 0 height and 0 width, obviously.

Usage:

alert(measureHeight('40px serif', 40, 'rg').height)

Here is the function:

function measureHeight(aFont, aSize, aChars, aOptions={}) {
    // if you do pass aOptions.ctx, keep in mind that the ctx properties will be changed and not set back. so you should have a devoted canvas for this
    // if you dont pass in a width to aOptions, it will return it to you in the return object
    // the returned width is Math.ceil'ed
    console.error('aChars: "' + aChars + '"');
    var defaultOptions = {
        width: undefined, // if you specify a width then i wont have to use measureText to get the width
        canAndCtx: undefined, // set it to object {can:,ctx:} // if not provided, i will make one
        range: 3
    };

    aOptions.range = aOptions.range || 3; // multiples the aSize by this much

    if (aChars === '') {
        // no characters, so obviously everything is 0
        return {
            relativeBot: 0,
            relativeTop: 0,
            height: 0,
            width: 0
        };
        // otherwise i will get IndexSizeError: Index or size is negative or greater than the allowed amount error somewhere below
    }

    // validateOptionsObj(aOptions, defaultOptions); // not needed because all defaults are undefined

    var can;
    var ctx; 
    if (!aOptions.canAndCtx) {
        can = document.createElement('canvas');;
        can.mozOpaque = 'true'; // improved performanceo on firefox i guess
        ctx = can.getContext('2d');

        // can.style.position = 'absolute';
        // can.style.zIndex = 10000;
        // can.style.left = 0;
        // can.style.top = 0;
        // document.body.appendChild(can);
    } else {
        can = aOptions.canAndCtx.can;
        ctx = aOptions.canAndCtx.ctx;
    }

    var w = aOptions.width;
    if (!w) {
        ctx.textBaseline = 'alphabetic';
        ctx.textAlign = 'left'; 
        ctx.font = aFont;
        w = ctx.measureText(aChars).width;
    }

    w = Math.ceil(w); // needed as i use w in the calc for the loop, it needs to be a whole number

    // must set width/height, as it wont paint outside of the bounds
    can.width = w;
    can.height = aSize * aOptions.range;

    ctx.font = aFont; // need to set the .font again, because after changing width/height it makes it forget for some reason
    ctx.textBaseline = 'alphabetic';
    ctx.textAlign = 'left'; 

    ctx.fillStyle = 'white';

    console.log('w:', w);

    var avgOfRange = (aOptions.range + 1) / 2;
    var yBaseline = Math.ceil(aSize * avgOfRange);
    console.log('yBaseline:', yBaseline);

    ctx.fillText(aChars, 0, yBaseline);

    var yEnd = aSize * aOptions.range;

    var data = ctx.getImageData(0, 0, w, yEnd).data;
    // console.log('data:', data)

    var botBound = -1;
    var topBound = -1;

    // measureHeightY:
    for (y=0; y<=yEnd; y++) {
        for (var x = 0; x < w; x += 1) {
            var n = 4 * (w * y + x);
            var r = data[n];
            var g = data[n + 1];
            var b = data[n + 2];
            // var a = data[n + 3];

            if (r+g+b > 0) { // non black px found
                if (topBound == -1) { 
                    topBound = y;
                }
                botBound = y; // break measureHeightY; // dont break measureHeightY ever, keep going, we till yEnd. so we get proper height for strings like "`." or ":" or "!"
                break;
            }
        }
    }

    return {
        relativeBot: botBound - yBaseline, // relative to baseline of 0 // bottom most row having non-black
        relativeTop: topBound - yBaseline, // relative to baseline of 0 // top most row having non-black
        height: (botBound - topBound) + 1,
        width: w// EDIT: comma has been added to fix old broken code.
    };
}

relativeBot, relativeTop, and height are the useful things in the return object.

Here is example usage:

Page Title

This is a Heading

This is a paragraph.

The relativeBot and relativeTop are what you see in this image here:

https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_text

https://www.whatwg.org/specs/web-apps/current-work/images/baselines.png


J
JJ Stiff

Funny that TextMetrics has width only and no height:

http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#textmetrics

Can you use a Span as on this example?

http://mudcu.be/journal/2011/01/html5-typographic-metrics/#alignFix


The reason to only have width is this: stackoverflow.com/a/12112978/1085483
k
kiss_von

First of all, you need to set the height of a font size, and then according to the value of the font height to determine the current height of your text is how much, cross-text lines, of course, the same height of the font need to accumulate, if the text does not exceed the largest text box Height, all show, otherwise, only show the text within the box text. High values need your own definition. The larger the preset height, the greater the height of the text that needs to be displayed and intercepted.

After the effect is processed(solve)

Before the effect is processed( unsolved)

  AutoWrappedText.auto_wrap = function(ctx, text, maxWidth, maxHeight) {
var words = text.split("");
var lines = [];
var currentLine = words[0];

var total_height = 0;
for (var i = 1; i < words.length; i++) {
    var word = words[i];
    var width = ctx.measureText(currentLine + word).width;
    if (width < maxWidth) {
        currentLine += word;
    } else {
        lines.push(currentLine);
        currentLine = word;
        // TODO dynamically get font size
        total_height += 25;

        if (total_height >= maxHeight) {
          break
        }
    }
}
if (total_height + 25 < maxHeight) {
  lines.push(currentLine);
} else {
  lines[lines.length - 1] += "…";
}
return lines;};

t
tmx976

I found that JUST FOR ARIAL the simplest, fastest and accuratest way to find height of bounding box is to use the width of certain letters. If you plan to use a certain font without letting user to choose one different, you can do a little research to find the right letter that do the job for that font.

Your browser does not support the HTML5 canvas tag.

Note: The canvas tag is not supported in Internet Explorer 8 and earlier versions.


佚名

setting the font size might not be practical though, since setting

ctx.font = ''

will use the one defined by CSS as well as any embedded font tags. If you use the CSS font you have no idea what the height is from a programmatic way, using the measureText method, which is very short sighted. On another note though, IE8 DOES return the width and height.


A
Andrey

This works 1) for multiline text as well 2) and even in IE9!

<div class="measureText" id="measureText">
</div>


.measureText {
  margin: 0;
  padding: 0;
  border: 0;
  font-family: Arial;
  position: fixed;
  visibility: hidden;
  height: auto;
  width: auto;
  white-space: pre-wrap;
  line-height: 100%;
}

function getTextFieldMeasure(fontSize, value) {
    const div = document.getElementById("measureText");

    // returns wrong result for multiline text with last line empty
    let arr = value.split('\n');
    if (arr[arr.length-1].length == 0) {
        value += '.';
    }

    div.innerText = value;
    div.style['font-size']= fontSize + "px";
    let rect = div.getBoundingClientRect();

    return {width: rect.width, height: rect.height};
};

R
Ronen Ness

I know this is an old answered question, but for future reference I'd like to add a short, minimal, JS-only (no jquery) solution I believe people can benefit from:

var measureTextHeight = function(fontFamily, fontSize) 
{
    var text = document.createElement('span');
    text.style.fontFamily = fontFamily;
    text.style.fontSize = fontSize + "px";
    text.textContent = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
    document.body.appendChild(text);
    var result = text.getBoundingClientRect().height;
    document.body.removeChild(text);
    return result;
};

S
Shivam Malhotra

I monkey patched CanvasRenderingContext2D.measureText() in one of my project to include actual height of the text. It's written in vanilla JS and has zero dependencies.

/*
 * Monkeypatch CanvasRenderingContext2D.measureText() to include actual height of the text
 */
; (function (global) {
  "use strict";

  var _measureText = global.CanvasRenderingContext2D.prototype.measureText;

  global.CanvasRenderingContext2D.prototype.measureText = function () {
    var textMetrics = _measureText.apply(this, arguments);

    var _getHeight = function (text) {
      var $span = global.document.createElement("span");
      var spanTextNode = global.document.createTextNode(text);
      $span.appendChild(spanTextNode);
      $span.setAttribute("style", `font: ${this.font}`);

      var $div = global.document.createElement("div");
      $div.setAttribute("style", "display: inline-block; width: 1px; height: 0; vertical-align: super;");

      var $parentDiv = global.document.createElement("div");
      $parentDiv.appendChild($span);
      $parentDiv.appendChild($div);

      var $body = global.document.getElementsByTagName("body")[0];
      $body.appendChild($parentDiv);

      var divRect = $div.getBoundingClientRect();
      var spanRect = $span.getBoundingClientRect();
      var result = {};

      $div.style.verticalAlign = "baseline";
      result.ascent = divRect.top - spanRect.top;

      $div.style.verticalAlign = "bottom";
      result.height = divRect.top - spanRect.top;

      result.descent = result.height - result.ascent;

      $body.removeChild($parentDiv);

      return result.height - result.descent;
    }.bind(this);

    var height = _getHeight(arguments[0]);

    global.Object.defineProperty(textMetrics, "height", { value: height });

    return textMetrics;
  };

})(window);

You can use it like this

ctx.font = "bold 64px Verdana, sans-serif"; // Automatically considers it as part of height calculation
var textMetrics = ctx.measureText("Foobar");
var textHeight = textMetrics.height;

o
oooiiiii

parseInt(ctx.font, 10)

e.g.

let text_height = parseInt(ctx.font, 10)

e.g. returns 35


L
Lodewijk

In normal situations the following should work:

var can = CanvasElement.getContext('2d');          //get context
var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable

Completely and utterly wrong. There is a HUGE difference between a point size and a pixel size. The point size results in different sized text depending upon the DPI at which the page is being rendered, where-as a pixel size does not take that into account.
Are you familiar with the concept of screen pixels? You might find it enlightening. Regardless, pt and px do indeed indicate different heights. It's worth noting that the font can fill less than it's "height", or more, anyway. I'm not sure if canvas pixels are scaled, but I assume so. It is a "wrong" answer, though. It's simple and can be used in many situations.
u
user3283552

This is madding... The height of the text is the font size.. Didn't any of you read the documentation?

context.font = "22px arial";

this will set the height to 22px.

the only reason there is a..

context.measureText(string).width

is because that the width of the string can not be determined unless it knows the string you want the width of but for all the strings drawn with the font.. the height will be 22px.

if you use another measurement than px then the height will still be the same but with that measurement so at most all you would have to do is convert the measurement.


Some letters can extend above or below the limits, see whatwg.org/specs/web-apps/current-work/images/baselines.png
Badly phrased but still usually true.
read all these answers but for my simple app this was the correct answer, pt === px
Just completely incorrect. Try some of the solutions proposed with varying fonts and you'll find large discrepancies.
s
sonichy

Approximate solution:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = "100px Arial";
var txt = "Hello guys!"
var wt = ctx.measureText(txt).width;
var height = wt / txt.length;

This will be accurate result in monospaced font.