194 votes

Comment trouver la hauteur du texte sur un canevas HTML ?

La spécification comporte une fonction context.measureText(text) qui vous indique la largeur nécessaire pour imprimer ce texte, mais je n'arrive pas à trouver un moyen de connaître la hauteur du texte. Je sais que c'est basé sur la police, mais je ne sais pas comment convertir une chaîne de caractères en hauteur de texte.

1 votes

J'aimerais connaître un meilleur moyen que la réponse ci-dessus. S'il existe un algorithme permettant de prendre une police de points arbitraires et de trouver les limites max/min sur celle-ci, je serais très heureux de l'entendre. =)

0 votes

@tjameson - il semble que oui. Voir la réponse d'ellisbben (et le complément que j'y ai apporté).

2 votes

Je me demande si le caractère Unicode "FULL BLOCK" (U+2588) peut être utilisé comme une approximation en multipliant sa largeur par deux.

91voto

Daniel Earwicker Points 63298

UPDATE - pour un exemple de ce fonctionnement, j'ai utilisé cette technique dans la Éditeur de carottes .

Suite à la réponse d'ellisbben, voici une version améliorée qui permet d'obtenir l'ascension et la descente à partir de la ligne de base, c'est-à-dire comme suit tmAscent et tmDescent renvoyée par la fonction Win32 GetTextMetric API. Ceci est nécessaire si vous voulez faire un tirage de texte avec des espaces dans des polices/tailles différentes.

Big Text on canvas with metric lines

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

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-block afin que je puisse définir son vertical-align et ensuite trouver où le navigateur l'a placé.

Vous récupérez donc un objet avec ascent , descent et height (qui est juste ascent + descent pour plus de commodité). Pour le tester, il est intéressant d'avoir une fonction qui dessine 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();
};

Vous pouvez alors 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');

3 votes

Pourquoi ne pas utiliser ce texte pour déterminer la hauteur ? abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 Selon la police de caractères, vous pouvez avoir des caractères beaucoup plus hauts ou plus bas que g et M

0 votes

"Mais par exemple, Brush script et Harlow Solid ont toutes deux des caractères qui plongent plus bas que g, et c'est juste en vérifiant ma liste limitée de polices disponibles pendant quelques minutes. Quoi qu'il en soit, ce n'est pas grave, je me demandais juste s'il y avait une bonne raison de ne pas essayer de couvrir plus de bases (il est vrai que vous ne les couvrirez pas toutes). Au fait, j'adore cette solution, qui fonctionne très bien pour moi.

1 votes

@ellisbben il est intéressant de noter que les résultats de cette opération diffèrent légèrement des vôtres, bien que je ne sache pas pourquoi. Par exemple, le vôtre indique Courier New 8pt ==> 12 pixels de haut, alors que celui-ci indique : Courier New 8pt ==> 13 pixels de haut. J'ai ajouté le "g" à votre méthode, mais ce n'est pas la différence. On peut se demander quelle valeur serait la plus utile (sans être nécessairement techniquement correcte).

70voto

Vic Points 71

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

ctx.font = 'bold 10px Arial';

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

14 votes

Comment la largeur nous donne-t-elle une approximation de la hauteur de la ligne ?

24 votes

Ils signifient que la largeur d'un seul "M" majuscule pour une taille de police donnée est la suivante à propos de 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).

4 votes

Réponse intéressante

43voto

Prestaul Points 31986

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 les limites verticales assez facilement.

Si vous avez besoin de quelque chose de plus précis, vous pourriez jeter 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 pourriez faire quelque chose comme ceci (cela fonctionne, mais dessine du texte sur votre toile 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 recommande pas cette méthode. Voici la méthode Bespin correspondante :

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

33 votes

Je doute que ce soit ce que les personnes qui ont écrit la spécification HTML5 avaient en tête.

22 votes

C'est un terrible hack que j'adore. +1

1 votes

Je ne comprends pas. Où est le rapport entre la montée de la police et la largeur de la lettre "m" ?

24voto

ellisbben Points 3213

EDIT : Utilisez-vous les transformations de la toile ? Si c'est le cas, vous devrez suivre la matrice de transformation. La méthode suivante devrait mesurer la hauteur du texte avec la transformation initiale.

EDIT #2 : Bizarrement, 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 les polices définies par les CSS. En théorie, il suffit donc d'ajouter au document un morceau de texte au style approprié et de mesurer sa hauteur. Je pense que c'est beaucoup plus facile que de rendre du texte et de vérifier ensuite les données en pixels, et cela devrait également respecter les ascendants et les descendants. Regardez 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 du DOM dont vous mesurez la hauteur, mais c'est assez simple ; en réalité, 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.
*/

1 votes

+1 Bien meilleure solution IMO. Il devrait être possible d'obtenir la position de la ligne de base également.

0 votes

J'ai ajouté une réponse qui obtient la ligne de base.

0 votes

Est-ce que ça marche ? Je n'ai même pas pensé à le coller dans un div. Il n'est probablement même pas nécessaire de l'ajouter au DOM, non ?

12voto

Ionuț G. Stan Points 62482

Je n'en suis pas sûr, mais je me souviens que Dion Almaer a dit quelque chose à ce sujet dans une présentation de Google IO à propos de Mozilla Bespin . Il s'agissait de retourner la lettre M et de mesurer sa largeur. Mais je peux me tromper.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X