skia-canvas
skia-canvas copied to clipboard
Font Measurement is inaccurate
Hello! I'm trying to use skia-canvas to do font measurement. I'd like to replace puppeteer with it in my SVG renderer for shiki:
Running with node:
const { Canvas } = require('skia-canvas')
const canvas = new Canvas(600, 600)
const ctx = canvas.getContext('2d')
ctx.font = `16px 'Courier'`
console.log(ctx.measureText('M'))
What I get:
TextMetrics {
width: 9.600000381469727,
actualBoundingBoxLeft: 0,
actualBoundingBoxRight: 9.600000381469727,
actualBoundingBoxAscent: 12.0625,
actualBoundingBoxDescent: 3.9375,
fontBoundingBoxAscent: 14.784375190734863,
fontBoundingBoxDescent: 4.415625095367432,
emHeightAscent: 14.784375190734863,
emHeightDescent: 4.415625095367432,
hangingBaseline: 11.4765625,
alphabeticBaseline: 0,
ideographicBaseline: -4.415625095367432,
lines: [
{
x: 0,
y: -12.0625,
width: 9.600000381469727,
height: 16,
baseline: 0,
startIndex: 0,
endIndex: 0
}
]
}
Running this script in Chrome:
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
ctx.font = `16px 'Courier'`
console.log(ctx.measureText('M'))
What I get:
TextMetrics {width: 9.6015625, actualBoundingBoxLeft: -0.09375, actualBoundingBoxRight: 9.5078125, actualBoundingBoxAscent: 9.265625, actualBoundingBoxDescent: 0}
actualBoundingBoxAscent: 9.265625
actualBoundingBoxDescent: 0
actualBoundingBoxLeft: -0.09375
actualBoundingBoxRight: 9.5078125
width: 9.6015625
__proto__: TextMetrics
I'm wondering why would skia-canvas generate a different actualBoundingBoxDescent than Chrome?
M should have 0 descent as far as I can tell.
Unfortunately there's basically zero agreement between different browsers on how the same glyphs should be measured. For instance, Safari's output to your sample script is:
actualBoundingBoxAscent: 10
actualBoundingBoxDescent: 1
actualBoundingBoxLeft: 0
actualBoundingBoxRight: 9.6015625
alphabeticBaseline: -0
emHeightAscent: 14
emHeightDescent: 4
fontBoundingBoxAscent: 14
fontBoundingBoxDescent: 4
hangingBaseline: 14
ideographicBaseline: -4
width: 9.6015625
Which is not to say that ‘correct’ and ‘incorrect’ have no meaning; just that it's kind of the Wild West out there.
Here's the how the metrics are currently calculated. If anyone has a better handle on how to make some of these judgment calls in terms of accuracy, conformance to the CSS spec, etc. I'd love to look over a pull request...
As with all questions involving font metrics, the answer is surely somewhere in here: https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align
@samizdatco I'm getting this in Firefox:
actualBoundingBoxAscent: 9.265625
actualBoundingBoxDescent: 0
actualBoundingBoxLeft: 0.90625
actualBoundingBoxRight: 10.5078125
width: 9.600000381469727
Unfortuantely I'm not familiar with font measurement so I can't help much there.
+1 to this issue, the metrics are incorrect and trying to calculate an accurate height for text appears to be impossible using skia-canvas. Are there any possible workarounds?
@PikaDude Perhaps you can use RenderingContext2D.outlineText('Your text here').bounds.top, it is more accurate and work for me.
@samizdatco Do you think use skia_safe::font::Font::get_bounds to calculate actualBoundingBoxAscent would be a better idea? If so I would try spare some time work for this.
@samizdatco Do you think use
skia_safe::font::Font::get_boundsto calculateactualBoundingBoxAscentwould be a better idea? If so I would try spare some time work for this.
That's what actually chromium did.