Comment trouver la hauteur du texte sur un canevas HTML?

148

La spécification a une fonction context.measureText (texte) qui vous indiquera la largeur nécessaire pour imprimer ce texte, mais je ne trouve pas de moyen de savoir quelle est sa hauteur. Je sais que c'est basé sur la police, mais je ne sais pas convertir une chaîne de police en une hauteur de texte.

swampsjohn
la source
1
J'aimerais connaître un meilleur moyen que la meilleure réponse. S'il y a un algorithme pour prendre une police de points arbitraire et trouver les limites max / min dessus, alors je serais très heureux d'en entendre parler. =)
beatgammit
@tjameson - il semble y en avoir. Voir la réponse d'ellisbben (et mon amélioration).
Daniel Earwicker
2
Je me demande si le caractère Unicode 'FULL BLOCK' (U + 2588) pourrait être utilisé comme approximation en multipliant sa largeur par deux.
Daniel F
1
Il est à noter que la réponse dépend un peu de vos besoins. Par exemple, la hauteur requise pour rendre le caractère "a" est différente de la hauteur requise pour rendre le caractère "y", en raison du descendant qui s'étend sous la ligne de base de la police. Les réponses HTML ci-dessous ne tiennent pas compte de cela et vous donneront une hauteur générale appropriée pour n'importe quel texte, tandis que la réponse de @ Noitidart donne une hauteur plus exacte pour un texte spécifique.
Dale Anderson
2
N'oubliez pas que vous pouvez avoir des personnages qui ressemblent à ceci M̶̢̹̝͖̦̖̭͕̭̣͆̃̀̅̒̊͌̿ͅ, c'est donc un problème vraiment délicat, alors résolvez le cas général.
GetFree

Réponses:

78

MISE À JOUR - pour un exemple de ce fonctionnement, j'ai utilisé cette technique dans l' éditeur Carota .

Dans le prolongement de la réponse d'ellisbben, voici une version améliorée pour obtenir l'ascension et la descente à partir de la ligne de base, c'est-à-dire identiques tmAscentet tmDescentrenvoyées par l' API GetTextMetric de Win32 . Ceci est nécessaire si vous souhaitez créer une séquence de texte enveloppée de mots avec des étendues dans différentes polices / tailles.

Gros texte sur toile avec lignes métriques

L'image ci-dessus a été générée sur un canevas dans Safari, le rouge étant la ligne supérieure où le canevas a été invité à dessiner le texte, le vert étant la ligne de base et le bleu étant le bas (donc le rouge au bleu est la pleine hauteur).

Utilisation de jQuery pour la concision:

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

En plus d'un élément de texte, j'ajoute un div avec display: inline-blockpour pouvoir définir sonvertical-align style, puis savoir où le navigateur l'a mis.

Vous récupérez donc un objet avec ascent, descentet height(ce qui est juste ascent+ descentpour plus de commodité). Pour le tester, cela vaut la peine d'avoir une fonction qui trace une ligne horizontale:

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();
};

Ensuite, vous pouvez voir comment le texte est positionné sur le canevas par rapport au haut, à la ligne de base et au bas:

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');
Daniel Earwicker
la source
3
Pourquoi ne pas utiliser ce texte pour déterminer la hauteur? abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 Selon la police, vous pouvez avoir des caractères beaucoup plus hauts ou plus bas que g et M
omatase
1
@ellisbben, il convient de noter que les résultats diffèrent légèrement des vôtres, même si je ne sais pas pourquoi. Par exemple, le vôtre dit Courier New 8pt ==> 12 pixels de haut, tandis que ceci dit: Courier New 8pt ==> 13 pixels de haut. J'ai ajouté le "g" à votre méthode, mais ce n'était pas la différence. On se demande quelle valeur serait la plus utile (pas nécessairement techniquement correcte).
Orwellophile
3
Je n'ai pu faire fonctionner correctement les choses que lorsque j'ai changé la première ligne de getTextHeight()en var text = $('<span>Hg</span>').css({ 'font-family': fontName, 'font-size' : fontSize });, c'est- à -dire en ajoutant la taille séparément.
cameron.bracken
1
Comment le faire fonctionner pour le texte non anglais? voir jsfiddle.net/siddjain/6vURk
morpheus
1
Je vous remercie ! modifier <div></div>pour <div style="white-space : nowrap;"></div>gérer une très longue corde
Mickaël Gauvin
40

Vous pouvez obtenir une approximation très proche de la hauteur verticale en vérifiant la longueur d'un grand M.

ctx.font='bold 10px Arial';

