Question

A few years ago I made a Javascript script for APNGedit to draw the Laughing Man logo. It used the now defunct mozTextAlongPath

I recently rediscovered this script and redid it using translations, rotations and fillText(). However, this doesn't respect character width nor is it kerned (it looks terrible).

Original circa 2009 (not perfect, but okay):

original laughing man using mozTextAlongPath

Current version:

new version using fillText

How can I draw text in an arc on an HTML5 canvas and make it look good?


Solution Code based on Kolink's answer:

ctx.fillStyle = primaryColor;
ctx.font = fontSize + 'px ' + fontFamily;

var textWidth = ctx.measureText(text).width,
    charRotation = 0,
    character, charWidth, nextChar, nextWidth, bothWidth, kern, extraRotation, charSegment;

for (var i=0, l=text.length; i<l; i++) {
    character = nextChar || text[i];
    charWidth = nextWidth || ctx.measureText(character).width;

    // Rotate so the letter base makes a circle segment instead of a tangent
    extraRotation = (Math.PI/2) - Math.acos((charWidth/2) / radius);

    ctx.save();
    ctx.translate(radius, h/2);
    ctx.rotate(charRotation);
    ctx.translate(0, -textRadius);
    ctx.rotate(extraRotation);
    ctx.fillText(character,0,0);
    ctx.restore();

    nextChar = text[i+1] || '';
    nextWidth = ctx.measureText(nextChar).width;

    bothWidth = ctx.measureText(character+nextChar).width;
    kern = bothWidth - charWidth - nextWidth;

    charSegment = (charWidth+kern) / textWidth; // percent of total text size this takes up
    charRotation += charSegment * (Math.PI*2);
}

fixed

Was it helpful?

Solution

Obviously, it is no difficulty to place letters on the arc itself (just align center bottom to the circle). However, as you noted, the problem is kerning.

Luckily, we have measureText(), which can tell us the width of the letters and therefore what kerning to use.

The circumference of your circle is simply 2πr, and total width of the text is ctx.measureText("Your text here");. Get the ratio of these two values and you will find out how much you need to space out or squash together your words.

You probably want to apply the spacing modifier to the words as a whole, rather than the individual letters. To do this, use measureText() on the sentence with spaces stripped to get the width of the letters (and by extension the total width of the spaces).

Now you need to plot where each word will go. Use measureText() again to find the width of each word and plot its center point on your circle, adding a portion of the total space value between each word. Now use measureText() on each individual letter and draw it in the right place to get perfect kerning.

All being well, you should have a perfectly spaced circle of text.

OTHER TIPS

So measure text is good, what I ended up doing, was Math.pow(measureText + measureTextOfLastChar, 3 / 4)

for some reason, square root of the sum of the widths of the current and previous character made some spacings too skinny, and without a square root at all, makes it bad too, but Math.pow(sum, 3/4) for some reason creates a great ratio. Heres the code ( in coffeescript )

CanvasRenderingContext2D::fillTextCircle = (str, centerX, centerY, radius, angle) ->
  len = str.length
  s = undefined
  @save()
  @translate centerX, centerY
  @rotate - (1 + 1 / len) * angle / 2
  n = 0

  prevWidth = 0
  while n < len
    thisWidth = @measureText(str[n]).width
    @rotate angle / len * Math.pow(thisWidth + prevWidth, 3 / 4) / @measureText(str).width
    s = str[n]
    prevWidth = @measureText(str[n]).width
    @fillText s, -@measureText(str[n]).width / 2, -1 * radius
    n++
  @restore()

then call it using

context.fillTextCircle('hiya world', halfWidth, halfHeight, 95, 26)

I was guessing and checking a bit, though I took calc 4 so I subconsciously knew what I was doing. Anyway, it produces perfect character spacing, that you couldn't get without Math.pow(sum_character_widths, 3/4)

Everything can be changed, except keep the Math.pow(sum, 3/4) in the loop, since that's the part I made better than the rest of the stuff I found online.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top