¿Cómo puedes encontrar la altura del texto en un lienzo HTML?

148

La especificación tiene una función context.measureText (texto) que le indicará cuánto ancho necesitaría para imprimir ese texto, pero no puedo encontrar una manera de saber qué tan alto es. Sé que se basa en la fuente, pero no sé convertir una cadena de fuente a una altura de texto.

Swampjohn
fuente
1
Me encantaría saber una mejor manera que la respuesta principal. Si hay algún algoritmo para tomar una fuente de punto arbitrario y encontrar los límites máximo / mínimo, entonces me alegraría saberlo. =)
beatgammit
@tjameson: parece que sí. Vea la respuesta de ellisbben (y mi mejora).
Daniel Earwicker
2
Me pregunto si el carácter Unicode 'FULL BLOCK' (U + 2588) podría usarse como una aproximación multiplicando su ancho por dos.
Daniel F
1
Vale la pena señalar que la respuesta depende un poco de sus requisitos. Por ejemplo, la altura requerida para representar el carácter "a" es diferente a la altura requerida para representar el carácter "y", debido al descendente que se extiende por debajo de la línea de base de la fuente. Las respuestas basadas en HTML a continuación no tienen en cuenta esto y le darán una altura general apropiada para cualquier texto, mientras que la respuesta de @ Noitidart le da una altura más exacta para un texto específico.
Dale Anderson el
2
Recuerda que puedes tener personajes que se vean así M̶̢̹̝͖̦̖̭͕̭̣͆̃̀̅̒̊͌̿ͅ, así que este es un problema realmente complicado, así que resuelve el caso general.
GetFree

Respuestas:

78

ACTUALIZACIÓN : para un ejemplo de este trabajo, utilicé esta técnica en el editor de Carota .

A continuación de la respuesta de ellisbben, aquí hay una versión mejorada para obtener el ascenso y el descenso desde la línea de base, es decir, la misma tmAscenty tmDescentdevuelta por la API GetTextMetric de Win32 . Esto es necesario si desea hacer una ejecución de texto en formato de palabra con tramos en diferentes fuentes / tamaños.

Texto grande sobre lienzo con líneas métricas

La imagen de arriba se generó en un lienzo en Safari, siendo el rojo la línea superior donde se le dijo al lienzo que dibujara el texto, el verde como la línea de base y el azul como la parte inferior (de rojo a azul es la altura completa).

Usando jQuery para ser sucinto:

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

Además de un elemento de texto, agrego un div con el display: inline-blockque puedo configurar su vertical-alignestilo y luego averiguar dónde lo ha colocado el navegador.

Entonces recuperas un objeto con ascent, descenty height(que es solo ascent+ descentpor conveniencia). Para probarlo, vale la pena tener una función que dibuje una línea horizontal:

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

Luego puede ver cómo se coloca el texto en el lienzo en relación con la parte superior, la línea base y la parte inferior:

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
fuente
3
¿Por qué no usar este texto para determinar la altura? abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 Dependiendo de la fuente, puede tener caracteres mucho más altos o más bajos que gy M
omatase
1
@ellisbben vale la pena señalar que los resultados de esto difieren ligeramente de los suyos, aunque no sé por qué. Por ejemplo, el suyo dice Courier New 8pt ==> 12 píxeles de alto, mientras que esto dice: Courier New 8pt ==> 13 píxeles de alto. Agregué la "g" a su método, pero esa no era la diferencia. Uno se pregunta qué valor sería más útil (no necesariamente técnicamente correcto).
Orwellophile
3
Solo pude hacer que las cosas funcionaran correctamente cuando cambié la primera línea de getTextHeight()a var text = $('<span>Hg</span>').css({ 'font-family': fontName, 'font-size' : fontSize });, es decir, agregué el tamaño por separado.
cameron.bracken
1
¿Cómo hacer que funcione para textos que no están en inglés? ver jsfiddle.net/siddjain/6vURk
morpheus
1
gracias ! modificar <div></div>para <div style="white-space : nowrap;"></div>manejar una cadena muy larga
Mickaël Gauvin
40

Puede obtener una aproximación muy cercana de la altura vertical al verificar la longitud de una M. mayúscula