lineHeight=ctx.measureText('M').width;
Vic Fanberg
la source
8
Comment la largeur nous donne-t-elle une approximation de la hauteur de la ligne?
Richard Barker
11
Ils signifient que la largeur d'un seul «M» majuscule à une taille de police donnée est à peu près la même que la hauteur de la ligne. (Je ne sais pas si c'est vrai, mais c'est ce que dit la réponse)
Nathan
2
C'est en fait une approximation assez décente que j'utilise pour le prototypage rapide. Ce n'est pas parfait, mais c'est une solution à 90%.
Austin D
37

La spécification du canevas ne nous donne pas de méthode pour mesurer la hauteur d'une chaîne. Cependant, vous pouvez définir la taille de votre texte en pixels et vous pouvez généralement déterminer quelles sont les limites verticales relativement facilement.

Si vous avez besoin de quelque chose de plus précis, vous pouvez lancer du texte sur le canevas, puis obtenir des données de pixels et déterminer combien de pixels sont utilisés verticalement. Ce serait relativement simple, mais pas très efficace. Vous pouvez faire quelque chose comme ceci (cela fonctionne, mais dessine du texte sur votre canevas que vous voudriez supprimer):

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);

Pour Bespin, ils simulent une hauteur en mesurant la largeur d'un 'm' minuscule ... Je ne sais pas comment cela est utilisé, et je ne recommanderais pas cette méthode. Voici la méthode Bespin pertinente:

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; }
    }
};
Prestaul
la source
28
Je doute que ce soit ce que les gens qui ont écrit la spécification HTML5 avaient à l'esprit.
Steve Hanov
17
C'est un terrible hack terrible que j'adore. +1
Allain Lalonde
1
Je ne comprends pas. Où est le lien entre l'ascension de la police et la largeur de la lettre «m»?
kayahr
6
emest une mesure de police relative où un em est égal à la hauteur de la lettre Mdans la taille de police par défaut.
jerone
1
Bon, la hauteur pas la largeur ... Je suis toujours confus au sujet de la connexion. De plus, je pense que les ems ne sont pas pertinents étant donné que nous ne nous soucions que de la hauteur en pixels.
Prestaul
35

Les navigateurs commencent à prendre en charge les métriques de texte avancées , ce qui rendra cette tâche triviale lorsqu'elle est largement prise en charge:

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

fontHeightvous obtient la hauteur de la zone de délimitation qui est constante quelle que soit la chaîne rendue. actualHeightest spécifique à la chaîne rendue.

Spec: https://www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent et les sections juste en dessous.

État de l'assistance (20 août 2017):

ZachB
la source
2
Tout le monde, veuillez voter sur les pages de bogues pour que ces fonctionnalités soient implémentées plus tôt
Jeremiah Rose
21

EDIT: Utilisez-vous des transformations de canevas? Si tel est le cas, vous devrez suivre la matrice de transformation. La méthode suivante doit mesurer la hauteur du texte avec la transformation initiale.

EDIT # 2: Curieusement, le code ci-dessous ne produit pas de réponses correctes lorsque je l'exécute sur cette page StackOverflow; il est tout à fait possible que la présence de certaines règles de style puisse casser cette fonction.

Le canevas utilise des polices telles que définies par CSS, donc en théorie, nous pouvons simplement ajouter un morceau de texte de style approprié au document et mesurer sa hauteur. Je pense que c'est beaucoup plus facile que de rendre du texte puis de vérifier les données de pixels et que cela devrait également respecter les ascendeurs et les descendants. Vérifiez ce qui suit:

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.");
  }
}

Vous devrez vous assurer que le style de police est correct sur l'élément DOM dont vous mesurez la hauteur, mais c'est assez simple; vraiment vous devriez utiliser quelque chose comme

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.
*/
ellisbben
la source
1
+1 bien meilleure solution IMO. Il devrait également être possible d'obtenir la position de la ligne de base.
Daniel Earwicker
Ont ajouté une réponse qui obtient la ligne de base.
Daniel Earwicker
Est-ce que ça marche? Je n'ai même pas pensé à le coller dans un div. Cela n'a probablement même pas besoin d'être ajouté au DOM, non?
beatgammit
J'ignore totalement la taille et la position des champs d'un nœud lorsqu'il ne fait pas partie du document. Je serais très intéressé de lire une référence qui traite de cela, si vous en connaissez une.
ellisbben
5
+1 pour un écran plein de code compliqué qui ne serait que context.measureText (text) .height dans un univers parallèle avec une meilleure API Canvas
rsp
11

La hauteur du texte en pixels n'est-elle pas égale à la taille de la police (en pts) si vous définissez la police en utilisant context.font?

