canvas textmetrics 为什么每一项的宽度总和不等于总数
Why is the sum width of each item not equal to the total in canvas textmetrics
字符1
的宽度是8.8984375.
所以我认为10个字符的宽度是88.984375,但实际上是78.296875。
let canvas = document.querySelector('#canvas');
let ctx = canvas.getContext('2d');
ctx.font = '16px/16px arial';
let per = ctx.measureText('1').width;
let num = 10;
let total = ctx.measureText('1111111111').width;
console.log(per, total, per * 10);
<canvas id="canvas"></canvas>
文本指标很复杂,尤其是当字体不是等宽时。一些字符在单独测量时会被填充。使用"arial"
时1
恰好是其中之一
正在计算填充。
假设
与其相邻的字符(两边)将占据相同的宽度
两端相同字符的字符串将具有相同的填充。
计算
对于字符“1”,我们可以通过测量“111”和“11111”这两种情况来计算出近似的填充。然后我们可以创建一个表达式来定义字符宽度 w
和填充 p
.
因此对于字符串...
"111" 宽度为 w * 3 + p = w3
,
"11111" 宽度为 w * 5 + p = w5
.
我们现在有两个带有 2 个未知数的方程,因为我们可以使用 measureText
.
得到 w3
和 w5
求解 p(填充)p = (15 * (w3 / 3) - (3 * w5)) / 2
那么w(字符的宽度)就是w = (w3 - p) / 3
例子
该示例计算 1 和 1 之间的宽度 1 到像素宽度的 ~100th。
const width = str => ctx.measureText(str).width;
const test = (str, calc) =>
console.log("'" + str + "' width = " + width(str).toFixed(2) +
"px, calculated = " + calc.toFixed(2) +
"px, dif = " + (calc - width(str)).toFixed(2) + "px");
const ctx = canvas.getContext("2d");
ctx.font = '16px arial';
const c = "1";
// calculate padding and width of 1
const w3 = width(c.padStart(3, c));
const w5 = width(c.padStart(5, c));
const padding = (15 * (w3 / 3) - 3 * w5) / 2;
const w = (w3 - padding) / 3;
// test result
test(c, w + padding);
test(c.padStart(10, c), w * 10 + padding);
test(c.padStart(20, c), w * 20 + padding);
console.log("Width of '"+c+"' is ~" + w.toFixed(2) + "px");
console.log("Padding is ~" + padding.toFixed(2) + "px");
<canvas id="canvas"></canvas>
其他角色
并非所有字符都有填充,下面的示例计算数字和小写字母的填充。
const width = str => ctx.measureText(str).width;
const test = (str, calc) =>
console.log("'" + str + "' width = " + width(str).toFixed(2) +
"px, calculated = " + calc.toFixed(2) +
"px, dif = " + (calc - width(str)).toFixed(2) + "px");
const ctx = canvas.getContext("2d");
ctx.font = '16px arial';
console.log("Font: " + ctx.font);
[..."1234567890abcdefghijklmnopqrstuvwxyz"].forEach(calcWidthFor);
function calcWidthFor(c) {
const w3 = width(c.padStart(3, c));
const padding = (15 * (w3 / 3) - (3 * width(c.padStart(5, c)))) / 2;
const w = (w3 - padding) / 3;
console.log("Width of '"+c+"' is ~ " + w.toFixed(2) + "px Padding is ~ " + padding.toFixed(2) + "px");
}
<canvas id="canvas"></canvas>
字符1
的宽度是8.8984375.
所以我认为10个字符的宽度是88.984375,但实际上是78.296875。
let canvas = document.querySelector('#canvas');
let ctx = canvas.getContext('2d');
ctx.font = '16px/16px arial';
let per = ctx.measureText('1').width;
let num = 10;
let total = ctx.measureText('1111111111').width;
console.log(per, total, per * 10);
<canvas id="canvas"></canvas>
文本指标很复杂,尤其是当字体不是等宽时。一些字符在单独测量时会被填充。使用"arial"
1
恰好是其中之一
正在计算填充。
假设
与其相邻的字符(两边)将占据相同的宽度
两端相同字符的字符串将具有相同的填充。
计算
对于字符“1”,我们可以通过测量“111”和“11111”这两种情况来计算出近似的填充。然后我们可以创建一个表达式来定义字符宽度 w
和填充 p
.
因此对于字符串...
"111" 宽度为
w * 3 + p = w3
,"11111" 宽度为
w * 5 + p = w5
.
我们现在有两个带有 2 个未知数的方程,因为我们可以使用 measureText
.
w3
和 w5
求解 p(填充)p = (15 * (w3 / 3) - (3 * w5)) / 2
那么w(字符的宽度)就是w = (w3 - p) / 3
例子
该示例计算 1 和 1 之间的宽度 1 到像素宽度的 ~100th。
const width = str => ctx.measureText(str).width;
const test = (str, calc) =>
console.log("'" + str + "' width = " + width(str).toFixed(2) +
"px, calculated = " + calc.toFixed(2) +
"px, dif = " + (calc - width(str)).toFixed(2) + "px");
const ctx = canvas.getContext("2d");
ctx.font = '16px arial';
const c = "1";
// calculate padding and width of 1
const w3 = width(c.padStart(3, c));
const w5 = width(c.padStart(5, c));
const padding = (15 * (w3 / 3) - 3 * w5) / 2;
const w = (w3 - padding) / 3;
// test result
test(c, w + padding);
test(c.padStart(10, c), w * 10 + padding);
test(c.padStart(20, c), w * 20 + padding);
console.log("Width of '"+c+"' is ~" + w.toFixed(2) + "px");
console.log("Padding is ~" + padding.toFixed(2) + "px");
<canvas id="canvas"></canvas>
其他角色
并非所有字符都有填充,下面的示例计算数字和小写字母的填充。
const width = str => ctx.measureText(str).width;
const test = (str, calc) =>
console.log("'" + str + "' width = " + width(str).toFixed(2) +
"px, calculated = " + calc.toFixed(2) +
"px, dif = " + (calc - width(str)).toFixed(2) + "px");
const ctx = canvas.getContext("2d");
ctx.font = '16px arial';
console.log("Font: " + ctx.font);
[..."1234567890abcdefghijklmnopqrstuvwxyz"].forEach(calcWidthFor);
function calcWidthFor(c) {
const w3 = width(c.padStart(3, c));
const padding = (15 * (w3 / 3) - (3 * width(c.padStart(5, c)))) / 2;
const w = (w3 - padding) / 3;
console.log("Width of '"+c+"' is ~ " + w.toFixed(2) + "px Padding is ~ " + padding.toFixed(2) + "px");
}
<canvas id="canvas"></canvas>