Text metrics is complicated especially when fonts are not mono spaced. Some characters are padded when measured on their own. 1
happens to be one of them when using "arial"
Calculating the padding.
Assumptions
Calculation
For the character "1" we can workout the approximate padding by measuring 2 cases "111" and "11111". We can then create an expression to define the character width w
, and the padding p
.
Thus for the strings...
We now have two equations with 2 unknowns as we can get w3
and w5
using measureText
.
Solving for p (padding) p = (15 * (w3 / 3) - (3 * w5)) / 2
Then w (width of character) is w = (w3 - p) / 3
Example
The example calculates the width of 1 between 1 and 1 to ~100th of a pixel width.
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>
Other Characters
Not all characters have the padding the example below calculates the padding for numbers and lowercase letters.
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>