Here's a compact version of my problem
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
ctx.font = '11pt Calibri'
ctx.fillStyle = '#000000'
let temp = ctx.font
console.log(ctx.font)
console.log(ctx.measureText('M').width)
ctx.font = 'bold ' + ctx.font
console.log(ctx.font)
console.log(ctx.measureText('M').width)
ctx.font = 'italic ' + ctx.font
console.log(ctx.font)
console.log(ctx.measureText('M').width)
ctx.font = temp
console.log(ctx.font)
console.log(ctx.measureText('M').width)
Running this code in chrome produces incorrect numbers, at least at the end. First I'm setting the font to '11pt Calibri', but chrome immediately changes it to '15px Calibri' for some reason, and because of that it's producing text that's slightly bigger than correct. I read that the canvas runs at 96dpi so the correct px should be 14.6.
After that, I'm measuring the width of a text M, which comes out at 12.53401184 for me, this number is important.
After that, I've modified the font to add bold and italic, and then I roll it back to what the font originally was. Now when I measure it, it gives me 12.824707, which is a massive 0.3px off. I'm drawing text on a canvas with anywhere from 600px to 800px width, and I need it to wrap correctly, so I need it to be precise to 1px over the line, so individual letters need to have at least 0.02px accuracy, which worked decently until I started using bolds and italics.
None of the above problems exist on firefox, and disabling canvas hardware acceleration on chrome doesn't seem to have any effect. I'm using chrome 52.0 which is the current latest version.
Edit: I figured out you don't even need to do any of that to get the incorrect number, simply doing this is enough.
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
ctx.font = '11pt Calibri'
ctx.fillStyle = '#000000'
console.log(ctx.font)
console.log(ctx.measureText('M').width)
let temp = ctx.font
ctx.font = temp
console.log(ctx.font)
console.log(ctx.measureText('M').width)
CSS Absolute and Magic Units
Using pt
for font sizing is not recommended as it has no real meaning for media that represent visual information in terms of pixels (discrete indivisible image units) and is displayed on screens which have no fixed pixel density.
pt
is an absolute measuring unit, the same as cm
while px
is a "magic unit" and only ever has an absolute meaning when the media type is print.
OP: "I read that the canvas runs at 96dpi so the correct px should be 14.6."
This is not correct the canvas does not have a absolute measuring unit. A pixel as a CSS unit only has an absolute dimension when the media type is print in which case 1px = 1/96 of an inch. The canvas is not considered as a printed media.
The apparent problem
ctx.font = '11pt Calibri'
console.log(ctx.font); // 15px Calibri
console.log(ctx.measureText('M').width); // 12.534011840820312
ctx.font = ctx.font
console.log(ctx.font); // 15px Calibri
console.log(ctx.measureText('M').width); // 12.82470703125
Though the ctx.font
values are the same the measured font widths are different
The simple solution
ctx.font = ctx.font = '11pt Calibri';
Will avoid the measured size discrepancies, but i am sure that nobody would consider this as anything but an ugly work around to an "apparent" browser specific bug.
The solution
Don't use pt
units when setting canvas fonts.
What is going on.
The issue is a misunderstanding of what the ctx.font
property actually is. It does not represent the current font's actual internal representation but rather an abstract human readable form.
W3C 2D Canvas: "On getting, the font attribute must return the serialized form of the current font of the context."
The process of serialization will lose precision. Serialising CSS values.The W3C standard specifies that the font-size
be in px
units which in this case further amplifies the apparent "bug"
The font
properties set function takes the CSS font string, parses it. If valid then sets the canvas internal font and writes the serialized CSS font value to context.font
The two do not have to match and the standard does not specify that they should.
The behaviour as described in the question is not a "bug". Inconsistency between browsers though (as always) is a concern. If we are to follow the standards, one could consider that browsers not showing the measurement inconsistency have interpreted the standard incorrectly and filled a ambiguity with their own interpretation (though this is speculative on my part).
The simple solution to the problem is to follow the standard's guidelines and not use pt
when setting font-size
values for anything but printed media.
As with all computer media "dpi" only ever has meaning when printing and is not defined until then. Nor do pixels necessarily equate to dots when printing. Always use resolution when referring to pixels rather than dp1
(my pet hate)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With