在HTML Canvas中添加字母间距

5
我已经阅读了很多关于如何在Canvas中设置字母间距的StackOverflow答案和其他页面。其中一个更有用的是Canvas元素中的字母间距
正如另一个问题所说,'我有一个Canvas元素,我要在其上绘制文本。我想设置类似于CSS letter-spacing属性的字母间距。我的意思是,在绘制字符串时增加字母之间的像素量。'请注意,字母间距有时会被错误地称为字距。
我注意到一般的方法似乎是逐个字母地输出字符串,使用measureText(letter)来获取字母的宽度,然后添加额外的间距。这种方法的问题在于它没有考虑字距对和类似的东西。请参见上面的链接以及相关评论的示例。
对我来说,为了实现行间距为“spacing”,应该这样做:
  1. 从位置(X,Y)开始。
  2. 使用measureText()测量整个字符串的宽度wAll
  3. 从字符串中删除第一个字符
  4. 使用fillText()在位置(X,Y)打印第一个字符
  5. 使用measureText()测量结果较短字符串的宽度wShorter。
  6. 将较短字符串的宽度从整个字符串的宽度中减去,得到字符的字距宽度wChar = wAll-wShorter
  7. 通过wChar + spacing增加X
  8. wAll = wShorter
  9. 从步骤3重复

这样不会考虑到字距吗?我有什么遗漏吗?measureText()是否添加了一堆填充,这些填充取决于最外层的字符,如果是这样,那么fillText()是否使用相同的系统来输出字符,从而抵消该问题?上面的链接中有人提到“像素对齐的字体提示”,但我不知道它如何应用于此。任何人都可以通俗易懂地解释一下这是否有效或存在问题吗?

编辑:这不是另一个问题的重复 - 它链接并参考了其他问题。该问题不是关于如何在画布中进行“字母间距调整”的,因此不是所提出的重复问题; 这是提出了可能的解决方案(据我所知,没有其他人提出),以解决该问题和其他问题,并询问是否有人能看到或知道该建议解决方案的任何问题 - 即它正在询问所提出的解决方案及其要点,包括measureText()的详细信息,fillText()和“像素对齐字体提示”。


我已经进行了一些初步测试,看起来它可以工作...但我还没有进行详尽的测试。 - James Carlyle-Clarke
1
你的问题不清楚你试图达到什么目的?如果你想要精确地将一个字母居中在[x,y]上,可以这样做:context.textAlign='center' 水平居中于X轴,context.textBaseline='middle' 垂直居中于Y轴,context.fillText('A',x,y) 在[x,y]处绘制居中的 A。 - markE
请分享一些代码... - Bob van Luijt
@markE,我希望现在更清楚了。很抱歉我误将您的评论标记为有用,而这与我想要的不相关,但是您对它不够清楚的评论至少促使我澄清,所以谢谢。 - James Carlyle-Clarke
@bvl - 我提供伪代码的原因是因为我没有实际代码,而且我希望只有在它有用的时候才写出来。不过,在等待答案的同时,我已经写了代码,你可以在下面的答案中看到。 - James Carlyle-Clarke
可能是Canvas元素中的字母间距的重复问题。 - Krisztián Balla
2个回答

4

好的,我已经根据上面的伪代码编写了代码,并通过截图和眼球比较进行了一些比较(缩放,使用直线从例如剪贴板框比较每个字符的X位置和宽度)。对我来说看起来完全相同,间距设为0。

以下是HTML:

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

以下是代码:

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 != "");
}

演示/眼球测试代码:

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

理想情况下,我会输入多个随机字符串,并逐像素进行比较。我也不确定Verdana字体的默认字距有多好,尽管我知道它比Arial更好 - 欢迎提出其他字体的建议。
到目前为止,一切看起来都很好。实际上,看起来完美无缺。 仍然希望有人能指出这个过程中的任何缺陷。
同时,我将把这个解决方案放在这里供其他人查看。

需要注意的是,虽然这不完全是对我的问题的回答,但它确实是对许多关于如何做到这一点的问题的回答,并且它就是为此而存在的。如果有人回答我的问题并解释为什么这不是一个好的系统,那么那个答案显然会取代我的答案。 - James Carlyle-Clarke
该解决方案的缺点是它具有O(n ^ 2)的复杂度,因为measureText在字符串中循环剩余字符,并且你每个字符调用一次。看起来很容易将其转换为仅考虑当前和下一个字符,这将使它成为O(n)算法。除此之外,非常好的解决方案! - Joaquin Cuenca Abela
@JoaquinCuencaAbela - 我同意你提出的原因,这可能有些过度了。我之所以保留它,是因为我不确定是否根据整个字符串(而不是字符对)添加了某种“神奇酱料”,但我怀疑你的添加将得到完全相同的结果,如果不是的话,至少连续三个字符或整个单词可能就足够了。欢迎任何人进行测试-请回帖。 - James Carlyle-Clarke
两点 -
  1. 这个解决方案非常适用于大多数拉丁文字(英语,法语等),但对来自非拉丁文字的字符(如婆罗米文,阿拉伯文,希伯来文等)会给出错误的结果。
  2. 这个解决方案可以稍微修改一下,以便正确处理表情符号。
- Nirmit Dalal
嗨@NirmitDalal,你能详细介绍一下你对表情符号建议的修改吗? - James Carlyle-Clarke

0

我的答案被删除了。 所以,我正在使用Chrome,这是我的完整代码。

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

而且你不需要使用这个函数this.fillTextWithSpacing。我没有使用它,但它仍然像魅力一样工作)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接