如何在 html 画布上使文本适合精确的宽度?

tob*_*one 2 html text measurement html5-canvas

如何在 html5 画布上将单行文本字符串调整为精确的宽度?到目前为止,我尝试的是以初始字体大小编写文本,使用 测量文本的宽度measureText(my_text).width,然后根据所需的文本宽度与实际文本宽度之间的比率计算新的字体大小。它给出的结果大致正确,但根据文本的不同,边缘处会有一些空白。

这是一些示例代码:

// Draw "guard rails" with 200px space in between
c.fillStyle = "lightgrey";
c.fillRect(90, 0, 10, 200);
c.fillRect(300, 0, 10, 200);

// Measure how wide the text would be with 100px font
var my_text = "AA";
var initial_font_size = 100;
c.font = initial_font_size + "px Arial";
var initial_text_width = c.measureText(my_text).width;

// Calculate the font size to exactly fit the desired width of 200px
var desired_text_width = 200; 
new_font_size = initial_font_size * desired_text_width / initial_text_width;

// Draw the text with the new font size
c.font = new_font_size + "px Arial";
c.fillStyle = "black";
c.textBaseline = "top";
c.fillText(my_text, 100, 0, 500);
Run Code Online (Sandbox Code Playgroud)

结果对于某些字符串来说是完美的,例如"AA"

在此输入图像描述

但对于其他字符串,例如"BB",边缘处有间隙,您可以看到文本没有到达“护栏”:

在此输入图像描述

我怎样才能使文本始终到达边缘?

小智 6

我在我的一个项目中也遇到了类似的问题。我不仅需要获得文本的准确宽度,而且还意识到,如果我在位置 X 渲染文本,由于Side Bearings ,它有时会流到 X 的左侧。

尽管我尽了最大的努力,但我无法让 DOM 为我提供这些值,因此我不得不求助于 SVG 来准确测量文本。

我最终得到了以下解决方案来精确测量文本,包括侧轴承或 X 偏移,我需要应用这些来使像素出现在正确的位置。

该代码仅在 Chrome 和 Firefox 中进行了测试,但基本上应该适用于所有现代浏览器。它还支持使用网络字体,只需将其加载到页面中,然后可以通过名称引用。

class TextMeasurer {
  constructor() {
    const SVG_NS = "http://www.w3.org/2000/svg";

    this.svg = document.createElementNS(SVG_NS, 'svg');

    this.svg.style.visibility = 'hidden';
    this.svg.setAttribute('xmlns', SVG_NS)
    this.svg.setAttribute('width', 0);
    this.svg.setAttribute('height', 0);

    this.svgtext = document.createElementNS(SVG_NS, 'text');
    this.svg.appendChild(this.svgtext);
    this.svgtext.setAttribute('x', 0);
    this.svgtext.setAttribute('y', 0);

    document.querySelector('body').appendChild(this.svg);
  }

  /**
   * Measure a single line of text, including the bounding box, inner size and lead and trail X
   * @param {string} text Single line of text
   * @param {string} fontFamily Name of font family
   * @param {string} fontSize Font size including units
   */
  measureText(text, fontFamily, fontSize) {
    this.svgtext.setAttribute('font-family', fontFamily);
    this.svgtext.setAttribute('font-size', fontSize);
    this.svgtext.textContent = text;

    let bbox = this.svgtext.getBBox();
    let textLength = this.svgtext.getComputedTextLength();

    // measure the overflow before and after the line caused by font side bearing
    // Rendering should start at X + leadX to have the edge of the text appear at X
    // when rendering left-aligned left-to-right
    let baseX = parseInt(this.svgtext.getAttribute('x'));
    let overflow = bbox.width - textLength;
    let leadX = Math.abs(baseX - bbox.x);
    let trailX = overflow - leadX;

    return {
      bbWidth: bbox.width,
      textLength: textLength,
      leadX: leadX,
      trailX: trailX,
      bbHeight: bbox.height
    };
  }
}

//Usage:
let m = new TextMeasurer();
let textDimensions = m.measureText("Hello, World!", 'serif', '12pt');
document.getElementById('output').textContent = JSON.stringify(textDimensions);
Run Code Online (Sandbox Code Playgroud)
<body>
  <div id="output"></div>
</body>
Run Code Online (Sandbox Code Playgroud)