Chrome canvas 2d context measureText给了我奇怪的结果

时间:2016-08-08 07:20:32

标签: javascript html5 google-chrome canvas

这是我的问题的紧凑版本

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)

在chrome中运行此代码会产生不正确的数字,至少在最后。首先我将字体设置为'11pt Calibri',但由于某种原因,chrome会立即将其更改为'15px Calibri',因为它产生的文字略大于正确。我读到画布以96dpi运行所以正确的px应该是14.6。

之后,我正在测量文本M的宽度,对我来说是12.53401184,这个数字很重要。

之后,我修改了字体以添加粗体和斜体,然后我将其回滚到原来的字体。现在,当我测量它时,它给了我12.824707,这是一个巨大的0.3px关闭。我在画布上绘制文字,宽度从600px到800px,我需要它正确包装,所以我需要它精确到1px超过线,所以单个字母需要至少0.02px精度,工作得体,直到我开始使用粗体和斜体。

firefox上没有上述任何问题,并且在chrome上禁用画布硬件加速似乎没有任何影响。我正在使用chrome 52.0,这是目前的最新版本。

编辑:我发现你甚至不需要做任何一个来获得错误的数字,只需这样就足够了。

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)

2 个答案:

答案 0 :(得分:4)

不要使用" pt"用于在画布上进行字体大小调整。

CSS绝对和魔术单位

使用pt进行字体大小调整是not recommended,因为它对于以像素(离散的不可分割的图像单位)表示视觉信息的媒体没有实际意义,并且显示在没有固定像素的屏幕上密度

pt是一个绝对测量单位,与cm相同,而px是一个"魔法单位"并且只有在打印介质类型时才具有绝对意义。

  

OP:"我读到画布以96dpi运行所以正确的px应该是14.6。"

这不正确画布没有绝对测量单位。作为CSS单元的像素在打印介质类型时仅具有绝对尺寸,在这种情况下1px = 1/96英寸。画布不被视为印刷媒体。

为什么宽度会发生变化?

明显的问题

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

虽然ctx.font值相同,但测量的字体宽度不同

简单的解决方案

ctx.font = ctx.font = '11pt Calibri';

将避免测量的大小差异,但我相信没有人会认为这只是一个丑陋的工作,而且很明显"浏览器特定的错误。

解决方案

设置画布字体时不要使用pt单位。

发生了什么。

问题是对ctx.font属性实际上是什么的误解。它不代表当前字体的实际内部表示,而是一种抽象的人类可读形式。

  

W3C 2D Canvas:"获取时,font属性必须返回上下文当前字体的序列化表单。"

序列化过程将失去精确性。 Serialising CSS values。W3C标准规定font-size属于px个单位,在这种情况下会进一步放大明显的"错误"

font属性集函数采用CSS字体字符串,解析它。如果有效,则设置画布内部字体并将序列化的CSS字体值写入context.font两者不必匹配,标准不指定它们应该。

摘要

问题中描述的行为不是"错误"。虽然(一如既往)浏览器之间的不一致是一个问题。如果我们要遵循标准,可以考虑不显示测量不一致性的浏览器错误地解释了标准,并用他们自己的解释填充了歧义(尽管这对我来说是推测性的)。

问题的简单解决方案是遵循标准指南,在为除打印媒体之外的任何内容设置pt值时不使用font-size

与所有计算机媒体一样" dpi"只有在打印时才有意义,直到那时才定义。打印时像素也不一定等于点。在引用像素而不是dp1(我的宠物讨厌)时总是使用分辨率

答案 1 :(得分:2)

我意识到为什么它被打破了。 Chrome会在内部执行某些操作来补偿pt值,即使字体被劫持到15px也是如此。所以当我从ctx.font获取字体值来修改它时,我得到修改的px值,而不是原始的pt,所以我实际上给它一个原始的15px值,所以当发生这种情况时,chrome不会补偿。解决方法是将原始字体保留在其他位置,例如ctx.originalFont,然后使用它来修改而不是ctx.font

例如,这可行

let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
ctx.font = '11pt Calibri'
ctx.originalFont = '11pt Calibri'
ctx.fillStyle = '#000000'

console.log(ctx.font)
console.log(ctx.measureText('M').width)
let temp = ctx.originalFont
ctx.font = temp
console.log(ctx.font)
console.log(ctx.measureText('M').width)