wake-up-neo.com

HTML5-Canvas ctx.fillText führt keine Zeilenumbrüche aus?

Ich kann anscheinend keinen Text zu einer Leinwand hinzufügen, wenn der Text "\ n" enthält. Ich meine, die Zeilenumbrüche zeigen/arbeiten nicht.

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

Der obige Code zeichnet "s ome \n <br/> thing" in einer Zeile. 

Ist dies eine Einschränkung von fillText oder mache ich es falsch? Die "\ n" s sind da und werden nicht gedruckt, funktionieren aber auch nicht.

83
Spectraljump

Ich fürchte, es ist eine Einschränkung von Canvas 'fillText. Es gibt keine Multi-Line-Unterstützung. Was noch schlimmer ist, es gibt keinen integrierten Weg, um die Höhe der Linie zu messen, nur die Breite, was es noch schwieriger macht, es selbst zu tun!

Viele Leute haben ihren eigenen Multi-Line-Support geschrieben. Das vielleicht bemerkenswerteste Projekt ist Mozilla Skywriter .

Das Wichtigste bei dem, was Sie tun müssen, sind mehrere fillText-Aufrufe, während Sie die Höhe des Textes jedes Mal zum y-Wert hinzufügen. (Das Messen der Breite von M ist das, was die Skywriter-Leute tun, um Text anzugleichen, glaube ich.)

48
Simon Sarris

Wenn Sie sich nur um die Zeilenumbrüche im Text kümmern möchten, können Sie dies simulieren, indem Sie den Text an den Zeilenumbrüchen aufteilen und fillText() mehrfach aufrufen.

So etwas wie 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>


Ich habe gerade einen Proof-of-Concept für die Verpackung gemacht (absolute Umhüllung bei angegebener Breite. Noch keine Worte mit dem Umgang mit Wörtern.)
Beispiel unter 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>


Und ein Word-Wrapping (Breaking in Spaces) Konzeptnachweis.
Beispiel unter 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>


Im zweiten und dritten Beispiel verwende ich die measureText() - Methode, die angibt, wie lang (in Pixel) eine Zeichenfolge beim Drucken sein wird.

51

Vielleicht zu spät zu dieser Party gekommen, aber ich fand das folgende Tutorial zum Einbetten von Text auf einer Leinwand perfekt. 

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

Ich konnte davon ausgehen, dass mehrere Zeilen zum Laufen gebracht wurden (sorry Ramirez, Ihre hat bei mir nicht funktioniert!). Mein vollständiger Code zum Umschließen von Text in eine Leinwand lautet wie folgt:

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

Dabei ist c die ID meiner Zeichenfläche und text die ID meines Textfelds.

Wie Sie wahrscheinlich sehen, verwende ich eine nicht standardmäßige Schrift. Sie können @ font-face verwenden, solange Sie die Schrift in einem VORHERIGEN Text verwendet haben, um die Leinwand zu bearbeiten. Andernfalls kann die Leinwand die Schrift nicht aufnehmen.

Hoffe das hilft jemandem.

38
Colin Wiseman

Zerlegen Sie den Text in Zeilen und zeichnen Sie jeden einzeln:

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

Hier ist meine Lösung, um die beliebte wrapText () - Funktion zu ändern, die hier bereits vorgestellt wird. Ich benutze die Prototyping-Funktion von JavaScript, damit Sie die Funktion vom Canvas-Kontext aus aufrufen können.

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

Grundnutzung:

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

Hier ist eine Demonstration, die ich zusammengebaut habe: http://jsfiddle.net/7RdbL/

17
Jake

Mit Javascript habe ich eine Lösung entwickelt. Es ist nicht schön, aber es hat für mich funktioniert:


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

Hoffentlich hilft das!

8
Ramirez

Der von @Gaby Petrioli bereitgestellte Code für Zeilenumbrüche (Zeilenumbrüche) ist sehr hilfreich . Ich habe seinen Code erweitert, um Zeilenumbrüche \n zu unterstützen. Oft ist es auch nützlich, die Abmessungen des Begrenzungsrahmens zu haben, sodass multiMeasureText() sowohl die Breite als auch die Höhe zurückgibt.

Sie können den Code hier sehen: http://jsfiddle.net/jeffchan/WHgaY/76/

7
jeffchan

Ich habe gerade das CanvasRenderingContext2D erweitert und zwei Funktionen hinzugefügt: mlFillText und mlStrokeText.

Die letzte Version finden Sie in GitHub

Mit dieser Funktion können Sie den Miltilin-Text in einer Box füllen. Sie können den Text vertikal und horizontal ausrichten. (Es berücksichtigt\n's und kann den Text auch rechtfertigen).

Die Prototypen sind: 

funktion mlFillText (Text, x, y, w, h, vAlign, hAlign, Zeilenhöhe); Funktion mlStrokeText (Text, x, y, w, h, vAlign, hAlign, Zeilenhöhe);

Wo vAlign sein kann: "top", "center" oder "button" Und hAlign kann sein: "left", "center", "right" oder "justify".

Sie können die Bibliothek hier testen: http://jsfiddle.net/4WRZj/1/

enter image description here

Hier ist der Code der Bibliothek:

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

Und hier ist das Anwendungsbeispiel:

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

Wenn Sie nur zwei Textzeilen benötigen, können Sie sie in zwei verschiedene fillText-Aufrufe aufteilen und jedem eine andere Grundlinie zuweisen.

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

Hier ist eine Version von Colins wrapText(), die auch vertikal zentrierten Text mit context.textBaseline = 'middle' unterstützt:

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

Ich denke, man kann sich immer noch auf CSS verlassen

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

Glücklicherweise können wir durch CSS-Hack-Ardry (siehe Typografische Metriken für mehr Möglichkeiten zur Korrektur älterer Implementierungen der Verwendung von CSS-Messungen) die Höhe des Texts ermitteln, indem Sie die offsetHeight von a mit den gleichen font-Eigenschaften messen:

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

von: http://www.html5rocks.com/de/tutorials/canvas/texteffects/

3
MarioF

Ich glaube nicht, dass dies auch nicht möglich ist, aber eine Problemumgehung besteht darin, ein <p>element zu erstellen und es mit Javascript zu positionieren.

2
Harmen

Ich bin durch dieses Problem gestoßen. Ich arbeite mit variabler Schriftgröße, daher wird dies berücksichtigt:

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

dabei ist .noteContent das inhaltbare edit-div, das der Benutzer bearbeitet hat (dies wird in jeder Funktion von jQuery verschachtelt), und ctx.font ist "14px Arial" (Beachten Sie, dass die Pixelgröße an erster Stelle steht).

2
MaKR

Das Zeichenelement unterstützt keine Zeichen wie die Zeilenumbrüche '\ n', das Tab '\ t' oder das <br /> -Tag.

Versuch es:

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 

oder vielleicht mehrere Zeilen:

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

Meine ES5-Lösung für das Problem:

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

Weitere Informationen zu diesem Problem sind in meinem Blog .

0
Oleg Berman

Ich habe ein einfaches npm-Modul für diesen genauen Gebrauch erstellt . https://www.npmjs.com/package/canvas-txt

Sie können Text automatisch in mehrere Zeilen teilen und auch basierend auf \n.

0
Geon George