简体   繁体   English

画布元素中的字母间距

[英]Letter spacing in canvas element

The question says it all pretty much.这个问题几乎说明了一切。 I've been searching around and starting to worry that it's impossible.我一直在四处寻找,开始担心这是不可能的。

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.我想设置类似于 CSS letter-spacing属性的letter-spacing By that I mean increasing the amount of pixels between letters when a string is drawn.我的意思是在绘制字符串时增加字母之间的像素数量。

My code for drawing the text is like so, ctx is the canvas context variable.我绘制文本的代码是这样的,ctx 是画布上下文变量。

ctx.font = "3em sheepsans";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillText("Blah blah text", 1024 / 2, 768 / 2);

I've tried adding ctx.letterSpacing = "2px";我试过添加ctx.letterSpacing = "2px"; before the drawing but with no avail.在绘图之前,但无济于事。 Is there a way to do this just with a simple setting, or will I have to make a function to individually draw each character with the spacing in mind?有没有办法只用一个简单的设置来做到这一点,或者我是否必须制作一个函数来单独绘制每个字符并记住间距?

You can't set the letter spacing property, but you you can accomplish wider letter spacing in canvas by inserting one of the various white spaces in between every letter in the string.您无法设置字母间距属性,但您可以通过在字符串中的每个字母之间插入各种空格之一来在画布中实现更宽的字母间距。 For instance例如

ctx.font = "3em sheepsans";
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.fillStyle = "rgb(255, 255, 255)";
var ctext = "Blah blah text".split("").join(String.fromCharCode(8202))
ctx.fillText(ctext, 1024 / 2, 768 / 2);

This will insert a hair space between every letter.这将在每个字母之间插入一个头发空间

Using 8201 (instead of 8202) will insert the slightly wider thin space使用 8201(而不是 8202)将插入稍宽的薄空间

For more white space options, see this list of Unicode Spaces有关更多空白选项,请参阅此 Unicode 空间列表

This method will help you to preserve the font's kerning much more easily than manually positioning each letter, however you wont be able to tighten your letter spacing this way.与手动定位每个字母相比,此方法将帮助您更轻松地保留字体的字距,但是您将无法以这种方式收紧字母间距。

I'm not sure if it should work (per specs), but in some browsers (Chrome) you can set the letter-spacing CSS property on the <canvas> element itself, and it will be applied to all text drawn on the context.我不确定它是否应该工作(根据规范),但在某些浏览器(Chrome)中,您可以在<canvas>元素本身上设置letter-spacing CSS 属性,它将应用于在上下文中绘制的所有文本. (Works in Chrome v56, does not work in Firefox v51 or IE v11.) (适用于 Chrome v56,不适用于 Firefox v51 或 IE v11。)

Note that in Chrome v56 you must re-get the canvas 2d context (and re-set any values you care about) after each change to the letter-spacing style;请注意,在 Chrome v56 中,每次更改letter-spacing样式后,您都必须重新获取画布 2d 上下文(并重新设置您关心的任何值); the spacing appears to be baked into the 2d context that you get.间距似乎已融入您获得的 2d 上下文中。

Example: https://jsfiddle.net/hg4pbsne/1/示例: https : //jsfiddle.net/hg4pbsne/1/

 var inp = document.querySelectorAll('input'), can = document.querySelector('canvas'), ctx = can.getContext('2d'); can.width = can.offsetWidth; [].forEach.call(inp,function(inp){ inp.addEventListener('input', redraw, false) }); redraw(); function redraw(){ ctx.clearRect(0,0,can.width,can.height); can.style.letterSpacing = inp[0].value + 'px'; ctx = can.getContext('2d'); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = '4em sans-serif'; ctx.fillText('Hello', can.width/2, can.height*1/4); can.style.letterSpacing = inp[1].value + 'px'; ctx = can.getContext('2d'); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = '4em sans-serif'; ctx.fillText('World', can.width/2, can.height*3/4); };
 canvas { background:white } canvas, label { display:block; width:400px; margin:0.5em auto }
 <canvas></canvas> <label>hello spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label> <label>world spacing: <input type="range" min="-20" max="40" value="1" step="0.1"></label>


Original, cross-browser answer:原始的跨浏览器答案:

