Home > Software engineering >  Is there a bug in HTML Canvas' fillText function?
Is there a bug in HTML Canvas' fillText function?

Time:06-10

I just came across some unexpected behaviour on the HTML Canvas element; I tried to strip the problem down as much as I could. In short, it appears the ctx.fillText fails to render the text in specific regions of the canvas.

This is the smallest script I could write to consistently reproduce the bug (I tested it on different machines, OSs and browsers). It creates a black canvas (1.25×1.25 in drawing space units, 1000×1000 in pixels, the drawing space origin is in the middle) and draws red dots as the mouse passes over it, but there are several horizontal stripes in which it fails to do so.

// define boundaries of drawing space
const left = -.625;
const tops = -.625;
const scale = 800;
const width = 1.25;
const height = 1.25;

// create canvas
let canvas = document.createElement("canvas");
canvas.width = scale * width;
canvas.height = scale * height;
canvas.style.backgroundColor = "black";
document.body.appendChild(canvas);

// create context
let ctx = canvas.getContext("2d");
ctx.scale(scale, scale);
ctx.translate(-left, -tops);
ctx.textBaseline = "middle";
ctx.textAlign = "center";
ctx.font = `.03px arial`;
ctx.fillStyle = "rgba(208, 64, 64, 1)";

// coordinates follow mouse
addEventListener("mousemove", event => {
  let mouseX = left   event.layerX / scale;
  let mouseY = tops   event.layerY / scale;
  ctx.fillText(".", mouseX, mouseY);
});

You can paste it in your dev-tools in about:blank and see for yourself. In any case here's a gif too:

The bug in action

As you can see, there are several horizontal stripes left untouched, even though I pass over them with the mouse. Also, the entirety of the bottom 80% of the canvas is unaffected.

A few important notes:

  • This occurs with any text, but I used the dot because it takes up the least amount of space, while bigger symbols easily bridged the gaps in the upper portion and made it more difficult to see.
  • The mousemove event is not the culprit, mouseX and mouseY update properly and smoothly.
  • It is not due to my mouse being dragged too quickly, it leaves the gaps no matter how slow I move.
  • It is not due to how small the scale is, as the x dimension has the same scaling as the y dimension, but the former doesn't present this issue.
  • This does not occur with the ctx.strokeText method, which works fine.
  • It also does not occur with ctx.fillPath.

Am I doing something wrong? Or is this actually a bug?

CodePudding user response:

The problem is the extremely small font size you're trying to render. You shouldn't use values like .03px - it makes sense for renderers to not be able to render something like that correctly, considering the typical smallest paintable size on a display is 1 pixel (a little smaller than that on high DPI displays, but probably not much less than .25px). It may work for painting simple lines, but rendering is more complicated than that (e.g. hinting).

Try the following values:

  • scale = 80
  • width = 12.5
  • height = 12.5
  • ctx.font = `.3px arial`;

Alternatively, try to paint a dot or a square rather than a "." string.

As a side note, I was able to reproduce the problem on Chrome, but on Firefox it actually renders fine.

  • Related