Bachalo
la source
2
C'est ce que prétend cette source: html5canvastutorials.com/tutorials/html5-canvas-text-metrics
Rui Marques
pour les cas simples: vous pouvez toujours analyser la hauteur, à partir du nom de la police: parseInt (ctx.font.split ('') [0] .replace ('px', '')); // chaîne d'analyse: "10px Verdana"
Sean
Vous pouvez utiliser px, pt, em et% pour la taille de la police. C'est précisément pourquoi cette réponse est trompeuse.
Jacksonkr
@Jacksonkr, oui mais vous pouvez quand même les analyser et les ajuster en conséquence, n'est-ce pas? Ou y a-t-il quelque part une limitation inhérente à cette approche?
Pacerier
@Pacerier La limitation est que vous pouvez introduire des bugs qui vous font vous arracher les cheveux. Rappelez-vous simplement que les types d'unités de mélange peuvent conduire à du code buggy / spaghetti. Cela dit, je ne suis pas au-dessus du hack occasionnel tant que le risque de problèmes est faible.
Jacksonkr
10

Comme le suggère JJ Stiff, vous pouvez ajouter votre texte à une étendue, puis mesurer la hauteur offset de la plage.

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);

Comme indiqué sur HTML5Rocks

Matt Palmerlee
la source
3
C'est une solution tellement sympa, merci ... mais je ne sais pas pourquoi si cette étendue n'a pas été ajoutée à la page et visible avant d'obtenir son offsetHeight! il renvoie toujours la hauteur comme ZERO dans Chrome et Firefox!
Mustafah
1
Vous avez raison, je suppose qu'il faut l'ajouter au dom pour qu'il prenne de la place. Voici un JS Fiddle de ce fonctionnement: jsfiddle.net/mpalmerlee/4NfVR/4 J'ai également mis à jour le code ci-dessus.
Matt Palmerlee
Utiliser clientHeight est également une possibilité. Bien que cette réponse soit une solution au problème, c'est une solution de contournement laide. +1 quand même.
Daniel F
Cela ne tient pas compte de la hauteur réelle du texte visible et apparaît généralement avec une marge supplémentaire en haut du texte…
Ain Tohvri
8

Juste pour ajouter à la réponse de Daniel (ce qui est génial! Et tout à fait raison!), Version sans 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];
}

Je viens de tester le code ci-dessus et fonctionne très bien sur les derniers Chrome, FF et Safari sur Mac.

EDIT: J'ai également ajouté la taille de la police et testé avec la police Web au lieu de la police système - fonctionne à merveille.

Sinisa
la source
7

J'ai résolu ce problème directement - en utilisant la manipulation des pixels.

Voici la réponse graphique:

Voici le 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;

}
Anton Putov
la source
2
Je pense que cette solution ne prend pas en compte les lettres pointillées comme le i et le j minuscules
wusauarus
3

J'écris un émulateur de terminal donc j'avais besoin de dessiner des rectangles autour des caractères.

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

Évidemment, si vous voulez la quantité exacte d'espace que le personnage occupe, cela ne vous aidera pas. Mais cela vous donnera une bonne approximation pour certaines utilisations.


la source
3

Voici une fonction simple. Aucune bibliothèque nécessaire.

J'ai écrit cette fonction pour obtenir les limites supérieure et inférieure par rapport à la ligne de base. Si textBaselineest défini sur alphabetic. Ce qu'il fait, c'est qu'il crée un autre canevas, puis y dessine, puis trouve le pixel le plus haut et le plus bas non vide. Et ce sont les limites du haut et du bas. Il le renvoie comme relatif, donc si la hauteur est de 20 px et qu'il n'y a rien en dessous de la ligne de base, alors la limite supérieure est-20 .

Vous devez lui fournir des caractères. Sinon, cela vous donnera 0 hauteur et 0 largeur, évidemment.

Usage:

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

Voici la fonction:

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,, relativeTopet heightsont les éléments utiles de l'objet de retour.

Voici un exemple d'utilisation:

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<script>
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
	};
}

</script>
</head>
<body style="background-color:steelblue;">
<input type="button" value="reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg', {canAndCtx:{can:document.getElementById('can'), ctx:document.getElementById('can').getContext('2d')}}).height)">
<input type="button" value="dont reuse can" onClick="alert(measureHeight('40px serif', 40, 'rg').height)">
<canvas id="can"></canvas>
<h1>This is a Heading</h1>
<p>This is a paragraph.</p>
</body>
</html>

Le relativeBotet relativeTopsont ce que vous voyez dans cette image ici:

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

Noitidart
la source
3

réponse en une ligne

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

CSS "line-height: normal" est compris entre 1 et 1,2

lisez ici pour plus d'informations

Durgpal Singh
la source
2

C'est ce que j'ai fait sur la base de certaines des autres réponses ici:

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));

Une fois je me suis battu contre un ours.
la source
1

