2

I am using convas context measureText to get the width of my text on the canvas. Below is the code:

    ctx.fillStyle = color;
    ctx.fontWeight = FONT_WEIGHT;
    ctx.font = `bolder italic ${fontsize}px`;

    const textWidth = ctx.measureText(text).width;

the problem is that if the font style is italic, the right of the text will be off boundary. That because measureText doesn't take italic into account. How can I calculate the text width for italic style?

Below are two screenshots for italic font on convas. The first one is the text without italic while the second one is with italic. You can see that the second one has a little off boundaries.

enter image description here

enter image description here

Joey Yi Zhao
  • 37,514
  • 71
  • 268
  • 523
  • Related: https://stackoverflow.com/questions/26296434/html-italic-letters-protrude-from-their-container-and-may-be-cut-by-the-next-co – Kaiido Mar 26 '18 at 02:58

3 Answers3

1

This is a problem inherent to italic rendering of fonts and their container box.
I am not a specialist and won't extend on the subject, which has already been treated in this Q/A about DOM + CSS. Simply note that 2DCanvas measureText also suffers from this same problem.

The only workaround I can think of is going through an svg element, which offers better graphical bounding box representation through its getBBox method.

// Calculate the BBox of a text through an svg Element
function getTextBox(txt, x, y) {
  var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
  var text = document.createElementNS("http://www.w3.org/2000/svg", 'text');
  // so we don't see the svg element in page
  svg.style = 'position: absolute; z-index:-1; width:0; height: 0';
  text.setAttribute('x', x || 0);
  text.setAttribute('y', y || 0);
  text.setAttribute('text-anchor', getAlignment(this.textAlign));
  text.setAttribute('alignment-baseline', this.textBaseline);
  text.style.font = this.font;
  text.textContent = txt;
  svg.appendChild(text);
  document.body.appendChild(svg);
  var box = text.getBBox();
  document.body.removeChild(svg);
  return box;

  // convert CSS text-align notation to correpsonding SVG text-anchor
  function getAlignment(css) {
    return {
      'left': 'start',
      'right': 'end',
      'center': 'middle'
    }[css] || '';

  }
}
// attach it to the proto so it's easier to grab context's current status
Object.defineProperty(CanvasRenderingContext2D.prototype, 'getTextBox', {get: function() {return getTextBox;}});


var x = 20,
  y = 100;
var ctx = canvas.getContext('2d');
ctx.font = 'bolder italic 100px serif';
var txt = 'Right';
ctx.fillText(txt, x, y);
// red => ctx.measureText
var m_width = ctx.measureText(txt).width;
ctx.strokeStyle = 'red';
ctx.strokeRect(x, 0, m_width, y);
ctx.strokeStyle = 'green';
// green => svg.getBBox
var box = ctx.getTextBox(txt, x, y);
ctx.strokeRect(box.x, box.y, box.width, box.height);
<canvas id="canvas"></canvas>
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • This is really cool it works pretty well in a lot of browsers. I've been using this for a long time, but I was really unlucky to find one platform where this doesn't work :(. It's not working on iOS 13+ in a Webview (it did in previous versions of iOS). Any idea why it's not working anymore or what I could do? – BrunoLM Nov 19 '19 at 18:24
0

Set ctx.font first. The result of ctx.measureText is based on the context's current font, the same one you'd see if you were drawing. Do a ctx.fillText to check you have the font set correctly, I find the syntax is easy to get wrong.

Ben West
  • 4,398
  • 1
  • 16
  • 16
0

You will want to load your text into a hidden HTML div and get the calculated width.

jojois74
  • 795
  • 5
  • 10