Bug 247987 - Regression: "font-optical-sizing: auto" has no effect in Safari 16 on macOS Ventura & iOS 16
Summary: Regression: "font-optical-sizing: auto" has no effect in Safari 16 on macOS V...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: CSS (show other bugs)
Version: Safari 16
Hardware: All All
: P2 Major
Assignee: Myles C. Maxfield
URL:
Keywords: InRadar
Depends on: 250363 250364 250366 250370 250372 252033 252034 252035 252140 252149 252150 252221 252231 252232 252235 252273
Blocks:
  Show dependency treegraph
 
Reported: 2022-11-16 11:33 PST by Stephen Nixon
Modified: 2023-04-12 04:14 PDT (History)
6 users (show)

See Also:


Attachments
Comparison of Safari 15 vs 16, with font optical size set automatically vs not set (449.99 KB, image/png)
2022-11-16 11:33 PST, Stephen Nixon
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Stephen Nixon 2022-11-16 11:33:40 PST
Created attachment 463558 [details]
Comparison of Safari 15 vs 16, with font optical size set automatically vs not set

Browsers should automatically set optical size in variable fonts that support it, unless there is CSS to override that behavior. 

This relates to the property "font-optical-sizing," which should be initially set to "auto" per the CSS spec: https://w3c.github.io/csswg-drafts/css-fonts/#property-index

However, I’ve noticed that in the latest versions of macOS and iOS, this is no longer the case in Safari. But, it worked on macOS Monterey, and it is still set automatically in current versions of Chrome and Firefox. 

Further, even if I do specifically set "font-optical-sizing: auto" in CSS, it has no effect. However, I can still control the font’s opsz through the font-variation-settings property.

Example site: https://kyrios.arrowtype.com 

(The small text appears extremely condensed when opsz is not set, because this font has an opsz "display" style that is more condensed than its "text" style.)

Bug found to exist in:
- Safari on iOS 16.1.1 (iPhone 12 Pro)
- Safari 16.1 on macOS Ventura 13.0 on Intel Mac (MacBook Pro 16-inch, 2019)
- Safari Technology Preview Release 157 (Safari 16.4, WebKit 18615.1.11.7), same MacBook as above

Correct behavior verified on older versions of Safari:
- Safari 15.2 on macOS Monterey 12.1 (Intel MacBook Pro 15-inch, 2018)
- Safari on iPadOS 15.5
Comment 1 Radar WebKit Bug Importer 2022-11-16 12:28:06 PST
<rdar://problem/102432138>
Comment 2 Myles C. Maxfield 2022-11-16 15:25:37 PST
Thanks for the bug report!

I think I know what's going wrong, and I have a plan to fix it, but it's a pretty involved fix with a bunch of refactoring. I'll use this as an umbrella bug, and make sub-bugs inside it; this is going to require a few different patches to fix.
Comment 3 Myles C. Maxfield 2022-11-16 16:01:50 PST
Also, one thing that would really help is test font that's licensed with either GPLv2, BSD, or OFL, so that I can check in this test font to WebKit's repo, for a regression test.
Comment 4 Myles C. Maxfield 2022-11-16 16:04:20 PST
Also: A workaround for this bug is to modify the font to remove the named instances with a non-default 'opsz'.
Comment 5 Stephen Nixon 2022-11-17 09:52:13 PST
Thanks for taking a look, Myles, and thanks also for suggesting a workaround!

I was going to suggest Roboto Flex, but I see that it doesn’t have named instances for opsz locations, and indeed, a quick test shows that it doesn’t suffer from this issue. I believe that Google Fonts avoids having named instances along the opsz axis, presumably because they want users to only think about selecting a weight, and not even having to realize that the opsz is being automatically set by the system.

If Apple needs someone to create a simple test font with named opsz instances and license it for open-source use, my services are available. :) The Kyrios license also allows unlimited web use, so feel free to pick up a license on Future Fonts and use that as a test case, if it seems useful here. 

(I’m not trying to sell anything per se, I just have limited time in the day and can’t volunteer too much of it to Apple, but I’m happy to help more if that would be helpful. Making a test font should be pretty quick and easy, e.g. you could make a single character that acts very differently along the opsz axis, then just make sure it has named instances on either end.)