This is not possible;这不可能; the HTML5 Canvas does not have all the text-transformation power of CSS in HTML. HTML5 Canvas 不具备 HTML 中 CSS 的所有文本转换能力。 I would suggest that you should combine the appropriate technologies for each usage.我建议您应该为每种用途组合适当的技术。 Use HTML layered with Canvas and perhaps even SVG, each doing what it does best.使用与 Canvas 甚至 SVG 分层的 HTML,每个都做它最擅长的。

Note also that 'rolling your own'—drawing each character with a custom offset—is going to produce bad results for most fonts, given that there are letter kerning pairs and pixel-aligned font hinting.另请注意,鉴于存在字母字距调整对和像素对齐字体提示,“滚动您自己的”(使用自定义偏移量绘制每个字符)将对大多数字体产生不良结果。

You can't set letter-spacing as a property of the Canvas context.您不能将字母间距设置为 Canvas 上下文的属性。 You can only achieve the effect by doing manual spacing, sorry.您只能通过手动间距来达到效果,抱歉。 (As in, drawing each letter manually increasing the x by some pixel amount on each) (如,手动绘制每个字母将 x 增加一些像素量)

For the record, you can set a few text properties by using ctx.font but letter-spacing is not one of them.作为记录,您可以使用ctx.font设置一些文本属性,但字母间距不是其中之一。 The ones you can set are: "font-style font-variant font-weight font-size/line-height font-family"可以设置的是:“字体样式字体变体字体粗细字体大小/行高字体系列”

For instance you can technically write ctx.font = "bold normal normal 12px/normal Verdana" (or any omission of any of those) and it will parse correctly.例如,您可以在技术上编写ctx.font = "bold normal normal 12px/normal Verdana" (或其中任何一个的任何省略),它会正确解析。

To allow for 'letter kerning pairs' and the like, I've written the following.为了允许“字母字距调整对”等,我写了以下内容。 It should take that into account, and rough testing suggests it does.它应该考虑到这一点,粗略的测试表明确实如此。 If you have any comments on it then I would point you to my question on the subject ( Adding Letter Spacing in HTML Canvas )如果您对此有任何评论,那么我会向您指出我关于该主题的问题( 在 HTML Canvas 中添加字母间距

Basically it uses measureText() to get the width of the whole string, and then removes the first character of the string and measures the width of the remaining string, and uses the difference to calculate the correct positioning - thus taking into account kerning pairs and the like.基本上它使用 measureText() 来获取整个字符串的宽度,然后删除字符串的第一个字符并测量剩余字符串的宽度,并使用差异来计算正确的定位 - 从而考虑到字距对和类似。 See the given link for more pseudocode.有关更多伪代码,请参阅给定链接。

Here's the HTML:这是 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.我也不确定 Verdana 的默认字距调整有多好,但我知道它比 Arial 更好 - 关于其他字体的建议尝试被感激地接受。

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.与此同时,我将把它放在这里供其他人看看他们是否正在寻找解决方案。

here's some coffeescript that allows you to set kerning to your context like so这是一些咖啡脚本,允许您像这样将字距调整到您的上下文

tctx = tcanv.getContext('2d')
tctx.kerning = 10
tctx.fillStyle = 'black'
tctx.fillText 'Hello World!', 10, 10

the supporting code is:支持代码是:

_fillText = CanvasRenderingContext2D::fillText
CanvasRenderingContext2D::fillText = (str, x, y, args...) ->

  # no kerning? default behavior
  return _fillText.apply this, arguments unless @kerning?

  # we need to remember some stuff as we loop
  offset = 0

  _.each str, (letter) =>

    _fillText.apply this, [
      letter
      x + offset + @kerning
      y
    ].concat args # in case any additional args get sent to fillText at any time

    offset += @measureText(letter).width + @kerning

The javascript would be javascript将是

var _fillText,
  __slice = [].slice;

_fillText = CanvasRenderingContext2D.prototype.fillText;

CanvasRenderingContext2D.prototype.fillText = function() {
  var args, offset, str, x, y,
    _this = this;

  str = arguments[0], x = arguments[1], y = arguments[2], args = 4 <= arguments.length ? __slice.call(arguments, 3) : [];
  if (this.kerning == null) {
    return _fillText.apply(this, arguments);
  }
  offset = 0;

  return _.each(str, function(letter) {
    _fillText.apply(_this, [letter, x + offset + _this.kerning, y].concat(args));
    offset += _this.measureText(letter).width + _this.kerning;
  });
};

Not true.不对。 You can add letter-spacing property to the canvas element in css and it works perfectly.您可以在 css 中为 canvas 元素添加 letter-spacing 属性,它可以完美运行。 No need for complicated workarounds.不需要复杂的解决方法。 I just figured it out right now in my canvas project.我现在才在我的画布项目中弄清楚了。 ie: canvas { width: 480px;即:画布{宽度:480px; height: 350px;高度:350px; margin: 30px auto 0;边距:30px 自动 0; padding: 15px 0 0 0;填充:15px 0 0 0; background: pink;背景:粉色; display: block;显示:块; border: 2px dashed brown;边框:2px 虚线棕色; letter-spacing: 0.1em;字母间距:0.1em; } }

Actually letter spacing concept canvas is not supporting.实际上字母间距概念画布不支持。

So i used javascript to do this.所以我用javascript来做到这一点。

var value = $('#sourceText1').val().split("").join(" ");

OR要么

var sample_text = "Praveen Chelumalla";
var text = sample_text.split("").join(" ");

I don't know about other people but I have adjusted line spacing by increasing the y value on the text that I am writing.我不知道其他人,但我通过增加我正在写的文本的 y 值来调整行距。 I'm actually splitting a string by spaces and kicking each word down a line inside a loop.我实际上是用空格分割一个字符串并将每个单词踢下一个循环内的一行。 The numbers i use are based on the default font.我使用的数字基于默认字体。 If you use a different font that these numbers may need to be adjusted.如果您使用不同的字体,这些数字可能需要调整。

// object holding x and y coordinates
var vectors = {'x':{1:100, 2:200}, 'y':{1:0, 2:100}
// replace the first letter of a word
var newtext = YOURSTRING.replace(/^\b[a-z]/g, function(oldtext) {
    // return it uppercase
    return oldtext.toUpperCase(); 
});
// split string by spaces
newtext = newtext.split(/\s+/);

// line height
var spacing = 10 ;
// initial adjustment to position
var spaceToAdd = 5;
// for each word in the string draw it based on the coordinates + spacing
for (var c = 0; c < newtext.length; c++) {
    ctx.fillText(newtext[c], vectors.x[i], vectors.y[i] - spaceToAdd);
    // increment the spacing 
    spaceToAdd += spacing;
}               

Here's another method based on James Carlyle-Clarke's previous answer.这是基于 James Carlyle-Clarke 之前回答的另一种方法。 It also lets you align the text left, center and right.它还允许您将文本左、中、右对齐。

export function fillTextWithSpacing(context, text, x, y, spacing, textAlign) {
    const totalWidth = context.measureText(text).width + spacing * (text.length - 1);
    switch (textAlign) {
        case "right":
            x -= totalWidth;
            break;
        case "center":
            x -= totalWidth / 2;
            break;
    }
    for (let i = 0; i < text.length; i++) {
        let char = text.charAt(i);
        context.fillText(char, x, y);
        x += context.measureText(char).width + spacing;
    }
}

This might an old question, but it's still relevant.这可能是一个老问题,但它仍然相关。 I took Patrick Matte's expansion of James Carlyle-Clarke's response and got something that, I think, works quite well as is still plain-old-Javascript.我采用了 Patrick Matte 对 James Carlyle-Clarke 回应的扩展,并得到了一些我认为效果很好的东西,因为它仍然是普通的 Javascript。 The idea is to measure the space between two consecutive characters and "add" to it.这个想法是测量两个连续字符之间的空间并“添加”到它。 Yes, negative numbers work.是的,负数有效。

Here's what I've got (the heavily commented version):这是我得到的(大量评论的版本):

function fillTextWithSpacing (context, text, x, y, spacing) {
    // Total width is needed to adjust the starting X based on text alignment.
    const total_width = context.measureText (text).width + spacing * (text.length - 1);

    // We need to save the current text alignment because we have to set it to
    // left for our calls to fillText() draw in the places we expect. Don't
    // worry, we're going to set it back at the end.
    const align = context.textAlign;
    context.textAlign = "left";

    // Nothing to do for left alignment, but adjustments are needed for right
    // and left. Justify defeats the purpose of manually adjusting character
    // spacing, and it requires a width to be known.
    switch (align) {
        case "right":
            x -= total_width;
            break;
        case "center":
            x -= total_width / 2;
            break;
    }

    // We have some things to keep track of and the C programmer in me likes
    // declarations on their own and in groups.
    let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next;

    // We're going to step through the text one character at a time, but we
    // can't use for(... of ...) because we need to be able to look ahead.
    for (offset = 0; offset < text.length; offset = offset + 1) {
        // Easy on the eyes later
        char = text.charAt (offset);
        // Default the spacing between the "pair" of characters to 0. We need
        // for the last character.
        pair_spacing = 0;
        // Check to see if there's at least one more character after this one.
        if (offset + 1 < text.length) {
            // This is always easier on the eyes
            char_next = text.charAt (offset + 1);
            // Measure to the total width of both characters, including the
            // spacing between them... even if it's negative.
            pair_width = context.measureText (char + char_next).width;
            // Measure the width of just the current character.
            char_width = context.measureText (char).width;
            // Measure the width of just the next character.
            char_next_width = context.measureText (char_next).width;
            // We can determine the kerning by subtracting the width of each
            // character from the width of both characters together.
            pair_spacing = pair_width - char_width - char_next_width;
        }

        // Draw the current character
        context.fillText (char, x, y);
        // Advanced the X position by adding the current character width, the
        // spacing between the current and next characters, and the manual
        // spacing adjustment (negatives work).
        x = x + char_width + pair_spacing + spacing;
    }

    // Set the text alignment back to the original value.
    context.textAlign = align;

    // Profit
}

And here's a demo:这是一个演示:

 let canvas = document.getElementById ("canvas"); canvas.width = 600; canvas.height = 150; let context = canvas.getContext ("2d"); function fillTextWithSpacing (context, text, x, y, spacing) { const total_width = context.measureText (text).width + spacing * (text.length - 1); const align = context.textAlign; context.textAlign = "left"; switch (align) { case "right": x -= total_width; break; case "center": x -= total_width / 2; break; } let offset, pair_width, char_width, char_next_width, pair_spacing, char, char_next; for (offset = 0; offset < text.length; offset = offset + 1) { char = text.charAt (offset); pair_spacing = 0; if (offset + 1 < text.length) { char_next = text.charAt (offset + 1); pair_width = context.measureText (char + char_next).width; char_width = context.measureText (char).width; char_next_width = context.measureText (char_next).width; pair_spacing = pair_width - char_width - char_next_width; } context.fillText (char, x, y); x = x + char_width + pair_spacing + spacing; } context.textAlign = align; } function update () { let font = document.getElementById ("font").value, size = parseInt (document.getElementById ("size").value, 10), weight = parseInt (document.getElementById ("weight").value, 10), italic = document.getElementById ("italic").checked, spacing = parseInt (document.getElementById ("spacing").value, 10), text = document.getElementById ("text").value; context.textAlign = "center"; context.textBaseline = "alphabetic"; context.fillStyle = "#404040"; context.font = (italic ? "italic " : "") + weight + " " + size + "px " + font; context.clearRect (0, 0, canvas.width, canvas.height); fillTextWithSpacing (context, text, canvas.width / 2, (canvas.height + size) / 2, spacing); } document.getElementById ("font").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("size").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("weight").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("italic").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("spacing").addEventListener ( "change", (event) => { update (); } ); document.getElementById ("text").addEventListener ( "input", (event) => { update (); } ); update ();
 select, input { display: inline-block; } input[type=text] { display: block; margin: 0.5rem 0; } canvas { border: 1px solid #b0b0b0; width: 600px; height: 150px; }
 <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8" /> </head> <body> <select id="font"> <option value="serif">Serif</option> <option value="sans-serif">Sans Serif</option> <option value="fixed-width">Fixed Width</option> </select> <label>Size: <input type="number" id="size" value="60" min="1" max="200" size="3" /></label> <label>Weight: <input type="number" id="weight" value="100" min="100" max="1000" step="100" size="4" /></label> <label>Italic: <input type="checkbox" id="italic" checked /></label> <label>Spacing: <input type="number" id="spacing" value="0" min="-200" max="200" size="4" /></label> <input type="text" id="text" placeholder="Text" value="hello" size="40"/> <canvas id="canvas"></canvas> </body> </html>

Letter spacing in canvas IS SUPPORTED, I used this支持画布中的字母间距,我使用了这个

canvas = document.getElementById('canvas');
canvas.style.letterSpacing = '2px';

I use:我用:

ctx.font = "32px Tahoma";//set font
ctx.scale(0.75,1);//important! the scale
ctx.fillText("LaFeteParFete test text", 2, 274);//draw
ctx.setTransform(1,0,0,1,0,0);//reset transform

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM