James Carlyle-Clarke
James Carlyle-Clarke

Reputation: 868

Adding Letter Spacing in HTML Canvas

I've read a lot of StackOverflow answers and other pages talking about how to do letter spacing in Canvas. One of the more useful ones was Letter spacing in canvas element

As that other question said, 'I've got this canvas element that I'm drawing text to. I want to set the letter spacing similar to the CSS letter-spacing attribute. By that I mean increasing the amount of pixels between letters when a string is drawn.' Note that letter spacing is sometimes, and incorrectly, referred to as kerning.

I notice that the general approach seems to be to output the string on a letter by letter basis, using measureText(letter) to get the letter's width and then adding additional spacing. The problem with this is it doesn't take into account letter kerning pairs and the like. See the above link for an example of this and related comments.

Seems to me that the way to do it, for a line spacing of 'spacing', would be to do something like:

  1. Start at position (X, Y).
  2. Measure wAll, the width of the entire string using measureText()
  3. Remove the first character from the string
  4. Print the first character at position (X, Y) using fillText()
  5. Measure wShorter, the width of the resulting shorter string using measureText().
  6. Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
  7. Increment X by wChar + spacing
  8. wAll = wShorter
  9. Repeat from step 3

Would this not take into account kerning? Am I missing something? Does measureText() add a load of padding that varies depending on the outermost character, or something, and if it does, would not fillText() use the same system to output the character, negating that issue? Someone in the link above mentioned 'pixel-aligned font hinting' but I don't see how that applies here. Can anyone advise either generally or specifically if this will work or if there are problems with it?

EDIT: This is not a duplicate of the other question - which it links to and refers to. The question is NOT about how to do 'letter spacing in canvas', per the proposed duplicate; this is proposing a possible solution (which as far as I know was not suggested by anyone else) to that and other questions, and asking if anyone can see or knows of any issues with that proposed solution - i.e. it's asking about the proposed solution and its points, including details of measureText(), fillText() and 'pixel-aligned font hinting'.

Upvotes: 5

Views: 5076

Answers (2)

Artyom Hachikov
Artyom Hachikov

Reputation: 15

My answer got deleted. So, I'm using chrome and here is my complete code.

second_image = $('#block_id').first();
canvas = document.getElementById('canvas');
canvas.style.letterSpacing = '2px';
ctx = canvas.getContext('2d');
canvas.crossOrigin = "Anonymous";
canvasDraw = function(text, font_size, font_style, fill_or_stroke){
    canvas.width = second_image.width();
    canvas.height = second_image.height();
    ctx.clearRect(0,0,canvas.width,canvas.height);
    ctx.drawImage(second_image.get(0), 0, 0, canvas.width, canvas.height);
    //refill text
    ctx.font = font_size +'px '+ font_style + ',Symbola';
    $test = ctx.font;
    ctx.textAlign = "center";
    if(fill_or_stroke){
        ctx.fillStyle = "#d2b76d";
        ctx.strokeStyle = "#9d8a5e";
        ctx.strokeText(text,canvas.width*$left,canvas.height*$top);
        ctx.fillText(text,canvas.width*$left,canvas.height*$top);
    }
    else{
        ctx.strokeStyle = "#888888";
        ctx.strokeText(text,canvas.width*$left,canvas.height*$top);
    }
};

And you don't need to use this function this.fillTextWithSpacing. I didn't use and it worked like a charm)

Upvotes: 0

James Carlyle-Clarke
James Carlyle-Clarke

Reputation: 868

Well, I've written the code, based on the pseudocode above, and done a few comparisons by screenshotting and eyeballing it for differences (zoomed, using straight lines from eg clip boxes to compare X position and width for each character). Looks exactly the same for me, with spacing set at 0.

Here's the HTML:

<canvas id="Test1" width="800px" height="200px"><p>Your browser does not support canvas.</p></canvas>

Here's the code:

this.fillTextWithSpacing = function(context, text, x, y, spacing)
{
    //Start at position (X, Y).
    //Measure wAll, the width of the entire string using measureText()
    wAll = context.measureText(text).width;

    do
    {
    //Remove the first character from the string
    char = text.substr(0, 1);
    text = text.substr(1);

    //Print the first character at position (X, Y) using fillText()
    context.fillText(char, x, y);

    //Measure wShorter, the width of the resulting shorter string using measureText().
    if (text == "")
        wShorter = 0;
    else
        wShorter = context.measureText(text).width;

    //Subtract the width of the shorter string from the width of the entire string, giving the kerned width of the character, wChar = wAll - wShorter
    wChar = wAll - wShorter;

    //Increment X by wChar + spacing
    x += wChar + spacing;

    //wAll = wShorter
    wAll = wShorter;

    //Repeat from step 3
    } while (text != "");
}

Code for demo/eyeball test:

element1 = document.getElementById("Test1");
textContext1 = element1.getContext('2d');

textContext1.font = "72px Verdana, sans-serif";
textContext1.textAlign = "left";
textContext1.textBaseline = "top";
textContext1.fillStyle = "#000000";

text = "Welcome to go WAVE";
this.fillTextWithSpacing(textContext1, text, 0, 0, 0);
textContext1.fillText(text, 0, 100);

Ideally I'd throw multiple random strings at it and do a pixel by pixel comparison. I'm also not sure how good Verdana's default kerning is, though I understand it's better than Arial - suggestions on other fonts to try gratefully accepted.

So... so far it looks good. In fact it looks perfect. Still hoping that someone will point out any flaws in the process.

In the meantime I will put this here for others to see if they are looking for a solution on this.

Upvotes: 4

Related Questions