web-dev-qa-db-fra.com

Habillage du texte dans un élément <canvas>

J'essaie d'ajouter du texte sur une image à l'aide de l'élément <canvas>. D'abord, l'image est dessinée et sur l'image, le texte est dessiné. Jusqu'ici tout va bien.

Mais là où je suis confronté à un problème, c'est que si le texte est trop long, il est coupé au début et se termine par la trame. Je n'ai pas l'intention de redimensionner la zone de travail, mais je me demandais comment envelopper le texte long sur plusieurs lignes afin que tout le texte soit affiché. Quelqu'un peut-il m'indiquer la bonne direction?

31
Gwood

Version mise à jour de la réponse de @ mizar, avec un bug grave et un bug mineur corrigés.

function getLines(ctx, text, maxWidth) {
    var words = text.split(" ");
    var lines = [];
    var currentLine = words[0];

    for (var i = 1; i < words.length; i++) {
        var Word = words[i];
        var width = ctx.measureText(currentLine + " " + Word).width;
        if (width < maxWidth) {
            currentLine += " " + Word;
        } else {
            lines.Push(currentLine);
            currentLine = Word;
        }
    }
    lines.Push(currentLine);
    return lines;
}

Nous utilisons ce code depuis un certain temps, mais aujourd'hui, nous essayions de comprendre pourquoi un texte n'était pas dessiné et nous avons trouvé un bug!

Il s'avère que si vous attribuez un seul mot (sans espaces) à la fonction getLines (), il renverra un tableau vide, plutôt qu'un tableau comportant une seule ligne.

Pendant que nous enquêtions sur cela, nous avons trouvé un autre bogue (beaucoup plus subtil), où les lignes peuvent se retrouver légèrement plus longues qu’elles ne devraient l’être, car le code original ne tenait pas compte des espaces lors de la mesure de la longueur d’une ligne.

Notre version mise à jour, qui fonctionne pour tout ce que nous avons jeté, est ci-dessus. Faites-moi savoir si vous trouvez des bugs!

25
crazy2be

Une méthode possible (pas complètement testée, mais elle fonctionne parfaitement pour l'instant)

    /**
     * Divide an entire phrase in an array of phrases, all with the max pixel length given.
     * The words are initially separated by the space char.
     * @param phrase
     * @param length
     * @return
     */
function getLines(ctx,phrase,maxPxLength,textStyle) {
    var wa=phrase.split(" "),
        phraseArray=[],
        lastPhrase=wa[0],
        measure=0,
        splitChar=" ";
    if (wa.length <= 1) {
        return wa
    }
    ctx.font = textStyle;
    for (var i=1;i<wa.length;i++) {
        var w=wa[i];
        measure=ctx.measureText(lastPhrase+splitChar+w).width;
        if (measure<maxPxLength) {
            lastPhrase+=(splitChar+w);
        } else {
            phraseArray.Push(lastPhrase);
            lastPhrase=w;
        }
        if (i===wa.length-1) {
            phraseArray.Push(lastPhrase);
            break;
        }
    }
    return phraseArray;
}
23
mizar

Voici ce que je pensais ... J'ai lu la réponse de @ mizar et y ai apporté quelques modifications ... et avec un peu d'aide, j'ai pu l'obtenir. 

code supprimé, voir violon.

Voici un exemple d'utilisation. http://jsfiddle.net/9PvMU/1/ - ce script peut également être vu here et a fini par être ce que j'ai utilisé à la fin ... cette fonction suppose que ctx est disponible dans la portée parente ... sinon, vous pouvez toujours le transmettre. 


modifier

le message était vieux et contenait ma version de la fonction avec laquelle je bricolais encore. Cette version semble avoir répondu à mes besoins jusqu'à présent et j'espère qu'elle pourra aider quelqu'un d'autre.


modifier

On m'a signalé qu'il y avait un petit bug dans ce code. Il m'a fallu du temps pour régler le problème, mais ici, il est mis à jour. Je l'ai testé moi-même et il semble fonctionner comme prévu maintenant. 

function fragmentText(text, maxWidth) {
    var words = text.split(' '),
        lines = [],
        line = "";
    if (ctx.measureText(text).width < maxWidth) {
        return [text];
    }
    while (words.length > 0) {
        var split = false;
        while (ctx.measureText(words[0]).width >= maxWidth) {
            var tmp = words[0];
            words[0] = tmp.slice(0, -1);
            if (!split) {
                split = true;
                words.splice(1, 0, tmp.slice(-1));
            } else {
                words[1] = tmp.slice(-1) + words[1];
            }
        }
        if (ctx.measureText(line + words[0]).width < maxWidth) {
            line += words.shift() + " ";
        } else {
            lines.Push(line);
            line = "";
        }
        if (words.length === 0) {
            lines.Push(line);
        }
    }
    return lines;
}
7
rlemon

context.measureText(text).width est ce que vous cherchez ... 

6
Warty

Extrait du script ici: http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

J'ai étendu pour inclure le support de paragraphe. Utilisez\n pour la nouvelle ligne.

function wrapText(context, text, x, y, line_width, line_height)
{
    var line = '';
    var paragraphs = text.split('\n');
    for (var i = 0; i < paragraphs.length; i++)
    {
        var words = paragraphs[i].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 > line_width && n > 0)
            {
                context.fillText(line, x, y);
                line = words[n] + ' ';
                y += line_height;
            }
            else
            {
                line = testLine;
            }
        }
        context.fillText(line, x, y);
        y += line_height;
        line = '';
    }
}

Le texte peut être formaté comme suit:

var text = 
[
    "Paragraph 1.",
    "\n\n",
    "Paragraph 2."
].join("");

Utilisation:

wrapText(context, text, x, y, line_width, line_height);

au lieu de

context.fillText(text, x, y);
1
squarcle

Essayez ce script pour envelopper le texte sur une toile.

 <script>
  function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
    var words = text.split(' ');
    var line = '';

    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 && n > 0) {
        ctx.fillText(line, x, y);
        line = words[n] + ' ';
        y += lineHeight;
      }
      else {
        line = testLine;
      }
    }
    ctx.fillText(line, x, y);
  }

  var canvas = document.getElementById('Canvas01');
  var ctx = canvas.getContext('2d');
  var maxWidth = 400;
  var lineHeight = 24;
  var x = (canvas.width - maxWidth) / 2;
  var y = 70;
  var text = 'HTML is the language for describing the structure of Web pages. HTML stands for HyperText Markup Language. Web pages consist of markup tags and plain text. HTML is written in the form of HTML elements consisting of tags enclosed in angle brackets (like <html>). HTML tags most commonly come in pairs like <h1> and </h1>, although some tags represent empty elements and so are unpaired, for example <img>..';

  ctx.font = '15pt Calibri';
  ctx.fillStyle = '#555555';

  wrapText(ctx, text, x, y, maxWidth, lineHeight);
  </script>
</body>

Voir la démo ici  http://codetutorial.com/examples-canvas/canvas-examples-text-wrap.

1
MichaelCalvin

regardez https://developer.mozilla.org/en/Drawing_text_using_a_canvas#measureText%28%29

Si vous pouvez voir le texte sélectionné et le voir plus large que votre toile, vous pouvez supprimer des mots jusqu'à ce que le texte soit suffisamment court. Avec les mots supprimés, vous pouvez commencer à la deuxième ligne et faire de même.

Bien sûr, cela ne sera pas très efficace, vous pouvez donc l'améliorer en ne supprimant pas un mot, mais plusieurs mots si vous voyez que le texte est beaucoup plus large que la largeur de la toile.

Je n'ai pas fait de recherche, mais peut-être y a-t-il même des bibliothèques javascript qui le font pour vous

1
Nathan

Je publie ma propre version utilisée ici puisque les réponses fournies ici ne sont pas insuffisantes pour moi. La première Parole devait être mesurée dans mon cas, pour pouvoir refuser les mots trop longs dans de petites zones de la zone de travail. Et j'avais besoin d'aide pour les combos 'break + space,' space + break 'ou double break/paragraph-break.

wrapLines: function(ctx, text, maxWidth) {
    var lines = [],
        words = text.replace(/\n\n/g,' ` ').replace(/(\n\s|\s\n)/g,'\r')
        .replace(/\s\s/g,' ').replace('`',' ').replace(/(\r|\n)/g,' '+' ').split(' '),
        space = ctx.measureText(' ').width,
        width = 0,
        line = '',
        Word = '',
        len = words.length,
        w = 0,
        i;
    for (i = 0; i < len; i++) {
        Word = words[i];
        w = Word ? ctx.measureText(Word).width : 0;
        if (w) {
            width = width + space + w;
        }
        if (w > maxWidth) {
            return [];
        } else if (w && width < maxWidth) {
            line += (i ? ' ' : '') + Word;
        } else {
            !i || lines.Push(line !== '' ? line.trim() : '');
            line = Word;
            width = w;
        }
    }
    if (len !== i || line !== '') {
        lines.Push(line);
    }
    return lines;
}

Il prend en charge toutes les variantes de sauts de ligne ou de paragraphe, supprime les doubles espaces ainsi que les sauts de paragraphe en tête ou en fin. Il retourne soit un tableau vide si le texte ne convient pas. Ou un tableau de lignes prêtes à être dessinées.

0
hexalys

Cela devrait amener les lignes correctement de la zone de texte: -

 function fragmentText(text, maxWidth) {
    var lines = text.split("\n");
    var fittingLines = [];
    for (var i = 0; i < lines.length; i++) {
        if (canvasContext.measureText(lines[i]).width <= maxWidth) {
            fittingLines.Push(lines[i]);
        }
        else {
            var tmp = lines[i];
            while (canvasContext.measureText(tmp).width > maxWidth) {
                tmp = tmp.slice(0, tmp.length - 1);
            }
            if (tmp.length >= 1) {
                var regex = new RegExp(".{1," + tmp.length + "}", "g");
                var thisLineSplitted = lines[i].match(regex);
                for (var j = 0; j < thisLineSplitted.length; j++) {
                    fittingLines.Push(thisLineSplitted[j]);
                }
            }
        }
    }
    return fittingLines;

Et ensuite, dessinez les lignes récupérées sur la toile: -

 var lines = fragmentText(textBoxText, (rect.w - 10)); //rect.w = canvas width, rect.h = canvas height
                    for (var showLines = 0; showLines < lines.length; showLines++) { // do not show lines that go beyond the height
                        if ((showLines * resultFont.height) >= (rect.h - 10)) {      // of the canvas
                            break;
                        }
                    }
                    for (var i = 1; i <= showLines; i++) {
                        canvasContext.fillText(lines[i-1], rect.clientX +5 , rect.clientY + 10 + (i * (resultFont.height))); // resultfont = get the font height using some sort of calculation
                    }
0
JBelfort
0
wizztjh