Buscar..


Dibujo de texto

Dibujar en lienzo no se limita a formas e imágenes. También puede dibujar texto al lienzo.

Para dibujar texto en el lienzo, obtenga una referencia al lienzo y luego llame al método fillText en el contexto.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.fillText("My text", 0, 0);

Los tres argumentos requeridos que se pasan a fillText son:

  1. El texto que desea mostrar.
  2. La posición horizontal (eje x)
  3. La posición vertical (eje y)

Además, hay un cuarto argumento opcional , que puede utilizar para especificar el ancho máximo de su texto en píxeles. En el siguiente ejemplo, el valor de 200 restringe el ancho máximo del texto a 200 px:

ctx.fillText("My text", 0, 0, 200);

Resultado:

Ejemplo de resultado del uso del método fillText en lienzo

También puede dibujar texto sin relleno, y solo un contorno en su lugar, utilizando el método strokeText :

ctx.strokeText("My text", 0, 0);

Resultado:

Ejemplo de resultado del uso del método strokeText en lienzo

Sin las propiedades de formato de fuente aplicadas, el lienzo representa el texto a 10px en sans-serif de forma predeterminada, lo que dificulta ver la diferencia entre el resultado de los métodos fillText y strokeText . Consulte el ejemplo de Formato de texto para obtener detalles sobre cómo aumentar el tamaño del texto y aplicar otros cambios estéticos al texto.

Formato de texto

El formato de fuente predeterminado proporcionado por los métodos fillText y strokeText no es muy atractivo estéticamente. Afortunadamente, la API del lienzo proporciona propiedades para formatear texto.

Usando la propiedad de font puede especificar:

  • Estilo de fuente
  • variante de fuente
  • peso de fuente
  • tamaño de fuente / altura de linea
  • Familia tipográfica

Por ejemplo:

ctx.font = "italic small-caps bold 40px Helvetica, Arial, sans-serif";
ctx.fillText("My text", 20, 50);

Resultado:

Ejemplo de resultado de especificar propiedades de fuente

Usando la propiedad textAlign , también puede cambiar la alineación del texto a:

  • izquierda
  • centrar
  • Correcto
  • final (igual que a la derecha)
  • inicio (igual a la izquierda)

Por ejemplo:

ctx.textAlign = "center";

Envolver el texto en párrafos

Native Canvas API no tiene un método para ajustar el texto en la siguiente línea cuando se alcanza el ancho máximo deseado. Este ejemplo envuelve el texto en párrafos.

function wrapText(text, x, y, maxWidth, fontSize, fontFace){
  var firstY=y;
  var words = text.split(' ');
  var line = '';
  var lineHeight=fontSize*1.286; // a good approx for 10-18px sizes

  ctx.font=fontSize+" "+fontFace;
  ctx.textBaseline='top';

  for(var n = 0; n < words.length; n++) {
    var testLine = line + words[n] + ' ';
    var metrics = ctx.measureText(testLine);
    var testWidth = metrics.width;
    if(testWidth > maxWidth) {
      ctx.fillText(line, x, y);
      if(n<words.length-1){
          line = words[n] + ' ';
          y += lineHeight;
      }
    }
    else {
      line = testLine;
    }
  }
  ctx.fillText(line, x, y);
}

Dibuja párrafos de texto en formas irregulares

Este ejemplo dibuja párrafos de texto en cualquier parte del lienzo que tenga píxeles opacos.

Funciona encontrando el siguiente bloque de píxeles opacos que es lo suficientemente grande como para contener la siguiente palabra especificada y rellenando ese bloque con la palabra especificada.

Los píxeles opacos pueden provenir de cualquier fuente: comandos de dibujo de ruta y / o imágenes.

introduzca la descripción de la imagen aquí

<!doctype html>
<html>
<head>
<style>
    body{ background-color:white; padding:10px; }
    #canvas{border:1px solid red;}
</style>
<script>
window.onload=(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;

    var fontsize=12;
    var fontface='verdana';
    var lineHeight=parseInt(fontsize*1.286);

    var text='It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way; in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.';
    var words=text.split(' ');
    var wordWidths=[];
    ctx.font=fontsize+'px '+fontface;
    for(var i=0;i<words.length;i++){ wordWidths.push(ctx.measureText(words[i]).width); }
    var spaceWidth=ctx.measureText(' ').width;
    var wordIndex=0
    var data=[];

    // Demo: draw Heart 
    // Note: the shape can be ANY opaque drawing -- even an image
    ctx.scale(3,3);
    ctx.beginPath();
    ctx.moveTo(75,40);
    ctx.bezierCurveTo(75,37,70,25,50,25);
    ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
    ctx.bezierCurveTo(20,80,40,102,75,120);
    ctx.bezierCurveTo(110,102,130,80,130,62.5);
    ctx.bezierCurveTo(130,62.5,130,25,100,25);
    ctx.bezierCurveTo(85,25,75,37,75,40);
    ctx.fillStyle='red';
    ctx.fill();
    ctx.setTransform(1,0,0,1,0,0);

    // fill heart with text
    ctx.fillStyle='white';
    var imgDataData=ctx.getImageData(0,0,cw,ch).data;
    for(var i=0;i<imgDataData.length;i+=4){
        data.push(imgDataData[i+3]);
    }
    placeWords();   

    // draw words sequentially into next available block of 
    //    available opaque pixels 
    function placeWords(){
        var sx=0;
        var sy=0;
        var y=0;
        var wordIndex=0;
        ctx.textBaseline='top';
        while(y<ch && wordIndex<words.length){
            sx=0;
            sy=y;
            var startingIndex=wordIndex;
            while(sx<cw && wordIndex<words.length){
                var x=getRect(sx,sy,lineHeight);
                var available=x-sx;
                var spacer=spaceWidth;  // spacer=0 to have no left margin
                var w=spacer+wordWidths[wordIndex];
                while(available>=w){
                    ctx.fillText(words[wordIndex],spacer+sx,sy);
                    sx+=w;
                    available-=w;
                    spacer=spaceWidth;
                    wordIndex++;
                    w=spacer+wordWidths[wordIndex];
                }
                sx=x+1;
            }
            y=(wordIndex>startingIndex)?y+lineHeight:y+1;
        }
    }

    // find a rectangular block of opaque pixels
    function getRect(sx,sy,height){
        var x=sx;
        var y=sy;
        var ok=true;
        while(ok){
            if(data[y*cw+x]<250){ok=false;}
            y++;
            if(y>=sy+height){
                y=sy;
                x++;
                if(x>=cw){ok=false;}
            }
        }
        return(x);
    }

}); // end $(function(){});
</script>
</head>
<body>
    <h4>Note: the shape must be closed and alpha>=250 inside</h4>
    <canvas id="canvas" width=400 height=400></canvas>
</body>
</html>

Rellena texto con una imagen

Este ejemplo llena el texto con una imagen específica.

¡Importante! La imagen especificada debe estar completamente cargada antes de llamar a esta función o el dibujo fallará. Use image.onload para asegurarse de que la imagen esté completamente cargada.

introduzca la descripción de la imagen aquí

function drawImageInsideText(canvas,x,y,img,text,font){
    var c=canvas.cloneNode();
    var ctx=c.getContext('2d');
    ctx.font=font;
    ctx.fillText(text,x,y);
    ctx.globalCompositeOperation='source-atop';
    ctx.drawImage(img,0,0);
    canvas.getContext('2d').drawImage(c,0,0);
}

Representación de texto a lo largo de un arco.

Este ejemplo muestra cómo representar texto a lo largo de un arco. Incluye cómo puedes agregar funcionalidad a CanvasRenderingContext2D al extender su prototipo.

Este ejemplo se deriva de la respuesta de Stackoverflow Circular Text .


Representación de ejemplo

Ejemplo de texto circular


Código de ejemplo

El ejemplo agrega 3 nuevas funciones de representación de texto al prototipo de contexto 2D.

  • ctx.fillCircleText (texto, x, y, radio, inicio, final, avance);
  • ctx.strokeCircleText (texto, x, y, radio, inicio, final, adelante);
  • ctx.measureCircleText (texto, radio);
(function(){
    const FILL = 0;        // const to indicate filltext render
    const STROKE = 1;
    var renderType = FILL; // used internal to set fill or stroke text
    const multiplyCurrentTransform = true; // if true Use current transform when rendering
                                           // if false use absolute coordinates which is a little quicker
                                           // after render the currentTransform is restored to default transform
                                           
      

    // measure circle text
    // ctx: canvas context
    // text: string of text to measure
    // r: radius in pixels
    //
    // returns the size metrics of the text
    //
    // width: Pixel width of text
    // angularWidth : angular width of text in radians
    // pixelAngularSize : angular width of a pixel in radians
    var measure = function(ctx, text, radius){        
        var textWidth = ctx.measureText(text).width; // get the width of all the text
        return {
            width               : textWidth,
            angularWidth        : (1 / radius) * textWidth,
            pixelAngularSize    : 1 / radius
        };
    }

    // displays text along a circle
    // ctx: canvas context
    // text: string of text to measure
    // x,y: position of circle center
    // r: radius of circle in pixels
    // start: angle in radians to start. 
    // [end]: optional. If included text align is ignored and the text is 
    //        scaled to fit between start and end;
    // [forward]: optional default true. if true text direction is forwards, if false  direction is backward
    var circleText = function (ctx, text, x, y, radius, start, end, forward) {
        var i, textWidth, pA, pAS, a, aw, wScale, aligned, dir, fontSize;
        if(text.trim() === "" || ctx.globalAlpha === 0){ // dont render empty string or transparent
            return;
        }
        if(isNaN(x) || isNaN(y) || isNaN(radius) || isNaN(start) || (end !== undefined && end !== null && isNaN(end))){ // 
            throw TypeError("circle text arguments requires a number for x,y, radius, start, and end.")
        }
        aligned = ctx.textAlign;        // save the current textAlign so that it can be restored at end
        dir = forward ? 1 : forward === false ? -1 : 1;  // set dir if not true or false set forward as true  
        pAS = 1 / radius;               // get the angular size of a pixel in radians
        textWidth = ctx.measureText(text).width; // get the width of all the text
        if (end !== undefined && end !== null) { // if end is supplied then fit text between start and end
            pA = ((end - start) / textWidth) * dir;
            wScale = (pA / pAS) * dir;
        } else {                 // if no end is supplied correct start and end for alignment
            // if forward is not given then swap top of circle text to read the correct direction
            if(forward === null || forward === undefined){
                if(((start % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2) > Math.PI){
                    dir = -1;
                }
            }
            pA = -pAS * dir ;
            wScale = -1 * dir;
            switch (aligned) {
            case "center":       // if centered move around half width
                start -= (pA * textWidth )/2;
                end = start + pA * textWidth;
                break;
            case "right":// intentionally falls through to case "end"
            case "end":
                end = start;
                start -= pA * textWidth;
                break;
            case "left":  // intentionally falls through to case "start"
            case "start":
                end = start + pA * textWidth;
            }
        }

        ctx.textAlign = "center";                     // align for rendering
        a = start;                                    // set the start angle
        for (var i = 0; i < text.length; i += 1) {    // for each character
            aw = ctx.measureText(text[i]).width * pA; // get the angular width of the text
            var xDx = Math.cos(a + aw / 2);           // get the yAxies vector from the center x,y out
            var xDy = Math.sin(a + aw / 2);
            if(multiplyCurrentTransform){ // transform multiplying current transform
                ctx.save();
                if (xDy < 0) { // is the text upside down. If it is flip it
                    ctx.transform(-xDy * wScale, xDx * wScale, -xDx, -xDy, xDx * radius + x, xDy * radius + y);
                } else {
                    ctx.transform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y);
                }
            }else{
                if (xDy < 0) { // is the text upside down. If it is flip it
                    ctx.setTransform(-xDy * wScale, xDx * wScale, -xDx, -xDy, xDx * radius + x, xDy * radius + y);
                } else {
                    ctx.setTransform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y);
                }
            }
            if(renderType === FILL){
                ctx.fillText(text[i], 0, 0);    // render the character
            }else{                    
                ctx.strokeText(text[i], 0, 0);  // render the character
            }
            if(multiplyCurrentTransform){  // restore current transform
                ctx.restore();
            }
            a += aw;                     // step to the next angle
        }
        // all done clean up.
        if(!multiplyCurrentTransform){
            ctx.setTransform(1, 0, 0, 1, 0, 0); // restore the transform
        }
        ctx.textAlign = aligned;            // restore the text alignment
    }
    // define fill text
    var fillCircleText = function(text, x, y, radius, start, end, forward){
        renderType = FILL;
        circleText(this, text, x, y, radius, start, end, forward);
    }
    // define stroke text
    var strokeCircleText = function(text, x, y, radius, start, end, forward){
        renderType = STROKE;
        circleText(this, text, x, y, radius, start, end, forward);
    }
    // define measure text
    var measureCircleTextExt = function(text,radius){
        return measure(this, text, radius);
    }
    // set the prototypes
    CanvasRenderingContext2D.prototype.fillCircleText = fillCircleText;
    CanvasRenderingContext2D.prototype.strokeCircleText = strokeCircleText;
    CanvasRenderingContext2D.prototype.measureCircleText = measureCircleTextExt;  
})();

Descripciones de funciones

Este ejemplo agrega 3 funciones al CanvasRenderingContext2D prototype . fillCircleText , strokeCircleText , y measureCircleText

CanvasRenderingContext2D.fillCircleText (texto, x, y, radio, inicio, [final, [adelante]]);

CanvasRenderingContext2D.strokeCircleText (texto, x, y, radio, inicio, [final, [adelante]]);

  • texto: Texto para renderizar como cadena.
  • x , y : Posición del centro del círculo como números.
  • radio: radio del circulo en pixeles
  • inicio: ángulo en radianes para comenzar.
  • [final]: opcional. Si se incluye, se ignora ctx.textAlign y el texto se escala para que se ajuste entre el inicio y el final.
  • [adelante]: opción predeterminada 'verdadero'. si la dirección verdadera del texto es hacia delante, si la dirección "falsa" es hacia atrás.

Ambas funciones utilizan textBaseline para colocar el texto verticalmente alrededor del radio. Para obtener los mejores resultados, use ctx.TextBaseline .

Las funciones lanzarán un TypeError es cualquiera de los argumentos numéricos como NaN.

Si el argumento de text recorta a una cadena vacía o ctx.globalAlpha = 0 la función simplemente se ctx.globalAlpha = 0 y no hace nada.

CanvasRenderingContext2D.measureCircleText (texto, radio);

 - **text:** String of text to measure.
 - **radius:** radius of circle in pixels.

Devuelve un objeto que contiene varias métricas de tamaño para representar texto circular

   - **width:** Pixel width of text as it would normaly be rendered
   - **angularWidth:** angular width of text in radians.
   - **pixelAngularSize:** angular width of a pixel in radians.

Ejemplos de uso

const rad = canvas.height * 0.4;
const text = "Hello circle TEXT!";
const fontSize = 40;
const centX = canvas.width / 2;
const centY = canvas.height / 2;
ctx.clearRect(0,0,canvas.width,canvas.height)

ctx.font = fontSize + "px verdana";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillStyle = "#000";
ctx.strokeStyle = "#666";

// Text under stretched from Math.PI to 0 (180 - 0 deg)
ctx.fillCircleText(text, centX, centY, rad, Math.PI, 0);

// text over top centered at Math.PI * 1.5 ( 270 deg)
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);

// text under top centered at Math.PI * 1.5 ( 270 deg)
ctx.textBaseline = "top";
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);


// text over top centered at Math.PI * 1.5 ( 270 deg)
ctx.textBaseline = "middle";
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);


// Use measureCircleText to get angular size
var circleTextMetric = ctx.measureCircleText("Text to measure", rad);
console.log(circleTextMetric.width);            // width of text if rendered normally
console.log(circleTextMetric.angularWidth);     // angular width of text
console.log(circleTextMetric.pixelAngularSize); // angular size of a pixel    



// Use measure text to draw a arc around the text
ctx.textBaseline = "middle";
var width = ctx.measureCircleText(text, rad).angularWidth;    
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);

// render the arc around the text
ctx.strokeStyle= "red";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(centX, centY, rad + fontSize / 2,Math.PI * 1.5 - width/2,Math.PI*1.5 + width/2);
ctx.arc(centX, centY, rad - fontSize / 2,Math.PI * 1.5 + width/2,Math.PI*1.5 - width/2,true);
ctx.closePath();
ctx.stroke();


NOTA: El texto representado es solo una aproximación de texto circular. Por ejemplo, si se representan dos l, las dos líneas no serán paralelas, pero si representa una "H", los dos bordes serán paralelos. Esto se debe a que cada carácter se representa lo más cerca posible de la dirección requerida, en lugar de que cada píxel se transforme correctamente para crear un texto circular.

NOTA: const multiplyCurrentTransform = true; definido en este ejemplo se utiliza para establecer el método de transformación utilizado. Si es false la transformación para la representación de texto circular es absoluta y no depende del estado de transformación actual. El texto no se verá afectado por ninguna escala anterior, rotación o transformación. Esto aumentará el rendimiento de la función de procesamiento, después de que la función se llame, la transformación se establecerá en el setTransform(1,0,0,1,0,0) predeterminado setTransform(1,0,0,1,0,0)

Si multiplyCurrentTransform = true (establecido como predeterminado en este ejemplo), el texto usará la transformación actual para que el texto se pueda escalar, inclinar, rotar, etc., pero modificar la transformación actual antes de llamar a las funciones fillCircleText y strokeCircleText . Dependiendo del estado actual del contexto 2D, esto puede ser algo más lento que multiplyCurrentTransform = false

Texto en curva, beziers cúbicos y cuadráticos.

introduzca la descripción de la imagen aquí

textOnCurve (texto, desplazamiento, x1, y1, x2, y2, x3, y3, x4, y4)

Representa el texto en curvas cuadráticas y cúbicas.

  • text es el texto a representar
  • distancia de offset desde el inicio de la curva hasta el texto> = 0
  • x1,y1 - x3,y3 puntos de curva cuadrática o
  • x1,y1 - x4,y4 puntos de curva cúbica o

Ejemplo de uso:

textOnCurve("Hello world!",50,100,100,200,200,300,100); // draws text on quadratic curve
                                                        // 50 pixels from start of curve


textOnCurve("Hello world!",50,100,100,200,200,300,100,400,200); 
                                                        // draws text on cubic curve
                                                        // 50 pixels from start of curve

La función y la función de ayudante curver

// pass 8 values for cubic bezier
// pass 6 values for quadratic
// Renders text from start of curve
var textOnCurve = function(text,offset,x1,y1,x2,y2,x3,y3,x4,y4){
    ctx.save();
    ctx.textAlign = "center";
    var widths = [];
    for(var i = 0; i < text.length; i ++){
        widths[widths.length] = ctx.measureText(text[i]).width;
    }
    var ch = curveHelper(x1,y1,x2,y2,x3,y3,x4,y4);
    var pos = offset;
    var cpos = 0;

    for(var i = 0; i < text.length; i ++){
        pos += widths[i] / 2;
        cpos = ch.forward(pos);
        ch.tangent(cpos);
        ctx.setTransform(ch.vect.x, ch.vect.y, -ch.vect.y, ch.vect.x, ch.vec.x, ch.vec.y);
        ctx.fillText(text[i],0,0);     

        pos += widths[i] / 2;
    }
    ctx.restore();
}

La función de ayuda de la curva está diseñada para aumentar el rendimiento de los puntos de búsqueda en el bezier.

// helper function locates points on bezier curves.
function curveHelper(x1, y1, x2, y2, x3, y3, x4, y4){
    var tx1, ty1, tx2, ty2, tx3, ty3, tx4, ty4;
    var a,b,c,u;
    var vec,currentPos,vec1,vect;
    vec = {x:0,y:0};
    vec1 = {x:0,y:0};
    vect = {x:0,y:0};
    quad = false;
    currentPos = 0;
    currentDist = 0;
    if(x4 === undefined || x4 === null){
        quad = true;
        x4 = x3;
        y4 = y3;
    }
    var estLen = Math.sqrt((x4 - x1) * (x4 - x1) + (y4 - y1) * (y4 - y1));
    var onePix = 1 / estLen;
    function posAtC(c){
        tx1 = x1; ty1 = y1;
        tx2 = x2; ty2 = y2;
        tx3 = x3; ty3 = y3;
        tx1 += (tx2 - tx1) * c;
        ty1 += (ty2 - ty1) * c;
        tx2 += (tx3 - tx2) * c;
        ty2 += (ty3 - ty2) * c;
        tx3 += (x4 - tx3) * c;
        ty3 += (y4 - ty3) * c;
        tx1 += (tx2 - tx1) * c;
        ty1 += (ty2 - ty1) * c;
        tx2 += (tx3 - tx2) * c;
        ty2 += (ty3 - ty2) * c;
        vec.x = tx1 + (tx2 - tx1) * c;
        vec.y = ty1 + (ty2 - ty1) * c;    
        return vec;
    }
    function posAtQ(c){
        tx1 = x1; ty1 = y1;
        tx2 = x2; ty2 = y2;
        tx1 += (tx2 - tx1) * c;
        ty1 += (ty2 - ty1) * c;
        tx2 += (x3 - tx2) * c;
        ty2 += (y3 - ty2) * c;
        vec.x = tx1 + (tx2 - tx1) * c;
        vec.y = ty1 + (ty2 - ty1) * c;
        return vec;
    }    
    function forward(dist){
        var step;
        helper.posAt(currentPos);

        while(currentDist < dist){
            vec1.x = vec.x;
            vec1.y = vec.y;            
            currentPos += onePix;
            helper.posAt(currentPos);
            currentDist += step = Math.sqrt((vec.x - vec1.x) * (vec.x - vec1.x) + (vec.y - vec1.y) * (vec.y - vec1.y));

        }
        currentPos -= ((currentDist - dist) / step) * onePix
        currentDist -= step;
        helper.posAt(currentPos);
        currentDist += Math.sqrt((vec.x - vec1.x) * (vec.x - vec1.x) + (vec.y - vec1.y) * (vec.y - vec1.y));
        return currentPos;
    }
    
    function tangentQ(pos){
        a = (1-pos) * 2;
        b = pos * 2;
        vect.x = a * (x2 - x1) + b * (x3 - x2);
        vect.y = a * (y2 - y1) + b * (y3 - y2);       
        u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
        vect.x /= u;
        vect.y /= u;        
    }
    function tangentC(pos){
        a  = (1-pos)
        b  = 6 * a * pos;       
        a *= 3 * a;                  
        c  = 3 * pos * pos; 
        vect.x  = -x1 * a + x2 * (a - b) + x3 * (b - c) + x4 * c;
        vect.y  = -y1 * a + y2 * (a - b) + y3 * (b - c) + y4 * c;
        u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
        vect.x /= u;
        vect.y /= u;
    }  
    var helper = {
        vec : vec,
        vect : vect,
        forward : forward,
    }
    if(quad){
        helper.posAt = posAtQ;
        helper.tangent = tangentQ;
    }else{
        helper.posAt = posAtC;
        helper.tangent = tangentC;
    }
    return helper
}

Texto justificado

Este ejemplo muestra el texto justificado. Agrega funcionalidad adicional a CanvasRenderingContext2D al extender su prototipo o como un objeto global justifiedText (opcional, ver Nota A).


Ejemplo de renderizado.

introduzca la descripción de la imagen aquí
El código para representar esta imagen se encuentra en los ejemplos de uso en la parte inferior .


El ejemplo

La función como una función anónima inmediatamente invocada.

(function(){
    const FILL = 0;        // const to indicate filltext render
    const STROKE = 1;
    const MEASURE = 2;
    var renderType = FILL; // used internal to set fill or stroke text
    
    var maxSpaceSize = 3; // Multiplier for max space size. If greater then no justificatoin applied
    var minSpaceSize = 0.5; // Multiplier for minimum space size
    var renderTextJustified = function(ctx,text,x,y,width){
        var words, wordsWidth, count, spaces, spaceWidth, adjSpace, renderer, i, textAlign, useSize, totalWidth;
        textAlign = ctx.textAlign; // get current align settings
        ctx.textAlign = "left";
        wordsWidth = 0;
        words = text.split(" ").map(word => {
            var w = ctx.measureText(word).width;                
            wordsWidth += w;
            return {
                width : w,
                word : word,
            };
        });
        // count = num words, spaces = number spaces, spaceWidth normal space size
        // adjSpace new space size >= min size. useSize Resulting space size used to render
        count = words.length;
        spaces = count - 1;
        spaceWidth = ctx.measureText(" ").width;
        adjSpace = Math.max(spaceWidth * minSpaceSize, (width - wordsWidth) / spaces);
        useSize = adjSpace > spaceWidth * maxSpaceSize ? spaceWidth : adjSpace;
        totalWidth = wordsWidth + useSize * spaces
        if(renderType === MEASURE){ // if measuring return size
            ctx.textAlign = textAlign;
            return totalWidth;
        }
        renderer = renderType === FILL ? ctx.fillText.bind(ctx) : ctx.strokeText.bind(ctx); // fill or stroke
        switch(textAlign){
            case "right":
                x -= totalWidth;
                break;
            case "end":
                x += width - totalWidth;
                break;
            case "center": // intentional fall through to default
                x -= totalWidth / 2;                     
            default:
        }
        if(useSize === spaceWidth){ // if space size unchanged
            renderer(text,x,y);
        } else {
            for(i = 0; i < count; i += 1){
                renderer(words[i].word,x,y);
                x += words[i].width;
                x += useSize;
            }
        }
        ctx.textAlign = textAlign;
    }
    // Parse vet and set settings object.
    var justifiedTextSettings = function(settings){
        var min,max;
        var vetNumber = (num, defaultNum) => {
            num = num !== null && num !== null && !isNaN(num) ? num : defaultNum;
            if(num < 0){
                num = defaultNum;
            }
            return num;
        }
        if(settings === undefined || settings === null){
            return;
        }
        max = vetNumber(settings.maxSpaceSize, maxSpaceSize);
        min = vetNumber(settings.minSpaceSize, minSpaceSize);
        if(min > max){
            return;
        }
        minSpaceSize = min;
        maxSpaceSize = max;
    }
    // define fill text
    var fillJustifyText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        renderType = FILL;
        renderTextJustified(this, text, x, y, width);
    }
    // define stroke text
    var strokeJustifyText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        renderType = STROKE;
        renderTextJustified(this, text, x, y, width);
    }
    // define measure text
    var measureJustifiedText = function(text, width, settings){
        justifiedTextSettings(settings);
        renderType = MEASURE;
        return renderTextJustified(this, text, 0, 0, width);
    }
    // code point A
    // set the prototypes
    CanvasRenderingContext2D.prototype.fillJustifyText = fillJustifyText;
    CanvasRenderingContext2D.prototype.strokeJustifyText = strokeJustifyText;
    CanvasRenderingContext2D.prototype.measureJustifiedText = measureJustifiedText;  
    // code point B
    
    // optional code if you do not wish to extend the CanvasRenderingContext2D prototype
    /* Uncomment from here to the closing comment
    window.justifiedText = {
        fill : function(ctx, text, x, y, width, settings){
            justifiedTextSettings(settings);
            renderType = FILL;
            renderTextJustified(ctx, text, x, y, width);
        },
        stroke : function(ctx, text, x, y, width, settings){
            justifiedTextSettings(settings);
            renderType = STROKE;
            renderTextJustified(ctx, text, x, y, width);
        },
        measure : function(ctx, text, width, settings){
            justifiedTextSettings(settings);
            renderType = MEASURE;
            return renderTextJustified(ctx, text, 0, 0, width);
        }
    }
    to here*/
})();

Nota A: Si no desea extender el prototipo de CanvasRenderingContext2D del ejemplo todo el código entre // code point A y // code point B y elimine el comentario del código marcado /* Uncomment from here to the closing comment


Cómo utilizar

Se agregan tres funciones a CanvasRenderingContext2D y están disponibles para todos los objetos de contexto 2D creados.

  • ctx.fillJustifyText (texto, x, y, ancho, [configuración]);
  • ctx.strokeJustifyText (texto, x, y, ancho, [configuración]);
  • ctx.measureJustifiedText (texto, ancho, [configuración]);

Función de texto de relleno y trazo Texto de relleno o trazo y use los mismos argumentos. measureJustifiedText devolverá el ancho real al que se representaría el texto. Esto puede ser igual, menor o mayor que el width del argumento dependiendo de la configuración actual.

Nota: los argumentos dentro de [ y ] son opcionales.

Argumentos de función

  • texto: cadena que contiene el texto que se va a representar.

  • x, y: Coordina para representar el texto en.

  • ancho: ancho del texto justificado. El texto aumentará / disminuirá los espacios entre las palabras para ajustarse al ancho. Si el espacio entre palabras es mayor que maxSpaceSize (predeterminado = 6), se utilizará el espaciado normal y el texto no llenará el ancho requerido. Si el espaciado es menor que minSpaceSize (predeterminado = 0.5) el espaciado normal de tiempo, entonces se usa el tamaño de espacio mínimo y el texto sobrepasará el ancho solicitado

  • Configuraciones: Opcional. Objeto que contiene tamaños de espacio mínimo y máximo.

El argumento de settings es opcional y, si no se incluye, la representación de texto usará la última configuración definida o la predeterminada (que se muestra a continuación).

Tanto el mínimo como el máximo son los tamaños mínimo y máximo para el carácter [espacio] que separa las palabras. El valor predeterminado de maxSpaceSize = 6 significa que cuando el espacio entre los caracteres es> 63 * ctx.measureText ("") .width el texto no se justificará. Si el texto que se va a justificar tiene espacios menores que minSpaceSize = 0.5 (valor predeterminado 0.5) * ctx.measureText(" ").width el espaciado se establecerá en minSpaceSize * ctx.measureText(" ").width y el texto resultante se rebasará El ancho justificante.

Se aplican las siguientes reglas, min y max deben ser números. Si no, entonces los valores asociados no serán cambiados. Si minSpaceSize es más grande que maxSpaceSize ambas configuraciones de entrada no son válidas y el máximo máximo no se cambiará.

Ejemplo de objeto de configuración con valores predeterminados

settings = { 
    maxSpaceSize : 6;   // Multiplier for max space size. 
    minSpaceSize : 0.5; // Multiplier for minimum space size
};

NOTA: Estas funciones de texto introducen un cambio de comportamiento sutil para la propiedad textAlign del contexto 2D. 'Izquierda', 'derecha', 'centro' y 'inicio' se comportan como se espera, pero 'final' no se alineará desde la derecha del argumento de la función x sino desde la derecha de x + width

Nota: las configuraciones (tamaño de espacio mínimo y máximo) son globales para todos los objetos de contexto 2D.


Ejemplos de uso

var i = 0;
text[i++] = "This text is aligned from the left of the canvas."; 
text[i++] = "This text is near the max spacing size"; 
text[i++] = "This text is way too short."; 
text[i++] = "This text is too long for the space provied and will overflow#";
text[i++] = "This text is aligned using 'end' and starts at x + width"; 
text[i++] = "This text is near the max spacing size"; 
text[i++] = "This text is way too short."; 
text[i++] = "#This text is too long for the space provied and will overflow";
text[i++] = "This is aligned with 'center' and is placed from the center"; 
text[i++] = "This text is near the max spacing size"; 
text[i++] = "This text is way too short."; 
text[i++] = "This text is just too long for the space provied and will overflow";

// ctx is the 2d context
// canvas is the canvas

ctx.clearRect(0,0,w,h);
ctx.font = "25px arial";
ctx.textAlign = "center"
var left = 20;
var center = canvas.width / 2;
var width = canvas.width-left*2;
var y = 40;
var size = 16;
var i = 0;
ctx.fillText("Justified text examples.",center,y);
y+= 40;
ctx.font = "14px arial";
ctx.textAlign = "left"
var ww = ctx.measureJustifiedText(text[0], width);
var setting = {
    maxSpaceSize : 6,
    minSpaceSize : 0.5
}
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(left,y - size * 2);
ctx.lineTo(left, y + size * 15);
ctx.moveTo(canvas.width - left,y - size * 2);
ctx.lineTo(canvas.width - left, y + size * 15);
ctx.stroke();
ctx.textAlign = "left";
ctx.fillStyle = "red";
ctx.fillText("< 'left' aligned",left,y - size)
ctx.fillStyle = "black";
ctx.fillJustifyText(text[i++], left, y, width, setting);  // settings is remembered
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
y += 2.3*size;
ctx.fillStyle = "red";
ctx.fillText("< 'end' aligned from x plus the width -------------------->",left,y - size)
ctx.fillStyle = "black";
ctx.textAlign = "end";
ctx.fillJustifyText(text[i++], left, y, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);

y += 40;
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(center,y - size * 2);
ctx.lineTo(center, y + size * 5);
ctx.stroke();
ctx.textAlign = "center";
ctx.fillStyle = "red";
ctx.fillText("'center' aligned",center,y - size)
ctx.fillStyle = "black";
ctx.fillJustifyText(text[i++], center, y, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);

Párrafos justificados.

Representa el texto como párrafos justificados. REQUIERE el ejemplo de texto justificado


Ejemplo de render

introduzca la descripción de la imagen aquí
El párrafo superior tiene setting.compact = true y bottom false y el interlineado es 1.2 en lugar del 1.5 predeterminado. Representado por el código de uso de la parte inferior de este ejemplo.


Código de ejemplo

 // Requires justified text extensions 
(function(){
    // code point A
    if(typeof CanvasRenderingContext2D.prototype.fillJustifyText !== "function"){
        throw new ReferenceError("Justified Paragraph extension missing requiered CanvasRenderingContext2D justified text extension");
    }
    var maxSpaceSize = 3; // Multiplier for max space size. If greater then no justificatoin applied
    var minSpaceSize = 0.5; // Multiplier for minimum space size   
    var compact = true; // if true then try and fit as many words as possible. If false then try to get the spacing as close as possible to normal
    var lineSpacing = 1.5; // space between lines
    const noJustifySetting = {  // This setting forces justified text off. Used to render last line of paragraph.
        minSpaceSize : 1,
        maxSpaceSize : 1,
    }

    // Parse vet and set settings object.
    var justifiedTextSettings = function(settings){
        var min, max;
        var vetNumber = (num, defaultNum) => {
            num = num !== null && num !== null && !isNaN(num) ? num : defaultNum;
            return num < 0 ? defaultNum : num;
        }
        if(settings === undefined || settings === null){ return; }
        compact = settings.compact === true ? true : settings.compact === false ? false : compact;
        max = vetNumber(settings.maxSpaceSize, maxSpaceSize);
        min = vetNumber(settings.minSpaceSize, minSpaceSize);
        lineSpacing = vetNumber(settings.lineSpacing, lineSpacing);
        if(min > max){ return; }
        minSpaceSize = min;
        maxSpaceSize = max;
    }        
    var getFontSize = function(font){  // get the font size. 
        var numFind = /[0-9]+/;
        var number = numFind.exec(font)[0];
        if(isNaN(number)){
            throw new ReferenceError("justifiedPar Cant find font size");
        }
        return Number(number);
    }
    function justifiedPar(ctx, text, x, y, width, settings, stroke){
        var spaceWidth, minS, maxS, words, count, lines, lineWidth, lastLineWidth, lastSize, i, renderer, fontSize, adjSpace, spaces, word, lineWords, lineFound;
        spaceWidth = ctx.measureText(" ").width;
        minS = spaceWidth * minSpaceSize;
        maxS = spaceWidth * maxSpaceSize;
        words = text.split(" ").map(word => {  // measure all words.
            var w = ctx.measureText(word).width;                
            return {
                width : w,
                word : word,
            };
        });
        // count = num words, spaces = number spaces, spaceWidth normal space size
        // adjSpace new space size >= min size. useSize Resulting space size used to render
        count = 0;
        lines = [];
        // create lines by shifting words from the words array until the spacing is optimal. If compact
        // true then will true and fit as many words as possible. Else it will try and get the spacing as
        // close as possible to the normal spacing
        while(words.length > 0){
            lastLineWidth = 0;
            lastSize = -1;
            lineFound = false;
            // each line must have at least one word.
            word = words.shift();
            lineWidth = word.width;
            lineWords = [word.word];
            count = 0;
            while(lineWidth < width && words.length > 0){ // Add words to line
                word = words.shift();
                lineWidth += word.width;
                lineWords.push(word.word);
                count += 1;
                spaces = count - 1;
                adjSpace =  (width - lineWidth) / spaces;
                if(minS > adjSpace){  // if spacing less than min remove last word and finish line
                    lineFound = true;
                    words.unshift(word);
                    lineWords.pop();
                }else{
                    if(!compact){ // if compact mode 
                        if(adjSpace < spaceWidth){ // if less than normal space width
                            if(lastSize === -1){
                                lastSize = adjSpace;
                            }
                            // check if with last word on if its closer to space width
                            if(Math.abs(spaceWidth - adjSpace) < Math.abs(spaceWidth - lastSize)){
                                lineFound = true; // yes keep it
                            }else{
                                words.unshift(word);  // no better fit if last word removes
                                lineWords.pop();
                                lineFound = true;
                            }
                        }
                    }
                }
                lastSize = adjSpace; // remember spacing 
            }
            lines.push(lineWords.join(" ")); // and the line
        }
        // lines have been worked out get font size, render, and render all the lines. last
        // line may need to be rendered as normal so it is outside the loop.
        fontSize = getFontSize(ctx.font);
        renderer = stroke === true ? ctx.strokeJustifyText.bind(ctx) : ctx.fillJustifyText.bind(ctx);
        for(i = 0; i < lines.length - 1; i ++){
            renderer(lines[i], x, y, width, settings);
            y += lineSpacing * fontSize;
        }
        if(lines.length > 0){ // last line if left or start aligned for no justify
            if(ctx.textAlign === "left" || ctx.textAlign === "start"){
                renderer(lines[lines.length - 1], x, y, width, noJustifySetting);
                ctx.measureJustifiedText("", width, settings);
            }else{
                renderer(lines[lines.length - 1], x, y, width);
            }
        }
        // return details about the paragraph.
        y += lineSpacing * fontSize;
        return {
            nextLine : y,
            fontSize : fontSize,
            lineHeight : lineSpacing * fontSize,
        };
    }
    // define fill
    var fillParagraphText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        settings = {
            minSpaceSize : minSpaceSize,
            maxSpaceSize : maxSpaceSize,
        };
        return justifiedPar(this, text, x, y, width, settings);
    }
    // define stroke
    var strokeParagraphText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        settings = {
            minSpaceSize : minSpaceSize,
            maxSpaceSize : maxSpaceSize,
        };
        return justifiedPar(this, text, x, y, width, settings,true);
    }
    CanvasRenderingContext2D.prototype.fillParaText = fillParagraphText;
    CanvasRenderingContext2D.prototype.strokeParaText = strokeParagraphText;
})();

NOTA esto extiende el prototipo de CanvasRenderingContext2D . Si no desea que esto suceda, utilice el texto Justificado de ejemplo para averiguar cómo cambiar este ejemplo para que sea parte del espacio de nombres global.

NOTA CanvasRenderingContext2D.prototype.fillJustifyText un ReferenceError si este ejemplo no puede encontrar la función CanvasRenderingContext2D.prototype.fillJustifyText


Cómo utilizar

ctx.fillParaText(text, x, y, width, [settings]);
ctx.strokeParaText(text, x, y, width, [settings]);

Consulte el texto justificado para obtener detalles sobre los argumentos. Los argumentos entre [ y ] son opcionales.

El argumento de settings tiene dos propiedades adicionales.

  • compacto: predeterminado true . Si es verdadero intenta empaquetar tantas palabras como sea posible por línea. Si es falso, intenta obtener el espacio entre palabras lo más cercano posible al espacio normal.
  • lineSpacing Default 1.5 . Espacio por línea por defecto 1.5 la distancia de en línea a la siguiente en términos de tamaño de fuente

Las propiedades que faltan en el objeto de configuración se predeterminarán a sus valores predeterminados o a los últimos valores válidos. Las propiedades solo se cambiarán si los nuevos valores son válidos. Para los valores válidos compact los valores booleanos son true o false valores de verdad no se consideran válidos

Devolver objeto

Las dos funciones devuelven un objeto que contiene información para ayudarlo a colocar el siguiente párrafo. El objeto contiene las siguientes propiedades.

  • nextLine Posición de la siguiente línea después de los píxeles del párrafo.
  • Tamaño de la fuente fontSize. (Tenga en cuenta que solo use las fuentes definidas en píxeles, por ejemplo, 14px arial )
  • lineHeight Distance en píxeles de una línea a la siguiente

Este ejemplo utiliza un algoritmo simple que trabaja una línea a la vez para encontrar el mejor ajuste para un párrafo. Esto no significa que sea el mejor ajuste (más bien el mejor del algoritmo) Es posible que desee mejorar el algoritmo creando un algoritmo de línea de paso múltiple sobre las líneas generadas. Mover palabras desde el final de una línea hasta el comienzo de la siguiente, o desde el inicio hasta el final. La mejor apariencia se logra cuando el espaciado en todo el párrafo tiene la variación más pequeña y es el más cercano al espaciado normal del texto.

Como este ejemplo depende del ejemplo de texto justificado, el código es muy similar. Es posible que desee mover los dos en una función. Reemplace la función justifiedTextSettings en el otro ejemplo con la utilizada en este ejemplo. Luego copie todo el resto del código de este ejemplo en el cuerpo de la función anónima del ejemplo de texto justificado . Ya no tendrá que probar las dependencias encontradas en // Code point A Se puede eliminar.


Ejemplo de uso

ctx.font = "25px arial";
ctx.textAlign = "center"

var left = 10;
var center = canvas.width / 2;
var width = canvas.width-left*2;
var y = 20;
var size = 16;
var i = 0;
ctx.fillText("Justified paragraph examples.",center,y);
y+= 30;
ctx.font = "14px arial";
ctx.textAlign = "left"
// set para settings
var setting = {
    maxSpaceSize : 6,
    minSpaceSize : 0.5,
    lineSpacing : 1.2,
    compact : true,
}
// Show the left and right bounds.
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(left,y - size * 2);
ctx.lineTo(left, y + size * 15);
ctx.moveTo(canvas.width - left,y - size * 2);
ctx.lineTo(canvas.width - left, y + size * 15);
ctx.stroke();
ctx.textAlign = "left";
ctx.fillStyle = "black";

// Draw paragraph
var line = ctx.fillParaText(para, left, y, width, setting);  // settings is remembered    

// Next paragraph
y = line.nextLine + line.lineHeight;
setting.compact = false;
ctx.fillParaText(para, left, y, width, setting);

Nota: Para el texto alineado a la left o start la última línea del párrafo siempre tendrá un espaciado normal. Para todas las demás alineaciones, la última línea se trata como todas las demás.

Nota: Puede insertar el inicio del párrafo con espacios. Aunque esto puede no ser consistente de un párrafo a otro. Siempre es bueno aprender lo que hace una función y modificarla. Un ejercicio sería agregar una configuración a la configuración que sangra la primera línea en una cantidad fija. Indica que el bucle while necesitará hacer que la primera palabra aparezca más grande (+ sangría) words[0].width += ? y luego, al renderizar las líneas, sangra la primera línea.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow