繁体   English   中英

画布元素中的字母间距

[英]Letter spacing in canvas element

这个问题几乎说明了一切。 我一直在四处寻找,开始担心这是不可能的。

我有我要向其绘制文本的画布元素。 我想设置类似于 CSS letter-spacing属性的letter-spacing 我的意思是在绘制字符串时增加字母之间的像素数量。

我绘制文本的代码是这样的,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);

我试过添加ctx.letterSpacing = "2px"; 在绘图之前,但无济于事。 有没有办法只用一个简单的设置来做到这一点,或者我是否必须制作一个函数来单独绘制每个字符并记住间距?

您无法设置字母间距属性,但您可以通过在字符串中的每个字母之间插入各种空格之一来在画布中实现更宽的字母间距。 例如

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

这将在每个字母之间插入一个头发空间

使用 8201(而不是 8202)将插入稍宽的薄空间

有关更多空白选项,请参阅此 Unicode 空间列表

与手动定位每个字母相比,此方法将帮助您更轻松地保留字体的字距,但是您将无法以这种方式收紧字母间距。

我不确定它是否应该工作(根据规范),但在某些浏览器(Chrome)中,您可以在<canvas>元素本身上设置letter-spacing CSS 属性,它将应用于在上下文中绘制的所有文本. (适用于 Chrome v56,不适用于 Firefox v51 或 IE v11。)

请注意,在 Chrome v56 中,每次更改letter-spacing样式后,您都必须重新获取画布 2d 上下文(并重新设置您关心的任何值); 间距似乎已融入您获得的 2d 上下文中。

示例: 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>


原始的跨浏览器答案:

这不可能; HTML5 Canvas 不具备 HTML 中 CSS 的所有文本转换能力。 我建议您应该为每种用途组合适当的技术。 使用与 Canvas 甚至 SVG 分层的 HTML,每个都做它最擅长的。

另请注意,鉴于存在字母字距调整对和像素对齐字体提示,“滚动您自己的”(使用自定义偏移量绘制每个字符)将对大多数字体产生不良结果。

您不能将字母间距设置为 Canvas 上下文的属性。 您只能通过手动间距来达到效果,抱歉。 (如,手动绘制每个字母将 x 增加一些像素量)

作为记录,您可以使用ctx.font设置一些文本属性,但字母间距不是其中之一。 可以设置的是:“字体样式字体变体字体粗细字体大小/行高字体系列”

例如,您可以在技术上编写ctx.font = "bold normal normal 12px/normal Verdana" (或其中任何一个的任何省略),它会正确解析。

为了允许“字母字距调整对”等,我写了以下内容。 它应该考虑到这一点,粗略的测试表明确实如此。 如果您对此有任何评论,那么我会向您指出我关于该主题的问题( 在 HTML Canvas 中添加字母间距

基本上它使用 measureText() 来获取整个字符串的宽度,然后删除字符串的第一个字符并测量剩余字符串的宽度,并使用差异来计算正确的定位 - 从而考虑到字距对和类似。 有关更多伪代码,请参阅给定链接。

这是 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 更好 - 关于其他字体的建议尝试被感激地接受。

所以……目前看来还不错。 事实上,它看起来很完美。 仍然希望有人能指出这个过程中的任何缺陷。

与此同时,我将把它放在这里供其他人看看他们是否正在寻找解决方案。

这是一些咖啡脚本,允许您像这样将字距调整到您的上下文

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

支持代码是:

_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

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

不对。 您可以在 css 中为 canvas 元素添加 letter-spacing 属性,它可以完美运行。 不需要复杂的解决方法。 我现在才在我的画布项目中弄清楚了。 即:画布{宽度:480px; 高度:350px; 边距:30px 自动 0; 填充:15px 0 0 0; 背景:粉色; 显示:块; 边框:2px 虚线棕色; 字母间距:0.1em; }

实际上字母间距概念画布不支持。

所以我用javascript来做到这一点。

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

要么

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

我不知道其他人,但我通过增加我正在写的文本的 y 值来调整行距。 我实际上是用空格分割一个字符串并将每个单词踢下一个循环内的一行。 我使用的数字基于默认字体。 如果您使用不同的字体,这些数字可能需要调整。

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

这是基于 James Carlyle-Clarke 之前回答的另一种方法。 它还允许您将文本左、中、右对齐。

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

这可能是一个老问题,但它仍然相关。 我采用了 Patrick Matte 对 James Carlyle-Clarke 回应的扩展,并得到了一些我认为效果很好的东西,因为它仍然是普通的 Javascript。 这个想法是测量两个连续字符之间的空间并“添加”到它。 是的,负数有效。

这是我得到的(大量评论的版本):

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
}

这是一个演示:

 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>

支持画布中的字母间距,我使用了这个

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

我用:

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