Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

discrepancy in scale factor conversion between freetype and truetype packages #85

Open
dmitshur opened this issue Oct 24, 2021 · 4 comments · May be fixed by #86
Open

discrepancy in scale factor conversion between freetype and truetype packages #85

dmitshur opened this issue Oct 24, 2021 · 4 comments · May be fixed by #86
Assignees

Comments

@dmitshur
Copy link

I noticed there's sometimes a tiny discrepancy in font metrics as computed by freetype.Context and truetype.NewFace.

As a reproducible example, using the Go Mono font of size 86.4 exactly, at 72.0 DPI, the advance width for the glyph 'H' differs by 1/64 (the smallest value a fixed.Int26_6 can represent).

See the complete program on the Go Playground. Its output:

advance width of 'H' via truetype: 51:55
advance width of 'H' via freetype: 51:54

I've tracked it down and found the root cause. When computing the scale factor, the float64 → fixed 26.6 conversion is done differently between those two packages. In truetype, it rounds to the nearest 26.6 fixed point value:

scale:      fixed.Int26_6(0.5 + (opts.size() * opts.dpi() * 64 / 72)),

But in freetype, it uses the floor:

c.scale = fixed.Int26_6(c.fontSize * c.dpi * (64.0 / 72.0))

Between those two, it seems taking the nearest value is the better behavior, so I'll send a PR that adjusts freetype to fix this discrepancy. CC @nigeltao.

@dmitshur dmitshur self-assigned this Oct 24, 2021
dmitshur added a commit that referenced this issue Oct 24, 2021
With this change, the computation of the scale factor becomes
identical across the freetype and truetype packages, removing
deviations in the font metrics that are derived from scale.

The rounding computation is newer; it was introduced when the
truetype.Face type was added in commit 6deea24.

Fixes #85.
@dmitshur dmitshur linked a pull request Oct 24, 2021 that will close this issue
@nigeltao
Copy link
Contributor

it seems taking the nearest value is the better behavior

It's been a while since I remembered how this all works. Do you know what the C FreeType library does re round-down versus round-to-nearest? Does C FreeType even have a similar concept?? C FreeType and Go FreeType don't necessarily have identical APIs (even after accounting for C vs Go idioms)...

If you don't know, that's fine, I can dig into it. It's just that, if you already know, it'd save me some work.

FWIW, golang.org/x/image/font/opentype also rounds to nearest. https://github.com/golang/image/blob/a66eb6448b8d7557efb0c974c8d4d72085371c58/font/opentype/opentype.go#L111 says

scale:   fixed.Int26_6(0.5 + (opts.Size * opts.DPI * 64 / 72)),

so changing Go's freetype.Context (a 2010-ish era concept?? predating fixed.Int26_6) to round-to-nearest is probably the most consistent thing to do...

@dmitshur
Copy link
Author

Thanks for taking a look.

I haven’t looked at the C FreeType code, so I don’t know which it uses offhand. I wouldn’t mind trying to look later on if it can help.

@dmitshur
Copy link
Author

dmitshur commented Oct 31, 2021

I took a look.

Specifically, I was looking over the C FreeType API and trying to see if there's something that accepts a font size in floating point and converts to fixed point. From what I was able to find, the C FreeType API doesn't have that: it largely accepts integers for DPI and fixed point for points, or integers for pixels. This means I wasn't able to find direct precedent for the Go API to follow in this particular situation.

I did find a few places that in spirit seem to support the general idea of rounding to a nearest value rather than truncating, including:

There are also mentions of a couple exceptions due to historical reasons, such ascender being rounded up to an integer value, and descender rounded down to an integer value. But those were the only two exceptions in terms of unusual rounding that I spotted.

@nigeltao
Copy link
Contributor

Copy-pasting a pull-request comment #86 (comment) here:


I just noticed there's a Context.PointToFixed method that does the same conversion:

return fixed.Int26_6(x * float64(c.dpi) * (64.0 / 72.0))

We should not change one without also changing the other, as that would fix #85 but introduce another inconsistency within this package. Hmm.

@dmitshur
Copy link
Author

To update this issue with the latest PR status: the comment above is resolved in #86 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants