web-dev-qa-db-fra.com

La toile HTML5 ctx.fillText ne fait pas de sauts de ligne?

Il semble que je ne puisse pas ajouter de texte à un canevas si le texte inclut "\ n". Je veux dire, les sauts de ligne ne montrent pas/travail.

ctxPaint.fillText("s  ome \n \\n <br/> thing", x, y);

Le code ci-dessus affichera "s ome \n <br/> thing", sur une ligne. 

Est-ce une limitation de fillText ou est-ce que je me trompe? les "\ n" sont là et ne sont pas imprimés, mais ils ne fonctionnent pas non plus.

83
Spectraljump

J'ai bien peur que ce soit une limitation de la variable fillText de Canvas. Il n'y a pas de support multiligne. Quoi de pire, il n'y a pas de moyen intégré de mesurer la hauteur de ligne, mais seulement la largeur, ce qui rend la tâche encore plus difficile!

Beaucoup de gens ont écrit leur propre support multiligne, le projet le plus remarquable qui soit est Mozilla Skywriter .

Vous devez effectuer plusieurs appels fillText tout en ajoutant la hauteur du texte à la valeur y à chaque fois. (Mesurer la largeur de M est ce que les gens du skywriter font pour se rapprocher du texte, je crois.)

48
Simon Sarris

Si vous voulez juste vous occuper des caractères de nouvelle ligne dans le texte, vous pouvez le simuler en scindant le texte à la nouvelle ligne et en appelant plusieurs fois le fillText()

Quelque chose comme http://jsfiddle.net/BaG4J/1/

var c = document.getElementById('c').getContext('2d');
c.font = '11px Courier';
    console.log(c);
var txt = 'line 1\nline 2\nthird line..';
var x = 30;
var y = 30;
var lineheight = 15;
var lines = txt.split('\n');

for (var i = 0; i<lines.length; i++)
    c.fillText(lines[i], x, y + (i*lineheight) );
canvas{background-color:#ccc;}
<canvas id="c" width="150" height="150"></canvas>


Je viens de faire une preuve de concept d’emballage (emballage absolu à la largeur spécifiée. Aucun mot de manipulation ne se casse, mais)
exemple à http://jsfiddle.net/BaG4J/2/

var c = document.getElementById('c').getContext('2d');
c.font = '11px Courier';

var txt = 'this is a very long text to print';

printAt(c, txt, 10, 20, 15, 90 );


function printAt( context , text, x, y, lineHeight, fitWidth)
{
    fitWidth = fitWidth || 0;
    
    if (fitWidth <= 0)
    {
         context.fillText( text, x, y );
        return;
    }
    
    for (var idx = 1; idx <= text.length; idx++)
    {
        var str = text.substr(0, idx);
        console.log(str, context.measureText(str).width, fitWidth);
        if (context.measureText(str).width > fitWidth)
        {
            context.fillText( text.substr(0, idx-1), x, y );
            printAt(context, text.substr(idx-1), x, y + lineHeight, lineHeight,  fitWidth);
            return;
        }
    }
    context.fillText( text, x, y );
}
canvas{background-color:#ccc;}
<canvas id="c" width="150" height="150"></canvas>


Et une preuve de concept de Word-wrapping (break at spaces).
exemple à http://jsfiddle.net/BaG4J/5/

var c = document.getElementById('c').getContext('2d');
c.font = '11px Courier';

var txt = 'this is a very long text. Some more to print!';

printAtWordWrap(c, txt, 10, 20, 15, 90 );


function printAtWordWrap( context , text, x, y, lineHeight, fitWidth)
{
    fitWidth = fitWidth || 0;
    
    if (fitWidth <= 0)
    {
        context.fillText( text, x, y );
        return;
    }
    var words = text.split(' ');
    var currentLine = 0;
    var idx = 1;
    while (words.length > 0 && idx <= words.length)
    {
        var str = words.slice(0,idx).join(' ');
        var w = context.measureText(str).width;
        if ( w > fitWidth )
        {
            if (idx==1)
            {
                idx=2;
            }
            context.fillText( words.slice(0,idx-1).join(' '), x, y + (lineHeight*currentLine) );
            currentLine++;
            words = words.splice(idx-1);
            idx = 1;
        }
        else
        {idx++;}
    }
    if  (idx > 0)
        context.fillText( words.join(' '), x, y + (lineHeight*currentLine) );
}
canvas{background-color:#ccc;}
<canvas id="c" width="150" height="150"></canvas>


Dans les deuxième et troisième exemples, j'utilise la méthode measureText() qui indique la durée (en pixels) d'une chaîne lors de son impression.

51
Gabriele Petrioli

Peut-être arriver un peu tard à cette soirée, mais j’ai trouvé le tutoriel suivant pour envelopper du texte sur une toile. 

http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

A partir de là, j'ai pu penser à utiliser plusieurs lignes (désolé Ramirez, la tienne ne m'a pas fonctionné!). Mon code complet pour insérer du texte dans un canevas est le suivant:

<script type="text/javascript">

     // http: //www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/
     function wrapText(context, text, x, y, maxWidth, lineHeight) {
        var cars = text.split("\n");

        for (var ii = 0; ii < cars.length; ii++) {

            var line = "";
            var words = cars[ii].split(" ");

            for (var n = 0; n < words.length; n++) {
                var testLine = line + words[n] + " ";
                var metrics = context.measureText(testLine);
                var testWidth = metrics.width;

                if (testWidth > maxWidth) {
                    context.fillText(line, x, y);
                    line = words[n] + " ";
                    y += lineHeight;
                }
                else {
                    line = testLine;
                }
            }

            context.fillText(line, x, y);
            y += lineHeight;
        }
     }

     function DrawText() {

         var canvas = document.getElementById("c");
         var context = canvas.getContext("2d");

         context.clearRect(0, 0, 500, 600);

         var maxWidth = 400;
         var lineHeight = 60;
         var x = 20; // (canvas.width - maxWidth) / 2;
         var y = 58;


         var text = document.getElementById("text").value.toUpperCase();                

         context.fillStyle = "rgba(255, 0, 0, 1)";
         context.fillRect(0, 0, 600, 500);

         context.font = "51px 'LeagueGothicRegular'";
         context.fillStyle = "#333";

         wrapText(context, text, x, y, maxWidth, lineHeight);
     }

     $(document).ready(function () {

         $("#text").keyup(function () {
             DrawText();
         });

     });

    </script>

c est l'ID de ma toile et text est l'ID de ma zone de texte.

Comme vous pouvez probablement le voir, j'utilise une police non standard. Vous pouvez utiliser @ font-face tant que vous avez utilisé la police sur du texte AVANT de manipuler le canevas - sinon, le canevas ne prendra pas la police.

J'espère que ça aide quelqu'un.

38
Colin Wiseman

Divisez le texte en lignes et dessinez-les séparément:

function fillTextMultiLine(ctx, text, x, y) {
  var lineHeight = ctx.measureText("M").width * 1.2;
  var lines = text.split("\n");
  for (var i = 0; i < lines.length; ++i) {
    ctx.fillText(lines[i], x, y);
    y += lineHeight;
  }
}
22
Rok Strniša

Voici ma solution, modifiant la fonction populaire wrapText () déjà présentée ici. J'utilise la fonctionnalité de prototypage de JavaScript afin que vous puissiez appeler la fonction à partir du contexte de la zone de dessin.

CanvasRenderingContext2D.prototype.wrapText = function (text, x, y, maxWidth, lineHeight) {

    var lines = text.split("\n");

    for (var i = 0; i < lines.length; i++) {

        var words = lines[i].split(' ');
        var line = '';

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

        this.fillText(line, x, y);
        y += lineHeight;
    }
}

Utilisation de base:

var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
ctx.fillStyle = "black";
ctx.font = "12px sans-serif";
ctx.textBaseline = "top";
ctx.wrapText("Hello\nWorld!",20,20,160,16);

Voici une démonstration que j'ai composée: http://jsfiddle.net/7RdbL/

17
Jake

En utilisant javascript, j'ai développé une solution. Ce n'est pas beau mais ça a fonctionné pour moi:


function drawMultilineText(){

    // set context and formatting   
    var context = document.getElementById("canvas").getContext('2d');
    context.font = fontStyleStr;
    context.textAlign = "center";
    context.textBaseline = "top";
    context.fillStyle = "#000000";

    // prepare textarea value to be drawn as multiline text.
    var textval = document.getElementByID("textarea").value;
    var textvalArr = toMultiLine(textval);
    var linespacing = 25;
    var startX = 0;
    var startY = 0;

    // draw each line on canvas. 
    for(var i = 0; i < textvalArr.length; i++){
        context.fillText(textvalArr[i], x, y);
        y += linespacing;
    }
}

// Creates an array where the <br/> tag splits the values.
function toMultiLine(text){
   var textArr = new Array();
   text = text.replace(/\n\r?/g, '<br/>');
   textArr = text.split("<br/>");
   return textArr;
}

J'espère que cela pourra aider!

8
Ramirez

Le code pour le retour à la ligne fourni par @Gaby Petrioli est très utile . J'ai étendu son code afin de prendre en charge les caractères de nouvelle ligne \n. De plus, il est souvent utile d’avoir les dimensions du cadre de sélection, donc multiMeasureText() renvoie à la fois la largeur et la hauteur.

Vous pouvez voir le code ici: http://jsfiddle.net/jeffchan/WHgaY/76/

7
jeffchan

Je viens d'étendre CanvasRenderingContext2D en ajoutant deux fonctions: mlFillText et mlStrokeText.

Vous pouvez trouver la dernière version dans GitHub

Avec ces fonctions, vous pouvez remplir/tracer du texte miltiline dans une zone. Vous pouvez aligner le texte verticalement et horizontalement. (Il prend en compte les\n et peut aussi justifier le texte).

Les prototypes sont: 

fonction mlFillText (texte, x, y, w, h, vAlign, hAlign, hauteur de ligne); fonction mlStrokeText (texte, x, y, w, h, vAlign, hAlign, ligne)

Où vAlign peut être: "haut", "centre" ou "bouton" Et hAlign peut être: "gauche", "centre", "droite" ou "justifier"

Vous pouvez tester la lib ici: http://jsfiddle.net/4WRZj/1/

enter image description here

Voici le code de la bibliothèque:

// Library: mltext.js
// Desciption: Extends the CanvasRenderingContext2D that adds two functions: mlFillText and mlStrokeText.
//
// The prototypes are: 
//
// function mlFillText(text,x,y,w,h,vAlign,hAlign,lineheight);
// function mlStrokeText(text,x,y,w,h,vAlign,hAlign,lineheight);
// 
// Where vAlign can be: "top", "center" or "button"
// And hAlign can be: "left", "center", "right" or "justify"
// Author: Jordi Baylina. (baylina at uniclau.com)
// License: GPL
// Date: 2013-02-21

function mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, fn) {
    text = text.replace(/[\n]/g, " \n ");
    text = text.replace(/\r/g, "");
    var words = text.split(/[ ]+/);
    var sp = this.measureText(' ').width;
    var lines = [];
    var actualline = 0;
    var actualsize = 0;
    var wo;
    lines[actualline] = {};
    lines[actualline].Words = [];
    i = 0;
    while (i < words.length) {
        var Word = words[i];
        if (Word == "\n") {
            lines[actualline].EndParagraph = true;
            actualline++;
            actualsize = 0;
            lines[actualline] = {};
            lines[actualline].Words = [];
            i++;
        } else {
            wo = {};
            wo.l = this.measureText(Word).width;
            if (actualsize === 0) {
                while (wo.l > w) {
                    Word = Word.slice(0, Word.length - 1);
                    wo.l = this.measureText(Word).width;
                }
                if (Word === "") return; // I can't fill a single character
                wo.Word = Word;
                lines[actualline].Words.Push(wo);
                actualsize = wo.l;
                if (Word != words[i]) {
                    words[i] = words[i].slice(Word.length, words[i].length);
                } else {
                    i++;
                }
            } else {
                if (actualsize + sp + wo.l > w) {
                    lines[actualline].EndParagraph = false;
                    actualline++;
                    actualsize = 0;
                    lines[actualline] = {};
                    lines[actualline].Words = [];
                } else {
                    wo.Word = Word;
                    lines[actualline].Words.Push(wo);
                    actualsize += sp + wo.l;
                    i++;
                }
            }
        }
    }
    if (actualsize === 0) lines[actualline].pop();
    lines[actualline].EndParagraph = true;

    var totalH = lineheight * lines.length;
    while (totalH > h) {
        lines.pop();
        totalH = lineheight * lines.length;
    }

    var yy;
    if (vAlign == "bottom") {
        yy = y + h - totalH + lineheight;
    } else if (vAlign == "center") {
        yy = y + h / 2 - totalH / 2 + lineheight;
    } else {
        yy = y + lineheight;
    }

    var oldTextAlign = this.textAlign;
    this.textAlign = "left";

    for (var li in lines) {
        var totallen = 0;
        var xx, usp;
        for (wo in lines[li].Words) totallen += lines[li].Words[wo].l;
        if (hAlign == "center") {
            usp = sp;
            xx = x + w / 2 - (totallen + sp * (lines[li].Words.length - 1)) / 2;
        } else if ((hAlign == "justify") && (!lines[li].EndParagraph)) {
            xx = x;
            usp = (w - totallen) / (lines[li].Words.length - 1);
        } else if (hAlign == "right") {
            xx = x + w - (totallen + sp * (lines[li].Words.length - 1));
            usp = sp;
        } else { // left
            xx = x;
            usp = sp;
        }
        for (wo in lines[li].Words) {
            if (fn == "fillText") {
                this.fillText(lines[li].Words[wo].Word, xx, yy);
            } else if (fn == "strokeText") {
                this.strokeText(lines[li].Words[wo].Word, xx, yy);
            }
            xx += lines[li].Words[wo].l + usp;
        }
        yy += lineheight;
    }
    this.textAlign = oldTextAlign;
}

(function mlInit() {
    CanvasRenderingContext2D.prototype.mlFunction = mlFunction;

    CanvasRenderingContext2D.prototype.mlFillText = function (text, x, y, w, h, vAlign, hAlign, lineheight) {
        this.mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, "fillText");
    };

    CanvasRenderingContext2D.prototype.mlStrokeText = function (text, x, y, w, h, vAlign, hAlign, lineheight) {
        this.mlFunction(text, x, y, w, h, hAlign, vAlign, lineheight, "strokeText");
    };
})();

Et voici l'exemple d'utilisation:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var T = "This is a very long line line with a CR at the end.\n This is the second line.\nAnd this is the last line.";
var lh = 12;

ctx.lineWidth = 1;

ctx.mlFillText(T, 10, 10, 100, 100, 'top', 'left', lh);
ctx.strokeRect(10, 10, 100, 100);

ctx.mlFillText(T, 110, 10, 100, 100, 'top', 'center', lh);
ctx.strokeRect(110, 10, 100, 100);

ctx.mlFillText(T, 210, 10, 100, 100, 'top', 'right', lh);
ctx.strokeRect(210, 10, 100, 100);

ctx.mlFillText(T, 310, 10, 100, 100, 'top', 'justify', lh);
ctx.strokeRect(310, 10, 100, 100);

ctx.mlFillText(T, 10, 110, 100, 100, 'center', 'left', lh);
ctx.strokeRect(10, 110, 100, 100);

ctx.mlFillText(T, 110, 110, 100, 100, 'center', 'center', lh);
ctx.strokeRect(110, 110, 100, 100);

ctx.mlFillText(T, 210, 110, 100, 100, 'center', 'right', lh);
ctx.strokeRect(210, 110, 100, 100);

ctx.mlFillText(T, 310, 110, 100, 100, 'center', 'justify', lh);
ctx.strokeRect(310, 110, 100, 100);

ctx.mlFillText(T, 10, 210, 100, 100, 'bottom', 'left', lh);
ctx.strokeRect(10, 210, 100, 100);

ctx.mlFillText(T, 110, 210, 100, 100, 'bottom', 'center', lh);
ctx.strokeRect(110, 210, 100, 100);

ctx.mlFillText(T, 210, 210, 100, 100, 'bottom', 'right', lh);
ctx.strokeRect(210, 210, 100, 100);

ctx.mlFillText(T, 310, 210, 100, 100, 'bottom', 'justify', lh);
ctx.strokeRect(310, 210, 100, 100);

ctx.mlStrokeText("Yo can also use mlStrokeText!", 0 , 310 , 420, 30, 'center', 'center', lh);
7
jbaylina

Si vous n'avez besoin que de deux lignes de texte, vous pouvez les scinder en deux appels fillText différents et leur attribuer une ligne de base différente.

ctx.textBaseline="bottom";
ctx.fillText("First line", x-position, y-position);
ctx.textBaseline="top";
ctx.fillText("Second line", x-position, y-position);
4
thingEvery

Voici une version de wrapText() de Colin qui supporte également le texte centré verticalement avec context.textBaseline = 'middle':

var wrapText = function (context, text, x, y, maxWidth, lineHeight) {
    var paragraphs = text.split("\n");
    var textLines = [];

    // Loop through paragraphs
    for (var p = 0; p < paragraphs.length; p++) {
        var line = "";
        var words = paragraphs[p].split(" ");
        // Loop through words
        for (var w = 0; w < words.length; w++) {
            var testLine = line + words[w] + " ";
            var metrics = context.measureText(testLine);
            var testWidth = metrics.width;
            // Make a line break if line is too long
            if (testWidth > maxWidth) {
                textLines.Push(line.trim());
                line = words[w] + " ";
            }
            else {
                line = testLine;
            }
        }
        textLines.Push(line.trim());
    }

    // Move text up if centered vertically
    if (context.textBaseline === 'middle')
        y = y - ((textLines.length-1) * lineHeight) / 2;

    // Render text on canvas
    for (var tl = 0; tl < textLines.length; tl++) {
        context.fillText(textLines[tl], x, y);
        y += lineHeight;
    }
};
4
Tom Söderlund

Je pense que vous pouvez toujours compter sur CSS

ctx.measureText().height doesn’t exist.

Heureusement, via CSS hack-ardry (voir Métriques typographiques pour plus de façons de corriger les anciennes implémentations utilisant des mesures CSS), nous pouvons trouver la hauteur du texte en mesurant l'offsetHauteur d'un avec les mêmes propriétés de police:

var d = document.createElement(”span”);
d.font = “20px arial”
d.textContent = “Hello world!”
var emHeight = d.offsetHeight;

à partir de: http://www.html5rocks.com/fr/tutorials/canvas/texteffects/

3
MarioF

Je ne pense pas que ce soit possible non plus, mais une solution de contournement consiste à créer un élément <p> et à le positionner avec Javascript.

2
Harmen

Je suis tombé dessus parce que j'avais le même problème. Je travaille avec une taille de police variable, donc cela en tient compte:

var texts=($(this).find('.noteContent').html()).split("<br>");
for (var k in texts) {
    ctx.fillText(texts[k], left, (top+((parseInt(ctx.font)+2)*k)));
}

où .noteContent est la division contenteditable que l'utilisateur a modifiée (elle est imbriquée dans une fonction jQuery), et ctx.font est "14px Arial" (notez que la taille en pixels vient en premier)

2
MaKR

L'élément de canevas ne prend pas en charge des caractères tels que les balises newline '\ n', tab '\ t' ou <br />.

Essayez le:

var newrow = mheight + 30;
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = "bold 24px 'Verdana'";
ctx.textAlign = "center";
ctx.fillText("Game Over", mwidth, mheight); //first line
ctx.fillText("play again", mwidth, newrow); //second line 

ou peut-être plusieurs lignes:

var textArray = new Array('line2', 'line3', 'line4', 'line5');
var rows = 98;
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.font = "bold 24px 'Verdana'";
ctx.textAlign = "center";
ctx.fillText("Game Over", mwidth, mheight); //first line

for(var i=0; i < textArray.length; ++i) {
rows += 30;
ctx.fillText(textArray[i], mwidth, rows); 
}  
0
Dariusz J

Ma solution ES5 pour le problème:

var wrap_text = (ctx, text, x, y, lineHeight, maxWidth, textAlign) => {
  if(!textAlign) textAlign = 'center'
  ctx.textAlign = textAlign
  var words = text.split(' ')
  var lines = []
  var sliceFrom = 0
  for(var i = 0; i < words.length; i++) {
    var chunk = words.slice(sliceFrom, i).join(' ')
    var last = i === words.length - 1
    var bigger = ctx.measureText(chunk).width > maxWidth
    if(bigger) {
      lines.Push(words.slice(sliceFrom, i).join(' '))
      sliceFrom = i
    }
    if(last) {
      lines.Push(words.slice(sliceFrom, words.length).join(' '))
      sliceFrom = i
    }
  }
  var offsetY = 0
  var offsetX = 0
  if(textAlign === 'center') offsetX = maxWidth / 2
  for(var i = 0; i < lines.length; i++) {
    ctx.fillText(lines[i], x + offsetX, y + offsetY)
    offsetY = offsetY + lineHeight
  }
}

Plus d'informations sur le sujet est sur mon blog .

0
Oleg Berman

J'ai créé un module npm simple pour cette utilisation exacte . https://www.npmjs.com/package/canvas-txt

Vous pouvez diviser le texte en multiligne automatiquement et également sur \n

0
Geon George