Поиск…


Текст чертежа

Рисование на холсте не ограничивается только формами и изображениями. Вы также можете нарисовать текст на холсте.

Чтобы нарисовать текст на холсте, получите ссылку на холст, а затем вызовите метод fillText в контексте.

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.fillText("My text", 0, 0);

Три обязательных аргумента, которые передаются в fillText :

  1. Текст, который вы хотите отобразить
  2. Положение по горизонтали (по оси x)
  3. Положение по вертикали (по оси Y)

Кроме того, существует четвертый необязательный аргумент, который можно использовать для указания максимальной ширины текста в пикселях. В приведенном ниже примере значение 200 ограничивает максимальную ширину текста до 200 пикселей:

ctx.fillText("My text", 0, 0, 200);

Результат:

Пример результата использования метода fillText на холсте

Вы также можете нарисовать текст без заливки, а вместо этого использовать схему, используя метод strokeText :

ctx.strokeText("My text", 0, 0);

Результат:

Пример результата использования метода strokeText на холсте

Без каких-либо свойств форматирования шрифта по умолчанию холст делает текст по умолчанию 10 пикселей в sans-serif, что затрудняет просмотр разницы между результатом методов fillText и strokeText . Подробные сведения о том, как увеличить размер текста и применить другие эстетические изменения к тексту, см. В примере форматирования текста.

Форматирование текста

Форматирование шрифта по умолчанию, предоставляемое методами fillText и strokeText не очень эстетично. К счастью, API canvas предоставляет свойства для форматирования текста.

Используя свойство font вы можете указать:

  • стиль шрифта
  • вариант шрифта
  • начертание шрифта
  • font-size / line-height
  • семейство шрифтов

Например:

ctx.font = "italic small-caps bold 40px Helvetica, Arial, sans-serif";
ctx.fillText("My text", 20, 50);

Результат:

Пример результата определения свойств шрифта

Используя свойство textAlign вы также можете изменить выравнивание текста:

  • оставил
  • центр
  • право
  • конец (тот же, что и правый)
  • start (то же, что и влево)

Например:

ctx.textAlign = "center";

Объединение текста в параграфы

У Native Canvas API нет способа обернуть текст на следующую строку, когда будет достигнута желаемая максимальная ширина. Этот пример обертывает текст в абзацы.

function wrapText(text, x, y, maxWidth, fontSize, fontFace){
  var firstY=y;
  var words = text.split(' ');
  var line = '';
  var lineHeight=fontSize*1.286; // a good approx for 10-18px sizes

  ctx.font=fontSize+" "+fontFace;
  ctx.textBaseline='top';

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

Нарисуйте абзацы текста в нерегулярные формы

Этот пример рисует абзацы текста на любые части холста с непрозрачными пикселями.

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

Прозрачные пиксели могут исходить из любого источника: команды рисования пути и / или изображения.

введите описание изображения здесь

<!doctype html>
<html>
<head>
<style>
    body{ background-color:white; padding:10px; }
    #canvas{border:1px solid red;}
</style>
<script>
window.onload=(function(){

    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");
    var cw=canvas.width;
    var ch=canvas.height;

    var fontsize=12;
    var fontface='verdana';
    var lineHeight=parseInt(fontsize*1.286);

    var text='It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way; in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.';
    var words=text.split(' ');
    var wordWidths=[];
    ctx.font=fontsize+'px '+fontface;
    for(var i=0;i<words.length;i++){ wordWidths.push(ctx.measureText(words[i]).width); }
    var spaceWidth=ctx.measureText(' ').width;
    var wordIndex=0
    var data=[];

    // Demo: draw Heart 
    // Note: the shape can be ANY opaque drawing -- even an image
    ctx.scale(3,3);
    ctx.beginPath();
    ctx.moveTo(75,40);
    ctx.bezierCurveTo(75,37,70,25,50,25);
    ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
    ctx.bezierCurveTo(20,80,40,102,75,120);
    ctx.bezierCurveTo(110,102,130,80,130,62.5);
    ctx.bezierCurveTo(130,62.5,130,25,100,25);
    ctx.bezierCurveTo(85,25,75,37,75,40);
    ctx.fillStyle='red';
    ctx.fill();
    ctx.setTransform(1,0,0,1,0,0);

    // fill heart with text
    ctx.fillStyle='white';
    var imgDataData=ctx.getImageData(0,0,cw,ch).data;
    for(var i=0;i<imgDataData.length;i+=4){
        data.push(imgDataData[i+3]);
    }
    placeWords();   

    // draw words sequentially into next available block of 
    //    available opaque pixels 
    function placeWords(){
        var sx=0;
        var sy=0;
        var y=0;
        var wordIndex=0;
        ctx.textBaseline='top';
        while(y<ch && wordIndex<words.length){
            sx=0;
            sy=y;
            var startingIndex=wordIndex;
            while(sx<cw && wordIndex<words.length){
                var x=getRect(sx,sy,lineHeight);
                var available=x-sx;
                var spacer=spaceWidth;  // spacer=0 to have no left margin
                var w=spacer+wordWidths[wordIndex];
                while(available>=w){
                    ctx.fillText(words[wordIndex],spacer+sx,sy);
                    sx+=w;
                    available-=w;
                    spacer=spaceWidth;
                    wordIndex++;
                    w=spacer+wordWidths[wordIndex];
                }
                sx=x+1;
            }
            y=(wordIndex>startingIndex)?y+lineHeight:y+1;
        }
    }

    // find a rectangular block of opaque pixels
    function getRect(sx,sy,height){
        var x=sx;
        var y=sy;
        var ok=true;
        while(ok){
            if(data[y*cw+x]<250){ok=false;}
            y++;
            if(y>=sy+height){
                y=sy;
                x++;
                if(x>=cw){ok=false;}
            }
        }
        return(x);
    }

}); // end $(function(){});
</script>
</head>
<body>
    <h4>Note: the shape must be closed and alpha>=250 inside</h4>
    <canvas id="canvas" width=400 height=400></canvas>
</body>
</html>

Заполнить текст изображением

Этот пример заполняет текст указанным изображением.

Важный! Указанное изображение должно быть полностью загружено перед вызовом этой функции или сбой чертежа. Используйте image.onload чтобы убедиться, что изображение полностью загружено.

введите описание изображения здесь

function drawImageInsideText(canvas,x,y,img,text,font){
    var c=canvas.cloneNode();
    var ctx=c.getContext('2d');
    ctx.font=font;
    ctx.fillText(text,x,y);
    ctx.globalCompositeOperation='source-atop';
    ctx.drawImage(img,0,0);
    canvas.getContext('2d').drawImage(c,0,0);
}

Отображение текста вдоль дуги.

В этом примере показано, как визуализировать текст вдоль дуги. Он включает в себя то, как вы можете добавить функциональность CanvasRenderingContext2D , расширив свой прототип.

Эти примеры взяты из ответа Circular Text .


Пример визуализации

Пример текста окружности


Пример кода

В примере добавлены 3 новые функции рендеринга текста в прототип 2D-контекста.

  • ctx.fillCircleText (текст, x, y, радиус, начало, конец, вперед);
  • ctx.strokeCircleText (текст, x, y, радиус, начало, конец, вперед);
  • ctx.measureCircleText (текст, радиус);
(function(){
    const FILL = 0;        // const to indicate filltext render
    const STROKE = 1;
    var renderType = FILL; // used internal to set fill or stroke text
    const multiplyCurrentTransform = true; // if true Use current transform when rendering
                                           // if false use absolute coordinates which is a little quicker
                                           // after render the currentTransform is restored to default transform
                                           
      

    // measure circle text
    // ctx: canvas context
    // text: string of text to measure
    // r: radius in pixels
    //
    // returns the size metrics of the text
    //
    // width: Pixel width of text
    // angularWidth : angular width of text in radians
    // pixelAngularSize : angular width of a pixel in radians
    var measure = function(ctx, text, radius){        
        var textWidth = ctx.measureText(text).width; // get the width of all the text
        return {
            width               : textWidth,
            angularWidth        : (1 / radius) * textWidth,
            pixelAngularSize    : 1 / radius
        };
    }

    // displays text along a circle
    // ctx: canvas context
    // text: string of text to measure
    // x,y: position of circle center
    // r: radius of circle in pixels
    // start: angle in radians to start. 
    // [end]: optional. If included text align is ignored and the text is 
    //        scaled to fit between start and end;
    // [forward]: optional default true. if true text direction is forwards, if false  direction is backward
    var circleText = function (ctx, text, x, y, radius, start, end, forward) {
        var i, textWidth, pA, pAS, a, aw, wScale, aligned, dir, fontSize;
        if(text.trim() === "" || ctx.globalAlpha === 0){ // dont render empty string or transparent
            return;
        }
        if(isNaN(x) || isNaN(y) || isNaN(radius) || isNaN(start) || (end !== undefined && end !== null && isNaN(end))){ // 
            throw TypeError("circle text arguments requires a number for x,y, radius, start, and end.")
        }
        aligned = ctx.textAlign;        // save the current textAlign so that it can be restored at end
        dir = forward ? 1 : forward === false ? -1 : 1;  // set dir if not true or false set forward as true  
        pAS = 1 / radius;               // get the angular size of a pixel in radians
        textWidth = ctx.measureText(text).width; // get the width of all the text
        if (end !== undefined && end !== null) { // if end is supplied then fit text between start and end
            pA = ((end - start) / textWidth) * dir;
            wScale = (pA / pAS) * dir;
        } else {                 // if no end is supplied correct start and end for alignment
            // if forward is not given then swap top of circle text to read the correct direction
            if(forward === null || forward === undefined){
                if(((start % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2) > Math.PI){
                    dir = -1;
                }
            }
            pA = -pAS * dir ;
            wScale = -1 * dir;
            switch (aligned) {
            case "center":       // if centered move around half width
                start -= (pA * textWidth )/2;
                end = start + pA * textWidth;
                break;
            case "right":// intentionally falls through to case "end"
            case "end":
                end = start;
                start -= pA * textWidth;
                break;
            case "left":  // intentionally falls through to case "start"
            case "start":
                end = start + pA * textWidth;
            }
        }

        ctx.textAlign = "center";                     // align for rendering
        a = start;                                    // set the start angle
        for (var i = 0; i < text.length; i += 1) {    // for each character
            aw = ctx.measureText(text[i]).width * pA; // get the angular width of the text
            var xDx = Math.cos(a + aw / 2);           // get the yAxies vector from the center x,y out
            var xDy = Math.sin(a + aw / 2);
            if(multiplyCurrentTransform){ // transform multiplying current transform
                ctx.save();
                if (xDy < 0) { // is the text upside down. If it is flip it
                    ctx.transform(-xDy * wScale, xDx * wScale, -xDx, -xDy, xDx * radius + x, xDy * radius + y);
                } else {
                    ctx.transform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y);
                }
            }else{
                if (xDy < 0) { // is the text upside down. If it is flip it
                    ctx.setTransform(-xDy * wScale, xDx * wScale, -xDx, -xDy, xDx * radius + x, xDy * radius + y);
                } else {
                    ctx.setTransform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y);
                }
            }
            if(renderType === FILL){
                ctx.fillText(text[i], 0, 0);    // render the character
            }else{                    
                ctx.strokeText(text[i], 0, 0);  // render the character
            }
            if(multiplyCurrentTransform){  // restore current transform
                ctx.restore();
            }
            a += aw;                     // step to the next angle
        }
        // all done clean up.
        if(!multiplyCurrentTransform){
            ctx.setTransform(1, 0, 0, 1, 0, 0); // restore the transform
        }
        ctx.textAlign = aligned;            // restore the text alignment
    }
    // define fill text
    var fillCircleText = function(text, x, y, radius, start, end, forward){
        renderType = FILL;
        circleText(this, text, x, y, radius, start, end, forward);
    }
    // define stroke text
    var strokeCircleText = function(text, x, y, radius, start, end, forward){
        renderType = STROKE;
        circleText(this, text, x, y, radius, start, end, forward);
    }
    // define measure text
    var measureCircleTextExt = function(text,radius){
        return measure(this, text, radius);
    }
    // set the prototypes
    CanvasRenderingContext2D.prototype.fillCircleText = fillCircleText;
    CanvasRenderingContext2D.prototype.strokeCircleText = strokeCircleText;
    CanvasRenderingContext2D.prototype.measureCircleText = measureCircleTextExt;  
})();

Описание функций

Этот пример добавляет 3 функции CanvasRenderingContext2D prototype . fillCircleText , strokeCircleText и measureCircleText

CanvasRenderingContext2D.fillCircleText (текст, x, y, радиус, начало, [конец, [вперед]]);

CanvasRenderingContext2D.strokeCircleText (текст, x, y, радиус, начало, [конец, [вперед]]);

  • text: текст для рендеринга в виде строки.
  • x , y : Положение центра круга как Числа.
  • радиус: радиус круга в пикселях
  • Начало: угол в радианах для начала.
  • [end]: необязательно. Если включено, ctx.textAlign игнорируется, а текст масштабируется, чтобы соответствовать началу и концу.
  • [вперед]: необязательный по умолчанию «истина». если истинное направление текста направлено вперед, если «ложное» направление назад.

Обе функции используют textBaseline для размещения текста по вертикали вокруг радиуса. Для достижения наилучших результатов используйте ctx.TextBaseline .

Функции будут генерировать TypeError - это любой из числовых аргументов, таких как NaN.

Если text аргумент обрезается до пустой строки или ctx.globalAlpha = 0 функция просто ctx.globalAlpha = 0 и ничего не делает.

CanvasRenderingContext2D.measureCircleText (текст, радиус);

 - **text:** String of text to measure.
 - **radius:** radius of circle in pixels.

Возвращает объект, содержащий различные показатели размера для отображения кругового текста

   - **width:** Pixel width of text as it would normaly be rendered
   - **angularWidth:** angular width of text in radians.
   - **pixelAngularSize:** angular width of a pixel in radians.

Примеры использования

const rad = canvas.height * 0.4;
const text = "Hello circle TEXT!";
const fontSize = 40;
const centX = canvas.width / 2;
const centY = canvas.height / 2;
ctx.clearRect(0,0,canvas.width,canvas.height)

ctx.font = fontSize + "px verdana";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillStyle = "#000";
ctx.strokeStyle = "#666";

// Text under stretched from Math.PI to 0 (180 - 0 deg)
ctx.fillCircleText(text, centX, centY, rad, Math.PI, 0);

// text over top centered at Math.PI * 1.5 ( 270 deg)
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);

// text under top centered at Math.PI * 1.5 ( 270 deg)
ctx.textBaseline = "top";
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);


// text over top centered at Math.PI * 1.5 ( 270 deg)
ctx.textBaseline = "middle";
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);


// Use measureCircleText to get angular size
var circleTextMetric = ctx.measureCircleText("Text to measure", rad);
console.log(circleTextMetric.width);            // width of text if rendered normally
console.log(circleTextMetric.angularWidth);     // angular width of text
console.log(circleTextMetric.pixelAngularSize); // angular size of a pixel    



// Use measure text to draw a arc around the text
ctx.textBaseline = "middle";
var width = ctx.measureCircleText(text, rad).angularWidth;    
ctx.fillCircleText(text, centX, centY, rad, Math.PI * 1.5);

// render the arc around the text
ctx.strokeStyle= "red";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(centX, centY, rad + fontSize / 2,Math.PI * 1.5 - width/2,Math.PI*1.5 + width/2);
ctx.arc(centX, centY, rad - fontSize / 2,Math.PI * 1.5 + width/2,Math.PI*1.5 - width/2,true);
ctx.closePath();
ctx.stroke();


ПРИМЕЧАНИЕ . Текст, отображаемый, является только приближением кругового текста. Например, если два l отображаются, две строки не будут параллельными, но если вы сделаете «H», то два ребра будут параллельными. Это происходит потому, что каждый символ отображается как можно ближе к требуемому направлению, вместо того, чтобы каждый пиксель был правильно преобразован для создания кругового текста.

ПРИМЕЧАНИЕ: const multiplyCurrentTransform = true; определенный в этом примере, используется для установки используемого метода преобразования. Если false преобразование для циклического текстового рендеринга является абсолютным и не зависит от текущего состояния преобразования. Текст не будет выполняться никаким предыдущим масштабом, поворот или преобразование преобразований. Это увеличит производительность функции рендеринга, после того, как функция будет вызвана преобразованием, будет установлено значение по умолчанию setTransform(1,0,0,1,0,0)

Если multiplyCurrentTransform = true (в этом примере задано по умолчанию), текст будет использовать текущее преобразование, чтобы текст можно масштабировать, переводить, перекосить, поворачивать и т. Д., Но изменять текущее преобразование перед strokeCircleText функций fillCircleText и strokeCircleText . В зависимости от текущего состояния 2D-контекста это может быть несколько медленнее, а затем multiplyCurrentTransform = false

Текст на кривой, кубических и квадратичных безье

введите описание изображения здесь

textOnCurve (текст, смещение, x1, y1, x2, y2, x3, y3, x4, у4)

Отображает текст по квадратичным и кубическим кривым.

  • text - текст для визуализации
  • offset от начала кривой до текста> = 0
  • x1,y1 - x3,y3 точек квадратичной кривой или
  • x1,y1 - x4,y4 точек кубической кривой или

Пример использования:

textOnCurve("Hello world!",50,100,100,200,200,300,100); // draws text on quadratic curve
                                                        // 50 pixels from start of curve


textOnCurve("Hello world!",50,100,100,200,200,300,100,400,200); 
                                                        // draws text on cubic curve
                                                        // 50 pixels from start of curve

Функция и курсорная вспомогательная функция

// pass 8 values for cubic bezier
// pass 6 values for quadratic
// Renders text from start of curve
var textOnCurve = function(text,offset,x1,y1,x2,y2,x3,y3,x4,y4){
    ctx.save();
    ctx.textAlign = "center";
    var widths = [];
    for(var i = 0; i < text.length; i ++){
        widths[widths.length] = ctx.measureText(text[i]).width;
    }
    var ch = curveHelper(x1,y1,x2,y2,x3,y3,x4,y4);
    var pos = offset;
    var cpos = 0;

    for(var i = 0; i < text.length; i ++){
        pos += widths[i] / 2;
        cpos = ch.forward(pos);
        ch.tangent(cpos);
        ctx.setTransform(ch.vect.x, ch.vect.y, -ch.vect.y, ch.vect.x, ch.vec.x, ch.vec.y);
        ctx.fillText(text[i],0,0);     

        pos += widths[i] / 2;
    }
    ctx.restore();
}

Функция вспомогательной кривой предназначена для повышения производительности точек поиска на безье.

// helper function locates points on bezier curves.
function curveHelper(x1, y1, x2, y2, x3, y3, x4, y4){
    var tx1, ty1, tx2, ty2, tx3, ty3, tx4, ty4;
    var a,b,c,u;
    var vec,currentPos,vec1,vect;
    vec = {x:0,y:0};
    vec1 = {x:0,y:0};
    vect = {x:0,y:0};
    quad = false;
    currentPos = 0;
    currentDist = 0;
    if(x4 === undefined || x4 === null){
        quad = true;
        x4 = x3;
        y4 = y3;
    }
    var estLen = Math.sqrt((x4 - x1) * (x4 - x1) + (y4 - y1) * (y4 - y1));
    var onePix = 1 / estLen;
    function posAtC(c){
        tx1 = x1; ty1 = y1;
        tx2 = x2; ty2 = y2;
        tx3 = x3; ty3 = y3;
        tx1 += (tx2 - tx1) * c;
        ty1 += (ty2 - ty1) * c;
        tx2 += (tx3 - tx2) * c;
        ty2 += (ty3 - ty2) * c;
        tx3 += (x4 - tx3) * c;
        ty3 += (y4 - ty3) * c;
        tx1 += (tx2 - tx1) * c;
        ty1 += (ty2 - ty1) * c;
        tx2 += (tx3 - tx2) * c;
        ty2 += (ty3 - ty2) * c;
        vec.x = tx1 + (tx2 - tx1) * c;
        vec.y = ty1 + (ty2 - ty1) * c;    
        return vec;
    }
    function posAtQ(c){
        tx1 = x1; ty1 = y1;
        tx2 = x2; ty2 = y2;
        tx1 += (tx2 - tx1) * c;
        ty1 += (ty2 - ty1) * c;
        tx2 += (x3 - tx2) * c;
        ty2 += (y3 - ty2) * c;
        vec.x = tx1 + (tx2 - tx1) * c;
        vec.y = ty1 + (ty2 - ty1) * c;
        return vec;
    }    
    function forward(dist){
        var step;
        helper.posAt(currentPos);

        while(currentDist < dist){
            vec1.x = vec.x;
            vec1.y = vec.y;            
            currentPos += onePix;
            helper.posAt(currentPos);
            currentDist += step = Math.sqrt((vec.x - vec1.x) * (vec.x - vec1.x) + (vec.y - vec1.y) * (vec.y - vec1.y));

        }
        currentPos -= ((currentDist - dist) / step) * onePix
        currentDist -= step;
        helper.posAt(currentPos);
        currentDist += Math.sqrt((vec.x - vec1.x) * (vec.x - vec1.x) + (vec.y - vec1.y) * (vec.y - vec1.y));
        return currentPos;
    }
    
    function tangentQ(pos){
        a = (1-pos) * 2;
        b = pos * 2;
        vect.x = a * (x2 - x1) + b * (x3 - x2);
        vect.y = a * (y2 - y1) + b * (y3 - y2);       
        u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
        vect.x /= u;
        vect.y /= u;        
    }
    function tangentC(pos){
        a  = (1-pos)
        b  = 6 * a * pos;       
        a *= 3 * a;                  
        c  = 3 * pos * pos; 
        vect.x  = -x1 * a + x2 * (a - b) + x3 * (b - c) + x4 * c;
        vect.y  = -y1 * a + y2 * (a - b) + y3 * (b - c) + y4 * c;
        u = Math.sqrt(vect.x * vect.x + vect.y * vect.y);
        vect.x /= u;
        vect.y /= u;
    }  
    var helper = {
        vec : vec,
        vect : vect,
        forward : forward,
    }
    if(quad){
        helper.posAt = posAtQ;
        helper.tangent = tangentQ;
    }else{
        helper.posAt = posAtC;
        helper.tangent = tangentC;
    }
    return helper
}

Обоснованный текст

В этом примере выполняется выравнивание текста. Он добавляет дополнительные возможности CanvasRenderingContext2D , расширяя его прототип или как глобальный объект justifiedText (необязательно, см. Примечание A).


Пример рендеринга.

введите описание изображения здесь
Код для рендеринга этого изображения приведен в примерах использования внизу .


Пример

Функция как анонимная немедленно вызвала функцию.

(function(){
    const FILL = 0;        // const to indicate filltext render
    const STROKE = 1;
    const MEASURE = 2;
    var renderType = FILL; // used internal to set fill or stroke text
    
    var maxSpaceSize = 3; // Multiplier for max space size. If greater then no justificatoin applied
    var minSpaceSize = 0.5; // Multiplier for minimum space size
    var renderTextJustified = function(ctx,text,x,y,width){
        var words, wordsWidth, count, spaces, spaceWidth, adjSpace, renderer, i, textAlign, useSize, totalWidth;
        textAlign = ctx.textAlign; // get current align settings
        ctx.textAlign = "left";
        wordsWidth = 0;
        words = text.split(" ").map(word => {
            var w = ctx.measureText(word).width;                
            wordsWidth += w;
            return {
                width : w,
                word : word,
            };
        });
        // count = num words, spaces = number spaces, spaceWidth normal space size
        // adjSpace new space size >= min size. useSize Resulting space size used to render
        count = words.length;
        spaces = count - 1;
        spaceWidth = ctx.measureText(" ").width;
        adjSpace = Math.max(spaceWidth * minSpaceSize, (width - wordsWidth) / spaces);
        useSize = adjSpace > spaceWidth * maxSpaceSize ? spaceWidth : adjSpace;
        totalWidth = wordsWidth + useSize * spaces
        if(renderType === MEASURE){ // if measuring return size
            ctx.textAlign = textAlign;
            return totalWidth;
        }
        renderer = renderType === FILL ? ctx.fillText.bind(ctx) : ctx.strokeText.bind(ctx); // fill or stroke
        switch(textAlign){
            case "right":
                x -= totalWidth;
                break;
            case "end":
                x += width - totalWidth;
                break;
            case "center": // intentional fall through to default
                x -= totalWidth / 2;                     
            default:
        }
        if(useSize === spaceWidth){ // if space size unchanged
            renderer(text,x,y);
        } else {
            for(i = 0; i < count; i += 1){
                renderer(words[i].word,x,y);
                x += words[i].width;
                x += useSize;
            }
        }
        ctx.textAlign = textAlign;
    }
    // Parse vet and set settings object.
    var justifiedTextSettings = function(settings){
        var min,max;
        var vetNumber = (num, defaultNum) => {
            num = num !== null && num !== null && !isNaN(num) ? num : defaultNum;
            if(num < 0){
                num = defaultNum;
            }
            return num;
        }
        if(settings === undefined || settings === null){
            return;
        }
        max = vetNumber(settings.maxSpaceSize, maxSpaceSize);
        min = vetNumber(settings.minSpaceSize, minSpaceSize);
        if(min > max){
            return;
        }
        minSpaceSize = min;
        maxSpaceSize = max;
    }
    // define fill text
    var fillJustifyText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        renderType = FILL;
        renderTextJustified(this, text, x, y, width);
    }
    // define stroke text
    var strokeJustifyText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        renderType = STROKE;
        renderTextJustified(this, text, x, y, width);
    }
    // define measure text
    var measureJustifiedText = function(text, width, settings){
        justifiedTextSettings(settings);
        renderType = MEASURE;
        return renderTextJustified(this, text, 0, 0, width);
    }
    // code point A
    // set the prototypes
    CanvasRenderingContext2D.prototype.fillJustifyText = fillJustifyText;
    CanvasRenderingContext2D.prototype.strokeJustifyText = strokeJustifyText;
    CanvasRenderingContext2D.prototype.measureJustifiedText = measureJustifiedText;  
    // code point B
    
    // optional code if you do not wish to extend the CanvasRenderingContext2D prototype
    /* Uncomment from here to the closing comment
    window.justifiedText = {
        fill : function(ctx, text, x, y, width, settings){
            justifiedTextSettings(settings);
            renderType = FILL;
            renderTextJustified(ctx, text, x, y, width);
        },
        stroke : function(ctx, text, x, y, width, settings){
            justifiedTextSettings(settings);
            renderType = STROKE;
            renderTextJustified(ctx, text, x, y, width);
        },
        measure : function(ctx, text, width, settings){
            justifiedTextSettings(settings);
            renderType = MEASURE;
            return renderTextJustified(ctx, text, 0, 0, width);
        }
    }
    to here*/
})();

Примечание. Если вы не хотите распространять прототип CanvasRenderingContext2D из примера весь код между // code point A и // code point B и раскомментируйте код, помеченный /* Uncomment from here to the closing comment


Как пользоваться

Три функции добавляются в CanvasRenderingContext2D и доступны для всех созданных 2D-контекстных объектов.

  • ctx.fillJustifyText (текст, x, y, ширина, [настройки]);
  • ctx.strokeJustifyText (текст, x, y, ширина, [настройки]);
  • ctx.measureJustifiedText (текст, ширина, [настройки]);

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

Примечание. Аргументы внутри [ и ] являются необязательными.

Аргументы функции

  • text: Строка, содержащая текст для визуализации.

  • x, y: координаты для отображения текста.

  • width: Ширина выравниваемого текста. Текст будет увеличивать / уменьшать пробелы между словами, чтобы они соответствовали ширине. Если пространство между словами больше, чем maxSpaceSize (по умолчанию = 6), будет использоваться нормальное расстояние, и текст не заполнит требуемую ширину. Если интервал меньше minSpaceSize интервала времени minSpaceSize (по умолчанию = 0,5), тогда используется размер минимального пространства, и текст будет превышать запрошенную ширину

  • Настройки: Необязательно. Объект, содержащий минимальные и максимальные размеры пространства.

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

Как min, так и max являются минимальными и максимальными размерами для символа [пробела], разделяющего слова. По умолчанию maxSpaceSize = 6 означает, что, когда пробел между символами составляет> 63 * ctx.measureText (""), текст ширины не будет оправдан. Если текст, который должен быть оправдан, имеет пробелы меньше minSpaceSize = 0.5 (значение по умолчанию 0.5) * ctx.measureText(" ").width интервала будет равна minSpaceSize * ctx.measureText(" ").width и полученный текст будет превышен оправдательная ширина.

Применяются следующие правила: min и max должны быть числами. Если нет, то ассоциированные значения не будут изменены. Если minSpaceSize больше, чем maxSpaceSize оба параметра ввода недействительны, а min max не будет изменен.

Пример объекта установки со значениями по умолчанию

settings = { 
    maxSpaceSize : 6;   // Multiplier for max space size. 
    minSpaceSize : 0.5; // Multiplier for minimum space size
};

ПРИМЕЧАНИЕ. Эти текстовые функции вводят тонкое изменение поведения для свойства textAlign для 2D-контекста. «Левый», «правый», «центр» и «начало» ведут себя так, как ожидается, но «конец» не будет выравниваться справа от аргумента функции x а скорее справа от x + width

Примечание. Настройки (минимальный и максимальный размер пространства) являются глобальными для всех объектов 2D-контекста.


Примеры использования

var i = 0;
text[i++] = "This text is aligned from the left of the canvas."; 
text[i++] = "This text is near the max spacing size"; 
text[i++] = "This text is way too short."; 
text[i++] = "This text is too long for the space provied and will overflow#";
text[i++] = "This text is aligned using 'end' and starts at x + width"; 
text[i++] = "This text is near the max spacing size"; 
text[i++] = "This text is way too short."; 
text[i++] = "#This text is too long for the space provied and will overflow";
text[i++] = "This is aligned with 'center' and is placed from the center"; 
text[i++] = "This text is near the max spacing size"; 
text[i++] = "This text is way too short."; 
text[i++] = "This text is just too long for the space provied and will overflow";

// ctx is the 2d context
// canvas is the canvas

ctx.clearRect(0,0,w,h);
ctx.font = "25px arial";
ctx.textAlign = "center"
var left = 20;
var center = canvas.width / 2;
var width = canvas.width-left*2;
var y = 40;
var size = 16;
var i = 0;
ctx.fillText("Justified text examples.",center,y);
y+= 40;
ctx.font = "14px arial";
ctx.textAlign = "left"
var ww = ctx.measureJustifiedText(text[0], width);
var setting = {
    maxSpaceSize : 6,
    minSpaceSize : 0.5
}
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(left,y - size * 2);
ctx.lineTo(left, y + size * 15);
ctx.moveTo(canvas.width - left,y - size * 2);
ctx.lineTo(canvas.width - left, y + size * 15);
ctx.stroke();
ctx.textAlign = "left";
ctx.fillStyle = "red";
ctx.fillText("< 'left' aligned",left,y - size)
ctx.fillStyle = "black";
ctx.fillJustifyText(text[i++], left, y, width, setting);  // settings is remembered
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
y += 2.3*size;
ctx.fillStyle = "red";
ctx.fillText("< 'end' aligned from x plus the width -------------------->",left,y - size)
ctx.fillStyle = "black";
ctx.textAlign = "end";
ctx.fillJustifyText(text[i++], left, y, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);
ctx.fillJustifyText(text[i++], left, y+=size, width);

y += 40;
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(center,y - size * 2);
ctx.lineTo(center, y + size * 5);
ctx.stroke();
ctx.textAlign = "center";
ctx.fillStyle = "red";
ctx.fillText("'center' aligned",center,y - size)
ctx.fillStyle = "black";
ctx.fillJustifyText(text[i++], center, y, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);
ctx.fillJustifyText(text[i++], center, y+=size, width);

Обоснованные абзацы.

Выдает текст в качестве обоснованных абзацев. ТРЕБУЕТСЯ пример Обоснованный текст


Пример визуализации

введите описание изображения здесь
В верхнем абзаце параметр setting.compact = true и нижний false, а интервал между строками - 1,2, а не по умолчанию 1,5 . Представлен примером использования кода внизу этого примера.


Пример кода

 // Requires justified text extensions 
(function(){
    // code point A
    if(typeof CanvasRenderingContext2D.prototype.fillJustifyText !== "function"){
        throw new ReferenceError("Justified Paragraph extension missing requiered CanvasRenderingContext2D justified text extension");
    }
    var maxSpaceSize = 3; // Multiplier for max space size. If greater then no justificatoin applied
    var minSpaceSize = 0.5; // Multiplier for minimum space size   
    var compact = true; // if true then try and fit as many words as possible. If false then try to get the spacing as close as possible to normal
    var lineSpacing = 1.5; // space between lines
    const noJustifySetting = {  // This setting forces justified text off. Used to render last line of paragraph.
        minSpaceSize : 1,
        maxSpaceSize : 1,
    }

    // Parse vet and set settings object.
    var justifiedTextSettings = function(settings){
        var min, max;
        var vetNumber = (num, defaultNum) => {
            num = num !== null && num !== null && !isNaN(num) ? num : defaultNum;
            return num < 0 ? defaultNum : num;
        }
        if(settings === undefined || settings === null){ return; }
        compact = settings.compact === true ? true : settings.compact === false ? false : compact;
        max = vetNumber(settings.maxSpaceSize, maxSpaceSize);
        min = vetNumber(settings.minSpaceSize, minSpaceSize);
        lineSpacing = vetNumber(settings.lineSpacing, lineSpacing);
        if(min > max){ return; }
        minSpaceSize = min;
        maxSpaceSize = max;
    }        
    var getFontSize = function(font){  // get the font size. 
        var numFind = /[0-9]+/;
        var number = numFind.exec(font)[0];
        if(isNaN(number)){
            throw new ReferenceError("justifiedPar Cant find font size");
        }
        return Number(number);
    }
    function justifiedPar(ctx, text, x, y, width, settings, stroke){
        var spaceWidth, minS, maxS, words, count, lines, lineWidth, lastLineWidth, lastSize, i, renderer, fontSize, adjSpace, spaces, word, lineWords, lineFound;
        spaceWidth = ctx.measureText(" ").width;
        minS = spaceWidth * minSpaceSize;
        maxS = spaceWidth * maxSpaceSize;
        words = text.split(" ").map(word => {  // measure all words.
            var w = ctx.measureText(word).width;                
            return {
                width : w,
                word : word,
            };
        });
        // count = num words, spaces = number spaces, spaceWidth normal space size
        // adjSpace new space size >= min size. useSize Resulting space size used to render
        count = 0;
        lines = [];
        // create lines by shifting words from the words array until the spacing is optimal. If compact
        // true then will true and fit as many words as possible. Else it will try and get the spacing as
        // close as possible to the normal spacing
        while(words.length > 0){
            lastLineWidth = 0;
            lastSize = -1;
            lineFound = false;
            // each line must have at least one word.
            word = words.shift();
            lineWidth = word.width;
            lineWords = [word.word];
            count = 0;
            while(lineWidth < width && words.length > 0){ // Add words to line
                word = words.shift();
                lineWidth += word.width;
                lineWords.push(word.word);
                count += 1;
                spaces = count - 1;
                adjSpace =  (width - lineWidth) / spaces;
                if(minS > adjSpace){  // if spacing less than min remove last word and finish line
                    lineFound = true;
                    words.unshift(word);
                    lineWords.pop();
                }else{
                    if(!compact){ // if compact mode 
                        if(adjSpace < spaceWidth){ // if less than normal space width
                            if(lastSize === -1){
                                lastSize = adjSpace;
                            }
                            // check if with last word on if its closer to space width
                            if(Math.abs(spaceWidth - adjSpace) < Math.abs(spaceWidth - lastSize)){
                                lineFound = true; // yes keep it
                            }else{
                                words.unshift(word);  // no better fit if last word removes
                                lineWords.pop();
                                lineFound = true;
                            }
                        }
                    }
                }
                lastSize = adjSpace; // remember spacing 
            }
            lines.push(lineWords.join(" ")); // and the line
        }
        // lines have been worked out get font size, render, and render all the lines. last
        // line may need to be rendered as normal so it is outside the loop.
        fontSize = getFontSize(ctx.font);
        renderer = stroke === true ? ctx.strokeJustifyText.bind(ctx) : ctx.fillJustifyText.bind(ctx);
        for(i = 0; i < lines.length - 1; i ++){
            renderer(lines[i], x, y, width, settings);
            y += lineSpacing * fontSize;
        }
        if(lines.length > 0){ // last line if left or start aligned for no justify
            if(ctx.textAlign === "left" || ctx.textAlign === "start"){
                renderer(lines[lines.length - 1], x, y, width, noJustifySetting);
                ctx.measureJustifiedText("", width, settings);
            }else{
                renderer(lines[lines.length - 1], x, y, width);
            }
        }
        // return details about the paragraph.
        y += lineSpacing * fontSize;
        return {
            nextLine : y,
            fontSize : fontSize,
            lineHeight : lineSpacing * fontSize,
        };
    }
    // define fill
    var fillParagraphText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        settings = {
            minSpaceSize : minSpaceSize,
            maxSpaceSize : maxSpaceSize,
        };
        return justifiedPar(this, text, x, y, width, settings);
    }
    // define stroke
    var strokeParagraphText = function(text, x, y, width, settings){
        justifiedTextSettings(settings);
        settings = {
            minSpaceSize : minSpaceSize,
            maxSpaceSize : maxSpaceSize,
        };
        return justifiedPar(this, text, x, y, width, settings,true);
    }
    CanvasRenderingContext2D.prototype.fillParaText = fillParagraphText;
    CanvasRenderingContext2D.prototype.strokeParaText = strokeParagraphText;
})();

ПРИМЕЧАНИЕ. Это расширяет прототип CanvasRenderingContext2D . Если вы этого не хотите, используйте пример Обоснованный текст, чтобы выяснить, как изменить этот пример как часть глобального пространства имен.

ПРИМЕЧАНИЕ. CanvasRenderingContext2D.prototype.fillJustifyText ReferenceError, если этот пример не может найти функцию CanvasRenderingContext2D.prototype.fillJustifyText


Как пользоваться

ctx.fillParaText(text, x, y, width, [settings]);
ctx.strokeParaText(text, x, y, width, [settings]);

Подробные сведения о аргументах см. В разделе Обозначенный текст . Аргументы между [ и ] необязательны.

Аргумент settings имеет два дополнительных свойства.

  • compact: По умолчанию true . Если true пытается упаковать как можно больше слов на строку. Если false, попытка установить расстояние между словами как можно ближе к нормальному интервалу.
  • lineSpacing По умолчанию 1.5 . Пробел на линию по умолчанию 1.5 Расстояние от строки до следующей с точки зрения размера шрифта

Свойства, отсутствующие в объекте настроек, будут по умолчанию равны их значениям по умолчанию или последним действительным значениям. Свойства будут изменены только в том случае, если новые значения действительны. Для compact допустимых значений используются только логические значения true или false Значения Truthy считаются недействительными.

Возвращаемый объект

Две функции возвращают объект, содержащий информацию, которая поможет вам разместить следующий абзац. Объект содержит следующие свойства.

  • nextLine Позиция следующей строки после пикселя абзаца.
  • fontSize Размер шрифта. (обратите внимание, что используйте только шрифты, определенные в пикселях, например, 14px arial )
  • lineHeight Расстояние в пикселях от одной строки до следующей

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

Поскольку этот пример зависит от примера с обоснованным текстом, код очень похож. Вы можете переместить два в одну функцию. Замените функцию justifiedTextSettings в другом примере на ту, которая используется в этом примере. Затем скопируйте весь остальной код из этого примера в тело анонимной функции в примере с обоснованным текстом . Вам больше не нужно проверять зависимости, найденные в // Code point A Его можно удалить.


Пример использования

ctx.font = "25px arial";
ctx.textAlign = "center"

var left = 10;
var center = canvas.width / 2;
var width = canvas.width-left*2;
var y = 20;
var size = 16;
var i = 0;
ctx.fillText("Justified paragraph examples.",center,y);
y+= 30;
ctx.font = "14px arial";
ctx.textAlign = "left"
// set para settings
var setting = {
    maxSpaceSize : 6,
    minSpaceSize : 0.5,
    lineSpacing : 1.2,
    compact : true,
}
// Show the left and right bounds.
ctx.strokeStyle = "red"
ctx.beginPath();
ctx.moveTo(left,y - size * 2);
ctx.lineTo(left, y + size * 15);
ctx.moveTo(canvas.width - left,y - size * 2);
ctx.lineTo(canvas.width - left, y + size * 15);
ctx.stroke();
ctx.textAlign = "left";
ctx.fillStyle = "black";

// Draw paragraph
var line = ctx.fillParaText(para, left, y, width, setting);  // settings is remembered    

// Next paragraph
y = line.nextLine + line.lineHeight;
setting.compact = false;
ctx.fillParaText(para, left, y, width, setting);

Примечание. Если текст выравнивается left или start последняя строка абзаца всегда будет иметь нормальный интервал. Для всех остальных выравниваний последняя строка рассматривается как все остальные.

Примечание. Вы можете вставить начало абзаца с пробелами. Хотя это может быть несовместимо с абзацем. Всегда полезно узнать, что делает функция и ее модификация. Упражнение должно было бы добавить настройку к настройкам, которые отступают от первой строки на фиксированную сумму. Подсказка: в цикле while необходимо временно сделать первое слово более крупным (+ отступом) words[0].width += ? а затем при визуализации строк отступ первой строки.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow