Подтвердить что ты не робот

Обтекание текстом в элементе <canvas>

Я пытаюсь добавить текст на изображение с помощью элемента <canvas>. Сначала изображение нарисовано и на изображении нарисован текст. Пока все хорошо.

Но я столкнулся с проблемой: если текст слишком длинный, он становится срезанным в начале и в конце холстом. Я не планирую изменять размер холста, но мне было интересно, как обернуть длинный текст на несколько строк, чтобы все это отображалось. Может ли кто-нибудь указать мне в правильном направлении?

4b9b3361

Ответ 1

Возможный метод (не полностью протестированный, но на данный момент он отлично работает)

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

Ответ 2

Обновленная версия ответа @mizar с одной серьезной и одной незначительной ошибкой.

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

Мы использовали этот код в течение некоторого времени, но сегодня мы пытались понять, почему какой-то текст не рисовал, и мы обнаружили ошибку!

Оказывается, если вы дадите единственное слово (без пробелов) функции getLines(), оно вернет пустой массив, а не массив с одной строкой.

Пока мы исследовали это, мы обнаружили еще одну (гораздо более тонкую) ошибку, где строки могут оказаться немного длиннее, чем они должны быть, поскольку исходный код не учитывал пробелы при измерении длины строки.

Наша обновленная версия, которая работает для всего, что мы бросили на нее, выше. Дайте мне знать, если вы найдете какие-либо ошибки!

Ответ 3

Вот мое заклинание... Я прочитал ответ @mizar и внес некоторые изменения в него... и с небольшой помощью я смог это получить.

код удален, см. скрипт.

Вот пример использования. http://jsfiddle.net/9PvMU/1/ - этот script также можно увидеть здесь и в конечном итоге это то, что я использовал в конце... эта функция предполагает, что ctx доступен в родительской области... если нет, вы всегда можете передать ее.


изменить

пост был старым и имел мою версию функции, с которой я все еще занимался. Эта версия, похоже, удовлетворила мои потребности до сих пор, и я надеюсь, что она может помочь кому-то еще.


изменить

Мне было сообщено, что в этом коде была небольшая ошибка. Мне потребовалось некоторое время, чтобы обойти его, но здесь он обновляется. Я сам его протестировал и, похоже, работает так, как ожидалось.

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

Ответ 4

context.measureText(text).width - это то, что вы ищете...

Ответ 5

посмотрите https://developer.mozilla.org/en/Drawing_text_using_a_canvas#measureText%28%29

Если вы видите выделенный текст и видите его более широкий, чем ваш холст, вы можете удалить слова, пока текст не станет достаточно коротким. С удаленными словами вы можете начать со второй строки и сделать то же самое.

Конечно, это будет не очень эффективно, поэтому вы можете улучшить его, не удаляя одно слово, а несколько слов, если видите, что текст намного шире ширины холста.

Я не исследовал, но, возможно, это даже библиотеки javascript, которые делают это для вас.

Ответ 6

Попробуйте этот script, чтобы обернуть текст на холсте.      

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

Смотрите демонстрацию здесь http://codetutorial.com/examples-canvas/canvas-examples-text-wrap.

Ответ 8

Из script здесь: http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/

Я расширил, чтобы включить поддержку абзаца. Используйте \n для новой строки.

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

Текст может быть отформатирован так:

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

Использование:

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

вместо

context.fillText(text, x, y);

Ответ 9

Это должно привести строки правильно из текстового поля: -

 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;

И затем нарисуйте выделенные строки на холсте: -

 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
                    }

Ответ 10

Я размещаю свою собственную версию здесь, так как ответов здесь было недостаточно для меня. Первое слово нужно было измерить в моем случае, чтобы иметь возможность отрицать слишком длинные слова из небольших областей холста. И мне нужна поддержка "break + space, space + break" или 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;
}

Он поддерживает любые варианты разрывов строк или разрывов абзацев, удаляет двойные пробелы, а также переходы верхнего или предыдущего абзаца. Он возвращает пустой массив, если текст не подходит. Или массив строк, готовых рисовать.