HTML5 Canvas – очень обширная тема со многими “вкусностями”, о многих из которых уже писали и ещё будут писать. Поэтому, я хочу немного рассказать в этой статье, только об одной маленькой, и как на первый взгляд может показаться банальной темой – работа с текстом. Я хочу показать, что с ним почти также можно работать, как с обычным текстом в вебе, т.е. позиционировать, накладывать стили и градиенты, а также писать многострочные предложения легко и без проблем.


Простой текст


Чтобы вывести обычный текст, можно воспользоваться двумя функциями контекста fillText() и strokeText().
Эти функции принимают три обязательных параметра: сам текст и координаты X и Y, т.е. место его расположения, и последний – необязательный: максимальная ширина текста, если вы зададите максимальную ширину текста меньше, чем фактическая ширина, то текст сожмется до указанной вами ширины. Не советую задавать максимальную ширину, так как при сжатии и у него становится плохой вид. Вместо такого сжатия рекомендую переносить текст на новую строку, стандартной функции для этого нет, но я приведу пример, как это легко реализовывается.
Если просто вывести текст при помощи таких функций, то такой текст будет мелкий и невзрачный. Это мы можем исправить задав ему шрифт в переменную контекста font. Шрифт задается, также как и в CSS ([font style][font weight][font size][font face]). Пример:

    ctx.fillStyle = "#00F";
    ctx.strokeStyle = "#F00";
    ctx.font = "italic 30pt Arial";
    ctx.fillText("Fill text", 20, 50);
    ctx.font = 'bold 30px sans-serif';
    ctx.strokeText("Stroke text", 20, 100);


image

Demo

Расположение т��кста


Есть два стандартных способа позиционирования текста, относительно своего расположения: вертикальное и горизонтальное.
Вертикальное позиционирование задается при помощи textBaseline, в неё устанавливается один из возможных вариантом: top, hanging, middle, alphabetic, ideographic и bottom.

    ctx.textBaseline = "bottom";
    ctx.fillText("bottom", 400, 75);


image

Demo

А горизонтально при помощи textAlign, может быть один из следующих параметров: center, start, end, left, right.

    context.textAlign = "center";
    ctx.textBaseline = "bottom";
    context.fillText("center", 250, 20);


image

Demo

Эти способы далеко не идеальны и при помощи них иногда проблематично спозиционировать текст, так как хочется именно вам. Ниже я опишу способ, как можно использовать своё позиционирование, даже многострочного текста.

Стилизация
Самое простое, как мы можем стилизовать текст, это задать ему цвет. Цвет задается при помощи fillStyle – для задания цвета заливки и strokeStyle – для задания цвета обводки.
Так же как и в CSS3 можно накладывать тени и на текст в канвасе. Это делается при помощи: shadowColor – задание цвета тени, shadowOffsetX и shadowOffsetY – задание отступа и shadowBlur – задание размытия тени.

    ctx.shadowColor = "#F00";
    ctx.shadowOffsetX = 5;
    ctx.shadowOffsetY = 5;
    ctx.shadowBlur = 5;
    ctx.strokeText("Shadow text", 20, 100);


image

Поддерживаются также и градиенты для текста. Сам градиент создается при помощи функции createLinearGradient(). А при помощи функции addColorStop() задаются цвета и позиции их в нём. Текст заливается градиентом также как и сплошным цветом, при помощи fillStyle и strokeStyle.

    var gradient = ctx.createLinearGradient(0, 0, 0, 60);
    gradient.addColorStop(0.0, 'rgba(0, 0, 255, 1)');
    gradient.addColorStop(0.3, 'rgba(128, 0, 255, 0.6)');
    gradient.addColorStop(0.6, 'rgba(0, 0, 255, 0.4)');
    gradient.addColorStop(1.0, 'rgba(0, 255, 0, 0.2)');
    ctx.fillStyle = gradient;


image

Кроме обычной заливки и заливки градиентом можно также залить какой-либо текстурой. Для этого необходимо до начала вывода текста загрузить изображение-текстуру. А затем при помощи createPattern() создать текстуру на его основе. Можно задать, чтобы изображение повторялось либо нет.

    var pattern = ctx.createPattern(img, 'repeat');
    ctx.fillStyle = pattern;


image

Demo

Ширина текста и многострочный текст



Допустим, вам необходимо написать текст на канвасе неограниченной длинны, будь то одно слово, или несколько предложений. Если вы будете его писать как есть, т.е. весь текст вставите в fillText(), то на экране будет видна только та часть, которая по своей ширине не превышает ширину канваса. Т.е. если ширина канваса 400 пикселей, то вы не уведете текст, который выходи�� за его ширину. Для того, чтобы сделать многострочный текст необходимо исхитрится. Но алгоритм прост. Сначала высчитываем ширину текста, сверяем её с максимальной шириной области, куда мы хотим вывести этот текст. Если ширина текста не превышает ту ширину, то выводим текст, а если превышает, то разбиваем текст самым удобным для вас способом, я предпочитаю разбивать по пробелам.
Для того чтобы высчитать ширину текста можно воспользоваться функцией measureText(), которая отдает в объектной форме ширину текста (к сожалению данная функция не отдает высоту текста, надеюсь эта возможность будет добавлена, но а пока придется высчитывать иными способами, как именно далее в статье).
Пример:

function wrapText(context, text, marginLeft, marginTop, maxWidth, lineHeight)
    {
        var words = text.split(" ");
        var countWords = words.length;
        var line = "";
        for (var n = 0; n < countWords; n++) {
            var testLine = line + words[n] + " ";
            var testWidth = context.measureText(testLine).width;
            if (testWidth > maxWidth) {
                context.fillText(line, marginLeft, marginTop);
                line = words[n] + " ";
                marginTop += lineHeight;
            }
            else {
                line = testLine;
            }
        }
        context.fillText(line, marginLeft, marginTop);
    }
    var canvas = document.getElementById("canvas");
    var context = canvas.getContext("2d");
    var maxWidth = 400; //размер поле, где выводится текст
    var lineHeight = 25;
    /*если мы знаем высоту текста, то мы можем
     предположить, что высота строки должна быть именно такой*/
    var marginLeft = 20;
    var marginTop = 40;
    var text = "Сначала мы разбиваем текст на слова по пробелам, а потом обходим эти слова в цикле, " +
            "объединяя их по одному в строку. Если при последнем объединении ширина этой строки меньше максимальной, " +
            "то продолжаем, а если больше, то выводим строку без последнего слова, а его записываем в новую строку." +
            "И так продолжаем, пока не обработаем весь текст.";
    context.font = "16pt Calibri";
    context.fillStyle = "#000";
    wrapText(context, text, marginLeft, marginTop, maxWidth, lineHeight);


image

Demo

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

Определяем высоту текста



При работе с текстом на канвасе ни функция measureText(), ни одна другая не дают нам определить высоту текста. Всё же можно справиться и с этой бедой, вариант решения этой проблемы, который я хочу предложить вам, не очень красив, но всё же оно работает.
Когда мы задаем стиль текста (размер и стиль шрифта) в font, то мы его задаем в таком же стиле, так как бы мы его задавали в CSS. Шрифты и их размеры, конечно если доступен такой шрифт в канвасе, то также он будет доступен и в обычных стилях.
И так, идея вот в чем, у нас есть размер и тип шрифта в переменной font, мы создаем в DOM новый элемент, туда помещаем текст, и устанавливаем стили из той переменной. Далее при помощи offsetHeight получаем высоту элемента, которая и будет высотой нашего текста. После этого можем благополучно удалить этот элемент из DOM.
Пример реализации этой идеи:

var text = "Этот текст должен быть написан справа в самом низу.";
    var marginLeft = canvas.width - context.measureText(text).width;
    var marginTop = canvas.height - getFontHeight(context.font);
    context.fillText(text, marginLeft, marginTop);
    function getFontHeight(font) {
        var parent = document.createElement("span");
        parent.appendChild(document.createTextNode("height"));
        document.body.appendChild(parent);
        parent.style.cssText = "font: " + font + "; white-space: nowrap; display: inline;";
        var height = parent.offsetHeight;
        document.body.removeChild(parent);
        return height;
    }


Результат:

image

Demo

Буду рад, если кто-то предложит более красивый или правильный вариант получения высоты текста.
Спасибо.