Cheers!
Comment 6 Stephen Nixon 2022-11-25 17:23:32 PST
In case anyone else comes across this issue and is having a similar problem, here’s a quick script to edit the fvar table of a variable font, using Python and FontTools:

https://gist.github.com/arrowtype/04a77f85eee6cf85f72267ee18d21428

This workaround does indeed seem to work well for the Kyrios website. Thanks again, Myles!
Comment 7 Stephen Nixon 2022-11-25 17:41:54 PST
One significant additional component to this issue:

If the workaround is applied to a font with both opsz _and_ wght axes, then the default wght plus max (non-default) opsz unintentionally yields the min wght.

As an example, my project Name Sans has a default of "Text Bold" (wght=700, opsz=12). However, if I limit its fvar instances to only default opsz instances, then style it in Safari to "Display Bold" (wght=700, opsz=96), what renders is "Display Hairline" (wght=1, opsz=96).

Here’s that example: https://deploy-preview-11--namesans.netlify.app

Again, this seems to be working well in Firefox & Chrome, but not Safari 16.1.


(Or perhaps this is a separate but related issue? Please let me know if you’d want to treat this separately, Myles.)

---

Myles, if you can’t move forward without open-licensed test fonts, and there is no capacity to make this on your end, and it would really be a difficult tasks to budget anything towards test fonts... I realize it’s not your fault, it’s a corporate/bureaucracy issue. I’d rather this issue be fixed for font users than to fight corporate issues too much.

I can make time this coming week to put together a quick test font and give it an open license. Please let me know if this would indeed unblock the issue work, and if there are any specific requirements you would have in such a font.

Cheers!
Comment 9 Stephen Nixon 2022-11-28 13:30:11 PST
Thanks so much, Jesse!

I hadn’t expected Google Fonts fonts to show this issue, because the GF guidelines specifically don’t allow multiple optical sizes in variable font instances... e.g. all of these fonts already follow Myles’s suggested workaround.

However, looking at those Google Fonts specimen pages (with the style waterfalls), it does do a very good job of demonstrating the second aspect of this bug – namely that the default style of these is showing up in the min wght, rather than wght=400 as all three of those families intend.

It’s less obvious to me whether they suffer from the general bug – opsz not being set automatically by Safari – without making specific testing pages.
Comment 10 Richard Rutter 2023-01-08 09:58:08 PST
Myles, you've probably got this already, but if not: Literata has optical sizing and is available with an OFL. https://www.type-together.com/demos/literata/
Comment 11 Myles C. Maxfield 2023-01-08 13:07:53 PST
Oh, thanks for all the links!

I totally forgot about this bug until today; I'll see if I can fix it this week.

Some background:

This is caused by a Core Text behavior change. Previously, when you create a font, by default, the font will have optical sizing enabled. Now, it will only have optical sizing enabled if there are no named instances that set opsz to a non-default value. We view this Core Text change as a behavior improvement and a progression.

For WebKit, though, we want font-optical-sizing:auto to enable optical sizing, regardless of the details of the named instances.

When creating a font, right now WebKit:
1. Creates a CTFontDescriptor
2. Creates a font
3. Inspects the font's attributes (e.g. see which features it supports, which variation axes it supports)
4. If the font is already set up appropriately, we're done, just use it.
5. Otherwise, create a delta CTFontDescriptor, and
6. Create a copy of the font with the delta CTFontDescriptor

In step 4, WebKit says "If font-optical-sizing:auto is specified, we can use the existing font, because we expect Core Text to apply optical sizing" but now that Core Text won't always apply optical sizing, that logic in WebKit is erroneous.

We probably shouldn't investigate the details of the font's named instances to make a fine-grained filter for which fonts should execute steps 5 and 6. Instead, we should be explicit and say "if CSS tells us to apply optical sizing, let's explicitly make sure optical sizing is enabled in Core Text"

However, we *probably* can't just say "yolo every font has to execute steps 5 and 6" because of performance. (Though this is worth testing.)

So, I think the fix here is to just delete steps 5 and 6 wholesale, for all fonts, and make the original font we create in step 2 have all the necessary attributes. That might actually cause a performance *progression*!

There are 2 reasons steps 5 and 6 existed in the first place:
1. To work around Core Text bugs from years and years ago, which have since been fixed in all OSes that WebKit ships on
2. So we know whether the variable font is a GX font or not, which affects the scaling of the font-weight and other property values: OT 1.9 variable fonts use 100-900 for weight, whereas GX fonts use roughly 0.0 to 2.0 for weight (see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6fvar.html)

(1) has an obvious solution, but I'm a little less sure about (2). I should check whether or not the other browsers do any scaling for GX fonts, or whether there's another solution here.

But, either way, deleting steps 5 and 6 is a not-insubstantial refactoring effort. It will probably be a few day's full-time work.
Comment 12 Myles C. Maxfield 2023-01-08 13:10:49 PST
One possible solution: Assume the font is OT1.9, and opportunistically create it that way, then check our assumption afterward and see if we guessed right (and if we didn't, fix it up). The guess will be correct almost all the time.
Comment 13 Stephen Nixon 2023-01-08 14:44:07 PST
Thanks so much for taking time to think this through and for sharing your thoughts, Myles! Excited you’ll be trying to work on this soon.

> Instead, we should be explicit and say "if CSS tells us to apply optical sizing, let's explicitly make sure optical sizing is enabled in Core Text"

As I interpret the CSS spec, optical-size:auto is the specified default, correct? So, it seems like a deviation to require a user to explicitly specify that before you apply that logic... but I could be misinterpreting your meaning.

One other nitpick: OT 1.9 variable fonts use *1-1000* for weight, not just 100–900. This probably doesn’t need to be said, but I’ve seen various bugs where things worked strangely in that newer area of the weight axis.
Comment 14 Myles C. Maxfield 2023-01-09 09:46:20 PST
(In reply to Stephen Nixon from comment #13)
> Thanks so much for taking time to think this through and for sharing your
> thoughts, Myles! Excited you’ll be trying to work on this soon.
> 
> > Instead, we should be explicit and say "if CSS tells us to apply optical sizing, let's explicitly make sure optical sizing is enabled in Core Text"
> 
> As I interpret the CSS spec, optical-size:auto is the specified default,
> correct? So, it seems like a deviation to require a user to explicitly
> specify that before you apply that logic... but I could be misinterpreting
> your meaning.

Oh, when I say "if CSS tells us to apply optical sizing" that could be caused by the author doing nothing, so the initial value of font-optical-sizing gets applied by default. This will be the case on most content, which is why performance is a concern here.

> 
> One other nitpick: OT 1.9 variable fonts use *1-1000* for weight, not just
> 100–900. This probably doesn’t need to be said, but I’ve seen various bugs
> where things worked strangely in that newer area of the weight axis.

Yep! Thanks for the correction.
Comment 15 Myles C. Maxfield 2023-01-10 01:44:47 PST
Sites that need to be fixed up:

- SystemFontDatabaseCoreText
  - createSystemUIFont()
  - createFontByApplyingWeightWidthItalicsAndFallbackBehavior()
- createFontForInstalledFonts()
- fontWithFamilySpecialCase()
- platformFontLookupWithFamily() (which could benefit from an enum)
- lookupFallbackFont()
- FontFamilySpecificationCoreText::fontRanges()
- FontCustomPlatformData::fontPlatformData()

Hiccups:
- CTFontCreateUIFontForLanguage() returns a font, not a font descriptor
- CTFontCreateForCharactersWithLanguageAndOption() returns a font, not a font descriptor

In order to avoid creating a bunch of derived CTFontDescriptors, I think I'm going to make a new type name, maybe PartialFontDescriptor, and have it be typedefed to CFMutableDictionary or something. That way, a bunch of functions can collaborate on building it, then at the end it can be turned into a font descriptor. Maybe that won't work in practice, but I'd like to try to do it.
Comment 16 Stephen Nixon 2023-01-10 06:32:53 PST
(In reply to Myles C. Maxfield from comment #14)

> Oh, when I say "if CSS tells us to apply optical sizing" that could be
> caused by the author doing nothing, so the initial value of
> font-optical-sizing gets applied by default. This will be the case on most
> content, which is why performance is a concern here.

Ah, makes sense! Thanks for the clarification.
Comment 17 Myles C. Maxfield 2023-02-14 21:25:46 PST
Pull request: https://github.com/WebKit/WebKit/pull/10126
Comment 18 EWS 2023-02-17 08:04:05 PST
Committed 260447@main (78f657ca44f6): <https://commits.webkit.org/260447@main>

Reviewed commits have been landed. Closing PR #10126 and removing active labels.