ctx.font='bold 10px Arial';

lineHeight=ctx.measureText('M').width;
Vic Fanberg
fuente
8
¿Cómo nos da el ancho una aproximación de la altura de la línea?
Richard Barker
11
Significan que el ancho de una única 'M' mayúscula en un tamaño de fuente dado es casi igual a la altura de la línea. (No sé si esto es cierto, pero eso es lo que dice la respuesta)
Nathan
2
En realidad, esta es una aproximación bastante decente que utilizo para la creación rápida de prototipos. No es perfecto, pero es una solución del 90%.
Austin D
37

La especificación del lienzo no nos da un método para medir la altura de una cadena. Sin embargo, puede establecer el tamaño de su texto en píxeles y generalmente puede determinar cuáles son los límites verticales con relativa facilidad.

Si necesita algo más preciso, puede arrojar texto en el lienzo y luego obtener datos de píxeles y calcular cuántos píxeles se usan verticalmente. Esto sería relativamente simple, pero no muy eficiente. Podría hacer algo como esto (funciona, pero dibuja algo de texto en su lienzo que desea eliminar):

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

Para Bespin, simulan una altura midiendo el ancho de una 'm' minúscula ... No sé cómo se usa esto, y no recomendaría este método. Aquí está el método Bespin relevante:

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
fuente
28
Dudo que esto sea lo que las personas que escribieron la especificación HTML5 tenían en mente.
Steve Hanov
17
Este es un truco terrible terrible que me encanta. +1
Allain Lalonde
1
No lo entiendo ¿Dónde está la conexión entre el ascenso de la fuente y el ancho de la letra "m"?
kayahr
66
emes una medida de fuente relativa donde un em es igual a la altura de la letra Men el tamaño de fuente predeterminado.
Jerone
1
Correcto, la altura no el ancho ... Todavía estoy confundido acerca de la conexión. Además, creo que los ems son irrelevantes dado que solo nos importa la altura en píxeles.
Prestaul
35

Los navegadores están comenzando a admitir métricas de texto avanzadas , lo que hará que esta tarea sea trivial cuando sea ampliamente compatible:

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

fontHeight obtiene la altura del cuadro delimitador que es constante independientemente de la cadena que se representa. actualHeightes específico de la cadena que se representa.

Especificaciones: https://www.w3.org/TR/2012/CR-2dcontext-20121217/#dom-textmetrics-fontboundingboxascent y las secciones justo debajo.

Estado de soporte (20-agosto-2017):

ZachB
fuente
2
Todos voten en las páginas de errores para que estas características se implementen antes
Jeremiah Rose
21

EDITAR: ¿Estás utilizando transformaciones de lienzo? Si es así, tendrá que rastrear la matriz de transformación. El siguiente método debe medir la altura del texto con la transformación inicial.

EDITAR # 2: Curiosamente, el siguiente código no produce respuestas correctas cuando lo ejecuto en esta página de StackOverflow; Es muy posible que la presencia de algunas reglas de estilo rompa esta función.

El lienzo utiliza fuentes definidas por CSS, por lo que, en teoría, podemos agregar un fragmento de texto con el estilo apropiado al documento y medir su altura. Creo que esto es significativamente más fácil que renderizar texto y luego verificar los datos de píxeles y también debe respetar los ascendentes y descendentes. Mira lo siguiente:

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

Tendrá que asegurarse de obtener el estilo de fuente correcto en el elemento DOM del que mide la altura, pero eso es bastante sencillo; realmente deberías usar algo como

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
fuente
1
+1 Mucho mejor solución IMO. También debería ser posible obtener la posición de la línea de base.
Daniel Earwicker
Han agregado una respuesta que obtiene la línea de base.
Daniel Earwicker
¿Esto funciona? Ni siquiera pensé en pegarlo en un div. Esto probablemente ni siquiera tiene que agregarse al DOM, ¿no?
beatgammit
Soy totalmente ignorante de qué tamaño y posición existen los campos de un nodo cuando no es parte del documento. Me interesaría mucho leer una referencia que aborde eso, si conoces una.
ellisbben
55
+1 para una pantalla llena de código complicado que sería solo context.measureText (texto) .Altura en un universo paralelo con una mejor API de Canvas
rsp
11

¿No es la altura del texto en píxeles igual al tamaño de fuente (en puntos) si define la fuente usando context.font?

Bachalo
fuente
2
Esto es lo que afirma esta fuente: html5canvastutorials.com/tutorials/html5-canvas-text-metrics
Rui Marques
para casos simples: siempre puede analizar la altura, desde el nombre de la fuente: parseInt (ctx.font.split ('') [0] .replace ('px', '')); // cadena de análisis: "10px Verdana"
Sean
Puede usar px, pt, em y% para el tamaño de fuente. Esto es precisamente por qué esta respuesta es engañosa.
Jacksonkr
@Jacksonkr, sí, pero aún así podrías analizarlos y ajustarlos en consecuencia, ¿verdad? ¿O hay una limitación inherente en alguna parte de este enfoque?
Pacerier
@Pacerier La limitación es que puede introducir algunos errores que causan que se quite el cabello. Solo recuerde que los tipos de unidades de mezcla pueden generar código de buggy / spaghetti. Dicho esto, no estoy por encima del hackeo ocasional mientras el riesgo de problemas sea bajo.
Jacksonkr
10

Como sugiere JJ Stiff, puede agregar su texto a un intervalo y luego medir la altura de desplazamiento del intervalo.

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

Como se muestra en HTML5Rocks

Matt Palmerlee
fuente
3
Esta es una buena solución, gracias ... pero no sé por qué si este lapso no se agregó a la página y no se puede ver antes de que aparezca su altura. ¡siempre devuelve la altura como CERO en Chrome y Firefox!
Mustafah
1
Tienes razón, supongo que tiene que agregarse al dom para que ocupe espacio. Aquí hay un JS Fiddle de este trabajo: jsfiddle.net/mpalmerlee/4NfVR/4 También he actualizado el código anterior.
Matt Palmerlee
Usar clientHeight también es una posibilidad. Aunque esta respuesta es una solución al problema, es una solución fea. Lo hice +1 sin embargo.
Daniel F
Esto no considera la altura real del texto visible y generalmente aparece con un margen adicional en la parte superior del texto ...
Ain Tohvri
8

Solo para agregar a la respuesta de Daniel (¡que es genial! ¡Y absolutamente correcto!), Versión sin 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];
}

Acabo de probar el código anterior y funciona muy bien en los últimos Chrome, FF y Safari en Mac.

EDITAR: también agregué el tamaño de fuente y probé con webfont en lugar de la fuente del sistema, funciona de maravilla.

Sinisa
fuente
7

Resolví este problema directamente, utilizando la manipulación de píxeles.

Aquí hay una respuesta gráfica:

Aquí está el código:

    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
fuente
2
Creo que esta solución no tiene en cuenta letras punteadas como minúsculas i y j
wusauarus
3

Estoy escribiendo un emulador de terminal, así que necesitaba dibujar rectángulos alrededor de los caracteres.

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

Obviamente, si quieres la cantidad exacta de espacio que ocupa el personaje, no ayudará. Pero le dará una buena aproximación para ciertos usos.


fuente
3

Aquí hay una función simple. No se necesita biblioteca.

Escribí esta función para obtener los límites superior e inferior en relación con la línea base. Si textBaselineestá ajustado a alphabetic. Lo que hace es crear otro lienzo, y luego dibuja allí, y luego encuentra el píxel superior y el inferior no en blanco. Y ese es el límite superior e inferior. Lo devuelve como relativo, por lo que si la altura es de 20 px y no hay nada debajo de la línea de base, entonces el límite superior es-20 .

Debes proporcionarle caracteres. De lo contrario, le dará 0 altura y 0 ancho, obviamente.

Uso:

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

Aquí está la función:

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` yheight son las cosas útiles en el objeto de retorno.

Aquí hay un ejemplo de uso:

<!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>

Los relativeBoty relativeTopson lo que ves en esta imagen aquí:

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

Noitidart
fuente
3

respuesta de una línea

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

CSS "line-height: normal" está entre 1 y 1.2

lea aquí para más información

Durgpal Singh
fuente
2

Esto es lo que hice en base a algunas de las otras respuestas aquí:

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

Una vez luché con un oso.
fuente
1

En primer lugar, debe establecer la altura de un tamaño de fuente, y luego, de acuerdo con el valor de la altura de la fuente, determinar la altura actual de su texto es cuánto, líneas de texto cruzado, por supuesto, la misma altura de la fuente la fuente debe acumularse, si el texto no excede la altura del cuadro de texto más grande, todos se muestran, de lo contrario, solo se muestra el texto dentro del cuadro de texto. Los valores altos necesitan su propia definición. Cuanto mayor sea la altura predeterminada, mayor será la altura del texto que debe mostrarse e interceptarse.

Después de procesar el efecto (resolver)

Antes de que se procese el efecto (sin resolver)

  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
fuente
1

Descubrí que SOLO PARA ARIAL la forma más simple, rápida y precisa de encontrar la altura del cuadro delimitador es usar el ancho de ciertas letras. Si planea usar una fuente determinada sin permitir que el usuario elija una diferente, puede investigar un poco para encontrar la letra correcta que haga el trabajo para esa fuente.

<!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
fuente
0

Sin embargo, configurar el tamaño de fuente puede no ser práctico, ya que

ctx.font = ''

utilizará el definido por CSS, así como cualquier etiqueta de fuente incrustada. Si usa la fuente CSS, no tiene idea de cuál es la altura de una manera programática, usando el método measureText, que es muy corto de vista. Sin embargo, en otra nota, IE8 sí devuelve el ancho y la altura.


fuente
0

¡Esto funciona 1) para texto multilínea también 2) e incluso en 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};
};
Andrey
fuente
0

Sé que esta es una vieja pregunta respondida, pero para referencia futura me gustaría agregar una solución corta, mínima, solo JS (sin jquery), creo que las personas pueden beneficiarse de:

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
fuente
-3

En situaciones normales, lo siguiente debería funcionar:

var can = CanvasElement.getContext('2d');          //get context
var lineHeight = /[0-9]+(?=pt|px)/.exec(can.font); //get height from font variable
Lodewijk
fuente
55
Completamente y completamente equivocado. Hay una GRAN diferencia entre un tamaño de punto y un tamaño de píxel. El tamaño en puntos da como resultado un texto de diferente tamaño dependiendo del DPI en el que se representa la página, donde, como un tamaño de píxel, no tiene eso en cuenta.
Orvid King
2
¿Conoces el concepto de píxeles de pantalla? Puede que te resulte esclarecedor. Independientemente, pt y px sí indican diferentes alturas. Vale la pena señalar que la fuente puede llenar menos de su "altura", o más, de todos modos. No estoy seguro de si los píxeles del lienzo están escalados, pero supongo que sí. Sin embargo, es una respuesta "incorrecta". Es simple y se puede usar en muchas situaciones.
Lodewijk
-4

Esto es una locura ... La altura del texto es el tamaño de la fuente ... ¿Ninguno de ustedes leyó la documentación?

context.font = "22px arial";

Esto establecerá la altura a 22px.

la única razón por la que hay un ...

context.measureText(string).width

es porque el ancho de la cadena no se puede determinar a menos que conozca la cadena de la que desea el ancho, pero para todas las cadenas dibujadas con la fuente ... la altura será de 22px.

si usa otra medida que no sea px, entonces la altura seguirá siendo la misma, pero con esa medida, lo máximo que tendrá que hacer es convertir la medida.

usuario3283552
fuente
Algunas letras pueden extenderse por encima o por debajo de los límites, vea whatwg.org/specs/web-apps/current-work/images/baselines.png
Octavia Togami
1
Mal redactado pero aún así generalmente cierto.
Lodewijk
lea todas estas respuestas, pero para mi aplicación simple esta fue la respuesta correcta, pt === px
robar el
Simplemente completamente incorrecto. Pruebe algunas de las soluciones propuestas con diferentes fuentes y encontrará grandes discrepancias.
Dale Anderson el
-4

Solución aproximada:

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;

Este será un resultado preciso en una fuente monoespaciada.

sonichy
fuente