Tout d'abord, vous devez définir la hauteur d'une taille de police, puis en fonction de la valeur de la hauteur de police pour déterminer la hauteur actuelle de votre texte est de combien, les lignes de texte croisé, bien sûr, la même hauteur de la la police doit s'accumuler, si le texte ne dépasse pas la plus grande hauteur de la zone de texte, tout afficher, sinon, afficher uniquement le texte dans le texte de la zone. Les valeurs élevées nécessitent votre propre définition. Plus la hauteur prédéfinie est grande, plus la hauteur du texte à afficher et à intercepter est grande.

Une fois l'effet traité (résoudre)

Avant que l'effet ne soit traité (non résolu)

  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;};
kiss_von
la source
1

J'ai trouvé que JUST FOR ARIAL, le moyen le plus simple, le plus rapide et le plus précis de trouver la hauteur du cadre de sélection consiste à utiliser la largeur de certaines lettres. Si vous prévoyez d'utiliser une certaine police sans laisser l'utilisateur en choisir une différente, vous pouvez faire un peu de recherche pour trouver la bonne lettre qui convient à cette police.

<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="700" height="200" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>

<script>
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.font = "100px Arial";
var txt = "Hello guys!"
var Hsup=ctx.measureText("H").width;
var Hbox=ctx.measureText("W").width;
var W=ctx.measureText(txt).width;
var W2=ctx.measureText(txt.substr(0, 9)).width;

ctx.fillText(txt, 10, 100);
ctx.rect(10,100, W, -Hsup);
ctx.rect(10,100+Hbox-Hsup, W2, -Hbox);
ctx.stroke();
</script>

<p><strong>Note:</strong> The canvas tag is not supported in Internet 
Explorer 8 and earlier versions.</p>

</body>
</html>

tmx976
la source
0

la définition de la taille de la police peut cependant ne pas être

ctx.font = ''

utilisera celui défini par CSS ainsi que toutes les balises de police intégrées. Si vous utilisez la police CSS, vous n'avez aucune idée de la hauteur par programmation, en utilisant la méthode measureText, qui est très myope. Sur une autre note cependant, IE8 renvoie la largeur et la hauteur.


la source
0

Cela fonctionne 1) pour le texte multiligne également 2) et même dans 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};
};
Andreï
la source
0

Je sais que c'est une vieille question avec réponse, mais pour référence future, j'aimerais ajouter une solution courte, minimale, uniquement JS (pas de jquery) dont je pense que les gens peuvent bénéficier:

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;
};
Ronen Ness
la source
-3

Dans des situations normales, les éléments suivants devraient fonctionner:

var can = CanvasElement.getContext('2d');          //get context
var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable
Lodewijk
la source
5
Complètement et totalement faux. Il y a une énorme différence entre une taille de point et une taille de pixel. La taille en points entraîne un texte de taille différente en fonction du PPP auquel la page est rendue, alors que la taille de pixel ne prend pas cela en compte.
Orvid King
2
Connaissez-vous le concept des pixels d'écran? Vous pourriez trouver cela éclairant. Quoi qu'il en soit, pt et px indiquent en effet des hauteurs différentes. Il est à noter que la police peut remplir moins que sa "hauteur", ou plus, de toute façon. Je ne sais pas si les pixels de la toile sont mis à l'échelle, mais je suppose que oui. C'est une "mauvaise" réponse, cependant. C'est simple et peut être utilisé dans de nombreuses situations.
Lodewijk
-4

C'est fou ... La hauteur du texte est la taille de la police. Aucun de vous n'a lu la documentation?

context.font = "22px arial";

cela définira la hauteur à 22px.

la seule raison pour laquelle il y a un ..

context.measureText(string).width

C'est parce que la largeur de la chaîne ne peut être déterminée que s'il connaît la chaîne dont vous voulez la largeur, mais pour toutes les chaînes dessinées avec la police .. la hauteur sera de 22px.

si vous utilisez une autre mesure que px, la hauteur sera toujours la même, mais avec cette mesure, tout ce que vous auriez à faire est de convertir la mesure.

user3283552
la source
Certaines lettres peuvent dépasser ou dépasser les limites, voir whatwg.org/specs/web-apps/current-work/images/baselines.png
Octavia Togami
1
Mal formulé mais toujours généralement vrai.
Lodewijk
lisez toutes ces réponses, mais pour mon application simple, c'était la bonne réponse, pt === px
rob
Tout simplement complètement incorrect. Essayez certaines des solutions proposées avec des polices différentes et vous constaterez de grandes différences.
Dale Anderson
-4

Solution approximative:

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;

Ce sera un résultat précis dans une police à espacement unique.

Sonichy
la source