Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chrome canvas 2d context measureText giving me weird results

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)
like image 967
orchlon Batsaikhan Avatar asked Aug 08 '16 07:08

orchlon Batsaikhan


1 Answers

Don't use "pt" for font sizing on canvas.

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.


Why does the Width change?

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.


Summary

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)

like image 159
Blindman67 Avatar answered Sep 22 '22 06:09

Blindman67