samizdatco/skia-canvas

TextMetrics & rendered text changed from v1.0.2 to v^2.0.0

Closed this issue · 4 comments

About

Texts aren't rendered same as in v1.0.2, TextMetrics are different for all textBaseLine but it is most noticeable for top && bottom, when middle || alphabetic are used changes are very small.

baseline: alphabetic image image
baseline: bottom image image

old: v1.0.2, new: v2.0.1

Notice: how text is shifted upwards when baseline: bottom

Reproducible example

Toggle me!
function drawTestText() {
  const width = 150
  const height = 100
  const canvas = new Canvas(width, height)
  const context = canvas.getContext('2d')

  context.fillStyle = 'white'
  context.fillRect(0, 0, width, height)

  context.strokeStyle = 'blue'
  context.beginPath()
  for (let i = 0; i < height; i += 10) {
    context.moveTo(0, i)
    context.lineTo(width, i)
  }
  context.stroke()
  context.closePath()

  context.font = '50px Lato'
  context.fillStyle = 'black'
  context.textAlign = 'left'
  context.textBaseline = 'bottom'
  const text = 'TEST'
  const measurements = context.measureText(text)
  console.log(measurements)
  context.fillText(text, 25, 75)

  context.beginPath()
  context.strokeStyle = 'red'
  context.strokeRect(
    25 + measurements.lines[0].x,
    75 + measurements.lines[0].y,
    measurements.lines[0].width,
    measurements.lines[0].height
  )
}

Expected behavior

I am unsure about this, to be honest. This is causing me problems because I had fine-tuned my render functions to display text in specific positions based on TextMetrics. Primarily, I would like to know if this is/was an expected change between versions and, if possible, what caused it.

Nothing changed between v1 and v2 within skia-canvas itself, but it upgrades to a much newer revision of rust-skia, so things have almost certainly changed under the hood. However: one alternative explanation that comes to mind is that your system is now using gpu-based rendering as of v2 (in which case setting canvas.gpu = false would yield different results).

That said, after looking over the way different baselines are computed (for #209) I think I've found some modifications that would better match the way browsers calculate offsets and determine the default leading when a lineHeight value isn't included in the ctx.font assignment.

More to come…

Nothing changed between v1 and v2 within skia-canvas itself, but it upgrades to a much newer revision of rust-skia, so things have almost certainly changed under the hood. However: one alternative explanation that comes to mind is that your system is now using gpu-based rendering as of v2 (in which case setting canvas.gpu = false would yield different results).

That said, after looking over the way different baselines are computed (for #209) I think I've found some modifications that would better match the way browsers calculate offsets and determine the default leading when a lineHeight value isn't included in the ctx.font assignment.

More to come…

It can't be a GPU thing as this happen also in CI and on my server, where there is none and also i have it specifically set to canvas.gpu = false

First of all, you're definitely correct that there's a vertical shift between version 1.0 and 2.0. What's odd though is that it's not in a fixed direction. Take a look at this animation alternating between versions and note how in some cases the text shifts up and in others down:

ezgif-1-1dcb97ffe6

My hunch is that this is the result of Skia trying to align the text with the pixel grid rather than using whatever fractional y-position the metrics alone would position the text at. Another project using SkParagraph for typesetting has mentioned that it rounds the lineHeight to the nearest pixel (which could account for the unpredictable vertical shifts seen above).

The other place where it might be shifting the position is in its application of font hinting—something that it's currently not possible to disable but seems like it's also to blame for the unexpectedly dark font rendering mentioned in #202.

Changing baseline calculations to match browser behavior

Separate from this pixel grid-related shifting, there's also the issue of baselines being calculated differently in Skia Canvas than in browsers. The changes in d7a13d8 should result in a much closer match (as seen in the final gif below), but will necessarily mean that positioning will shift even further relative to the 1.0 behavior. Sorry for the lack of stability, but shooting for 'correctness' seems worthwhile in this case.

Version 1.0 vs browser

ezgif-6-ea4f483761

Version 2.1 (prerelease) vs browser

ezgif-6-267b93d15d

Version 2.0.2 disables font hinting by default, resulting in lighter text that's closer to what browsers produce. It can be re-enabled by setting ctx.fontHinting to true to get smoother but bolder text rendering.

Text baselines now consult the font metrics to calculate the line height (rather than defaulting to 1.2em) when ctx.font only includes a size and not a /-separated line height. This also results in layouts much closer to browsers'. The top and middle baseline positions now use a formula taken from the chromium source, so expect them to shift relative to 2.0.1.