Recherche…


Texte de dessin

Le dessin sur toile ne se limite pas aux formes et aux images. Vous pouvez également dessiner du texte sur le canevas.

Pour dessiner du texte sur le canevas, obtenez une référence au canevas, puis appelez la méthode fillText sur le contexte.

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

Les trois arguments requis qui sont passés dans fillText sont les suivants:

  1. Le texte que vous souhaitez afficher
  2. La position horizontale (axe des x)
  3. La position verticale (axe des y)

En outre, il existe un quatrième argument facultatif , que vous pouvez utiliser pour spécifier la largeur maximale de votre texte en pixels. Dans l'exemple ci-dessous, la valeur de 200 limite la largeur maximale du texte à 200px:

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

Résultat:

Exemple de résultat de l'utilisation de la méthode fillText sur canvas

Vous pouvez également dessiner du texte sans remplissage et simplement un contour à l'aide de la méthode strokeText :

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

Résultat:

Exemple de résultat de l'utilisation de la méthode strokeText sur canvas

Sans les propriétés de mise en forme de la police, le canevas affiche par défaut le texte à 10 pixels dans sans-serif, ce qui rend difficile la différence entre le résultat des méthodes fillText et strokeText . Voir l' exemple de formatage de texte pour plus de détails sur la manière d'augmenter la taille du texte et d'appliquer d'autres modifications esthétiques au texte.

Texte de formatage

Le formatage de police par défaut fourni par les méthodes fillText et strokeText n'est pas très esthétique. Heureusement, l'API canvas fournit des propriétés pour le formatage du texte.

En utilisant la propriété de font , vous pouvez spécifier:

  • le style de police
  • variante de police
  • poids de la police
  • taille de police / hauteur de ligne
  • famille de polices

Par exemple:

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

Résultat:

Exemple de résultat de la spécification des propriétés de la police

En utilisant la propriété textAlign , vous pouvez également modifier l’alignement du texte sur:

  • la gauche
  • centre
  • droite
  • fin (même chose à droite)
  • commencer (comme à gauche)

Par exemple:

ctx.textAlign = "center";

Envelopper le texte en paragraphes

Native Canvas API ne dispose pas d'une méthode pour envelopper le texte sur la ligne suivante lorsqu'une largeur maximale souhaitée est atteinte. Cet exemple encapsule le texte dans des paragraphes.

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

Dessinez des paragraphes de texte dans des formes irrégulières

Cet exemple dessine des paragraphes de texte dans toutes les parties du canevas qui ont des pixels opaques.

Cela fonctionne en trouvant le prochain bloc de pixels opaques qui est assez grand pour contenir le mot spécifié suivant et en remplissant ce bloc avec le mot spécifié.

Les pixels opaques peuvent provenir de n'importe quelle source: commandes de dessin de chemin et / ou images.

entrer la description de l'image ici

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

Remplir le texte avec une image

Cet exemple remplit le texte avec une image spécifiée.

Important! L'image spécifiée doit être entièrement chargée avant d'appeler cette fonction ou le dessin échouera. Utilisez image.onload pour vous assurer que l'image est complètement chargée.

entrer la description de l'image ici

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

Rendu du texte le long d'un arc.

Cet exemple montre comment rendre du texte le long d'un arc. Il comprend comment vous pouvez ajouter des fonctionnalités à CanvasRenderingContext2D en étendant son prototype.

Cet exemple est dérivé du texte circulaire de la réponse stackoverflow.


Exemple de rendu

Exemple de texte en cercle


Exemple de code

L'exemple ajoute 3 nouvelles fonctions de rendu de texte au prototype de contexte 2D.

  • ctx.fillCircleText (texte, x, y, rayon, début, fin, avant);
  • ctx.strokeCircleText (texte, x, y, rayon, début, fin, avant);
  • ctx.measureCircleText (texte, rayon);
(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;  
})();

Description des fonctions

Cet exemple ajoute 3 fonctions au CanvasRenderingContext2D prototype . fillCircleText , strokeCircleText et measureCircleText

CanvasRenderingContext2D.fillCircleText (texte, x, y, rayon, début, [fin, [avant]]);

CanvasRenderingContext2D.strokeCircleText (texte, x, y, rayon, début, [fin, [avant]]);

  • text: Texte à afficher sous forme de chaîne.
  • x , y : position du centre du cercle sous forme de nombres.
  • rayon: rayon du cercle en pixels
  • start: angle en radians pour commencer.
  • [fin]: facultatif. Si inclus, ctx.textAlign est ignoré et le texte est mis à l'échelle entre le début et la fin.
  • [forward]: option par défaut 'true'. si la direction du texte est vraie, si la «fausse» direction est en arrière.

Les deux fonctions utilisent textBaseline pour positionner le texte verticalement autour du rayon. Pour les meilleurs résultats, utilisez ctx.TextBaseline .

Les fonctions lanceront un TypeError est l'un des arguments numériques en tant que NaN.

Si l'argument de text réduit à une chaîne vide ou ctx.globalAlpha = 0 la fonction ne fait que passer et ne fait rien.

CanvasRenderingContext2D.measureCircleText (text, radius);

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

Renvoie un objet contenant diverses mesures de taille pour le rendu du texte circulaire

   - **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.

Exemples d'utilisation

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


REMARQUE: Le texte rendu n'est qu'une approximation du texte circulaire. Par exemple, si deux ls sont rendues, les deux lignes ne seront pas parallèles, mais si vous rendez un "H", les deux arêtes seront parallèles. C'est parce que chaque caractère est rendu aussi proche que possible de la direction requise, plutôt que chaque pixel soit correctement transformé pour créer un texte circulaire.

NOTE: const multiplyCurrentTransform = true; défini dans cet exemple est utilisé pour définir la méthode de transformation utilisée. Si false la transformation pour le rendu du texte circulaire est absolue et ne dépend pas de l'état de la transformation en cours. Le texte ne sera affecté par aucune échelle, rotation ou traduction antérieure. Cela augmentera les performances de la fonction de rendu, après que la fonction soit appelée, la transformation sera définie sur setTransform(1,0,0,1,0,0) par défaut setTransform(1,0,0,1,0,0)

Si multiplyCurrentTransform = true (définie par défaut dans cet exemple) le texte utilisera la transformée en cours afin que le texte peut être mis à l' échelle traduit, en biais, rotation, etc. , mais la modification de la transformation en cours befor appelant les fillCircleText et strokeCircleText fonctions. Selon l'état actuel du contexte 2D, cela peut être un peu plus lent que multiplyCurrentTransform = false

Texte sur courbe, beziers cubiques et quadratiques

entrer la description de l'image ici

textOnCurve (texte, décalage, x1, y1, x2, y2, x3, y3, x4, y4)

Rend le texte sur les courbes quadratiques et cubiques.

  • text est le texte à rendre
  • distance de offset du début de la courbe au texte> = 0
  • x1,y1 - x3,y3 points de courbe quadratique ou
  • x1,y1 - x4,y4 points de courbe cubique ou

Exemple d'utilisation:

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 fonction d'assistance et la fonction de recourbement

// 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 fonction d'assistance de courbe est conçue pour améliorer les performances de recherche de points sur le 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
}

Texte justifié

Cet exemple affiche un texte justifié. Il ajoute des fonctionnalités supplémentaires à la CanvasRenderingContext2D en étendant son prototype ou un objet global justifiedText (voir la note en option A).


Exemple de rendu.

entrer la description de l'image ici
Le code pour rendre cette image se trouve dans les exemples d'utilisation en bas .


L'exemple

La fonction en tant que fonction anonyme immédiatement invoquée.

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

Remarque A: Si vous ne souhaitez pas étendre le prototype CanvasRenderingContext2D Supprimez de l'exemple tout le code entre // code point A et // code point B et décommentez le code marqué /* Uncomment from here to the closing comment


Comment utiliser

Trois fonctions sont ajoutées à CanvasRenderingContext2D et sont disponibles pour tous les objets de contexte 2D créés.

  • ctx.fillJustifyText (texte, x, y, largeur, [paramètres]);
  • ctx.strokeJustifyText (texte, x, y, largeur, [paramètres]);
  • ctx.measureJustifiedText (texte, largeur, [paramètres]);

Remplissez et contournez la fonction de texte, remplissez ou contournez le texte et utilisez les mêmes arguments. measureJustifiedText renvoie la largeur réelle à laquelle le texte sera rendu. Cela peut être égal, inférieur ou supérieur à la width de l'argument en fonction des paramètres actuels.

Remarque: les arguments situés à l'intérieur de [ et ] sont facultatifs.

Arguments de fonction

  • text: Chaîne contenant le texte à afficher.

  • x, y: coordonnées pour rendre le texte à.

  • width: Largeur du texte justifié. Le texte augmentera / diminuera les espaces entre les mots pour s'adapter à la largeur. Si l'espace entre les mots est supérieur à maxSpaceSize (valeur par défaut = 6), l'espacement normal sera utilisé et le texte ne remplira pas la largeur requise. Si l'espacement est inférieur à minSpaceSize (valeur par défaut = 0,5), l'espacement normal est utilisé, la taille de l'espace minimum est utilisée et le texte dépasse la largeur demandée.

  • paramètres: facultatif. Objet contenant des tailles d'espace min et max.

L'argument des settings est facultatif et, s'il n'est pas inclus, le rendu du texte utilisera le dernier paramètre défini ou le paramètre par défaut (illustré ci-dessous).

Les valeurs min et max sont les tailles min et max pour les mots séparant le caractère [espace]. La valeur par défaut maxSpaceSize = 6 signifie que lorsque l’espace entre les caractères est> 63 * ctx.measureText (""), le texte .width ne sera pas justifié. Si le texte à justifier comporte des espaces inférieurs à minSpaceSize = 0.5 (valeur par défaut 0,5) * ctx.measureText(" ").width l’espacement sera défini sur minSpaceSize * ctx.measureText(" ").width la largeur de justification.

Les règles suivantes sont appliquées, min et max doivent être des nombres. Sinon, les valeurs associées ne seront pas modifiées. Si minSpaceSize est plus grand que maxSpaceSize deux paramètres d'entrée sont invalides et min max ne sera pas modifié.

Exemple d'objet avec des valeurs par défaut

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

REMARQUE: Ces fonctions de texte introduisent un changement de comportement subtil pour la propriété textAlign du contexte 2D. "Left", "right", "center" et "start" se comportent comme prévu, mais "end" ne s'alignera pas de la droite de l'argument de la fonction x mais plutôt de la droite de x + width

Remarque: les paramètres (taille de l'espace min et max) sont globaux pour tous les objets de contexte 2D.


USAGE Exemples

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

Paragraphes justifiés.

Rend le texte en tant que paragraphes justifiés. Exige l'exemple Texte justifié


Exemple de rendu

entrer la description de l'image ici
Le paragraphe supérieur a setting.compact = true et bottom false et l'interligne est 1.2 plutôt que la valeur par défaut 1.5 . Rendu par exemple d'utilisation du code en bas de cet exemple.


Exemple de code

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

REMARQUE cela étend le prototype CanvasRenderingContext2D . Si vous ne souhaitez pas que cela se produise, utilisez l'exemple de texte justifié pour déterminer comment modifier cet exemple pour qu'il fasse partie de l'espace de noms global.

NOTE CanvasRenderingContext2D.prototype.fillJustifyText un ReferenceError si cet exemple ne trouve pas la fonction CanvasRenderingContext2D.prototype.fillJustifyText


Comment utiliser

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

Voir Texte justifié pour plus de détails sur les arguments. Les arguments entre [ et ] sont facultatifs.

L'argument settings a deux propriétés supplémentaires.

  • compact: Par défaut true . Si true essaie de mettre autant de mots que possible par ligne. Si la valeur est false, le système tente d'obtenir l'espacement des mots aussi proche que possible de l'espacement normal.
  • lineSpacing Par défaut 1.5 . Espace par défaut de la ligne 1.5 la distance entre la ligne et la suivante en termes de taille de police

Les propriétés manquantes dans l'objet de paramètres seront par défaut à leurs valeurs par défaut ou aux dernières valeurs valides. Les propriétés ne seront modifiées que si les nouvelles valeurs sont valides. Pour compact valeurs valides compact ne sont que des booléens true ou false valeurs de true ne sont pas considérées comme valides.

Objet retour

Les deux fonctions renvoient un objet contenant des informations pour vous aider à placer le paragraphe suivant. L'objet contient les propriétés suivantes.

  • nextLine Position de la ligne suivante après les pixels du paragraphe.
  • fontSize Taille de la police. (veuillez noter que n'utilisez que des polices définies en pixels, par exemple 14px arial )
  • lineHeight Distance en pixels d'une ligne à la suivante

Cet exemple utilise un algorithme simple qui fonctionne une ligne à la fois pour trouver le meilleur ajustement pour un paragraphe. Cela ne signifie pas que c'est le meilleur ajustement (plutôt que l'algorithme le mieux adapté). Vous pouvez souhaiter améliorer l'algorithme en créant un algorithme à plusieurs lignes sur les lignes générées. Déplacement des mots de la fin d'une ligne au début de la suivante ou du début à la fin. Le meilleur aspect est obtenu lorsque l’espacement sur l’ensemble du paragraphe a la plus petite variation et est le plus proche de l’espacement normal du texte.

Comme cet exemple dépend de l'exemple de texte justifié, le code est très similaire. Vous souhaiterez peut-être déplacer les deux dans une fonction. Remplacez la fonction justifiedTextSettings dans l'autre exemple par celle utilisée dans cet exemple. Copiez ensuite tout le reste du code de cet exemple dans le corps de la fonction anonyme de l'exemple de texte justifié . Vous n'aurez plus besoin de tester les dépendances trouvées à // Code point A Il peut être supprimé.


Exemple d'utilisation

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

Remarque: pour le texte aligné à left ou au start la dernière ligne du paragraphe aura toujours un espacement normal. Pour tous les autres alignements, la dernière ligne est traitée comme toutes les autres.

Remarque: Vous pouvez insérer le début du paragraphe avec des espaces. Bien que cela puisse ne pas être cohérent d'un paragraphe à l'autre. C'est toujours une bonne chose d'apprendre ce que fait une fonction et de la modifier. Un exercice serait d'ajouter un paramètre aux paramètres qui indente la première ligne d'un montant fixe. La boucle while devra faire apparaître temporairement le premier mot (+ indent) words[0].width += ? et puis, lors du rendu des lignes, indenter la première ligne.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow