Text Rendering using canvas-1

Text Rendering using canvas-1
Nikhil
Published on 2021-07-05 06:16:55

Text on curve, cubic and quadratic beziers


textOnCurve(text,offset,x1,y1,x2,y2,x3,y3,x4,y4)

Renders text on quadratic and cubic curves.

  • text is the text to render
  • offset distance from start of curve to text >= 0
  • x1,y1 - x3,y3 points of quadratic curve or
  • x1,y1 - x4,y4 points of cubic curve or

Example usage:

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

 

The Function and curver helper function


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

 

Drawing Text using Text rendering 


Drawing to canvas isn't just limited to shapes and images. You can also draw text to the canvas.

To draw text on the canvas, get a reference to the canvas and then call the fillText method on the context.

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

The three required arguments that are passed into fillText are:

1. The text that you would like to display
2. The horizontal (x-axis) position
3. The vertical (y-axis) position

Additionally, there is a fourth optional argument, which you can use to specify the maximum width of your text in pixels. In the example below the value of 200 restricts the maximum width of the text to 200px:

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

You can also draw text without a fill, and just an outline instead, using the strokeText method:

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

Without any font formatting properties applied, the canvas renders text at 10px in sans-serif by default, making it hard to see the difference between the result of the fillText and strokeText methods. See the Formatting Text example for details on how to increase text size and apply other aesthetic changes to text.

 

Text Rendering Formatting Text


The default font formatting provided by the fillText and strokeText methods isn't very aesthetically appealing. Fortunately the canvas API provides properties for formatting text.

Using the font property you can specify:

  • font-style
  • font-variant
  • font-weight
  • font-size / line-height
  • font-family

For example:

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

Using the textAlign property you can also change text alignment to either:

  • left
  • center
  • right
  • end (same as right)
  • start (same as left)

For example:

ctx.textAlign = "center";

 

Wrapping text into paragraphs


Native Canvas API does not have a method to wrap text onto the next line when a desired maximum width is reached. This example wraps text into paragraphs.

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

 

Draw text paragraphs into irregular shapes


This example draws text paragraphs into any portions of the canvas that have opaque pixels.

It works by finding the next block of opaque pixels that is large enough to contain the next specified word and filling that block with the specified word.

The opaque pixels can come from any source: Path drawing commands and /or images.

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

 

Fill text with an image using Text rendering


This example fills text with a specified image.

Important! The specified image must be fully loaded before calling this function or the drawing will fail. Use image.onload to be sure the image is fully loaded.

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

 

ATutorialHub Related Guide

Comments (8)

Leave a Comment

Your email address will not be published. Required fields are marked*

User Comments

html tutorial comments

panduranga gupta

2021-07-05 07:03:13

good website for learning and help me a lot

html tutorial comments

raju

2021-09-25 14:58:47

The awsome website i am looking like for a long time, good work atutorialhub team keep doing

html tutorial comments

Shivani

2021-09-01 15:03:56

Learning a lot from the courses present on atutorialhub. The courses are very well explained. Great experience

html tutorial comments

Harshitha

2021-09-10 15:05:45

It is very helpful to students and easy to learn the concepts

html tutorial comments

Sowmya

2021-09-14 15:06:41

Great job Tutorials are easy to understand Please make use of it

html tutorial comments

Zain Khan

2021-09-18 15:07:23

Great content and customized courses.

html tutorial comments

Rudrakshi Bhatt

2021-09-09 15:08:10

Well structured coursed and explained really well!

html tutorial comments

Pavana Somashekar

2021-09-11 15:09:08

Good platform for beginners and learn a lot on this website