w3c / csswg-drafts

CSS Working Group Editor Drafts
https://drafts.csswg.org/
Other
4.5k stars 665 forks source link

[css-fonts-4] Behavior for variable fonts with 'ital' axis ambiguous / underspec'ed #3125

Open drott opened 6 years ago

drott commented 6 years ago

@jpamental initially reported this issue discussing font matching and rendering results for a variable font that has an ital axis.

I do not see a clear and interoperable way to make them work described by the spec:

Assume there is a font face declaration as follows:

@font-face { /* Font Face 1 */
font-family: ItalAxisExampleFont;
font-style: oblique 0deg 20deg;
}

for a font that has an 'ital' axis ranging from 0 to 1.

And a style rule as follows:

* {
font-family: ItalAxisExampleFont;
font-style: italic;
}

The font matching algorithm describes that after no match for italic is found, oblique 20deg and above should be tried next, so Font Face 1 is matched.

However, as there is no way of specifying font-style: italic 0 1 the @font-face declaration and the actual font axes mismatch. The @font-face declaration pretends to be able to apply a slnt / oblique axis varying between 0 and 20 to the font. However, the font does not have a slnt axis.

If we really do not want to have a way of specifying a way for italic (not oblique) to be variable, then I am missing a clarification in the spec that would say something about how oblique <angle> <angle> and a resulting matched instance / style like oblique 20deg should be applied as ital 1 in terms of variation parameters to a font that does not have a real ital axis but instead is declared with oblique <angle> <angle>.

CC @behdad @kojii @jpamental

svgeesus commented 6 years ago

The ital axis is a bit weird; as registered, it is more for grouping roman and italic within a family than for a roman to italic variation axis:

The 'ital' axis tag is used within a STAT table of italic fonts to provide a complete characterization of a font in relation to its family within the STAT table. The Italic axis can be used as a variation axis within a variable font, though this is not expected to be common.

The problem is that when it is used as a variation axis, it is not used for matching.

@font-face { /* Font Face 1 has ital = 0.3 */
font-family: Example1;
font-style: auto
}
p { 
  font-style: italic; 
}

Currently, regardless of the value of the ital axis, it will never be matched with auto, only by explicitly declaring font-style: italic in the descriptor:

The auto values for these three descriptors have the following effects: For font selection purposes, the font is selected as if the appropriate initial value is chosen

The initial value for the font-style descriptor is normal, so even with ital=1.0 it will never match unless explicitly described as italic. Instead, the low-level property has to be used:

i, em {
  font-family: Example1;
  font-variation-settings: 'ital' 0.8; 
}
jpamental commented 6 years ago

There are a few nuances to this that are worth noting.

When using a variable font in two files (one containing upright characters, one containing italics), family grouping works perfectly in all browsers:

@font-face { 
  font-family: ExampleFont;
  src: url('VariableFont_Roman.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal;
}
@font-face { 
  font-family: ExampleFont;
  src: url('VariableFont_Italic.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: italic;
}

This results in every browser I've tested rendering the text correctly, calling the right font file and rendering the proper glyphs, so that

font-weight: 725;

gets you the proper bolder weight;

font-style: italic;

gets you proper italics;

font-weight: 725;
font-style: italic;

gets you the proper italics, set to the bolder value on the weight axis

Where this breaks down is when you have a single variable font file that contains both upright and italics. It's worth noting here that from the type designers' (and OT spec creators') perspective, italics ('ital') is boolean (0 or 1, upright or italic), whereas slant ('slnt') is intended to be a range. And it's highly unlikely that you would have both italics and slant in the same design.

If I understand it correctly, this is why it was implemented in Safari in this way (to use the degree range to indicate the presence of an italic axis). According to @litherum the correct syntax would be something like this:

@font-face { 
  font-family: ExampleFont;
  src: url('VariableFont_Combined.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: oblique 0deg 20deg;
}

where the resulting behavior would be that the browser would inspect the font and based on the presence of an italic axis, clamp font-style: normal or font-style: italic to the correct axis value. If there is a 'slnt' axis, it would render at the indicated deg value along that axis.

Currently, if I use that syntax, everything works as @litherum describes. If I omit font-style entirely from the @font-face definition, it will work as expected in all the other browsers, but in Safari it will also synthesize italics on top of the proper use of the italic axis.

Of Note In all cases, you still get the expected output if you use font-variation-settings

Proposed solution Given that it is quite possible possible to have both a upright and italic character sets in a single file, it seems better to allow more specific declaration. I would suggest:

@font-face { 
  font-family: ExampleFont;
  src: url('VariableFont_Combined.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: normal italic;
}

for when you have upright and italics, and

@font-face { 
  font-family: ExampleFont;
  src: url('VariableFont_Combined.woff2') format('woff2-variations');
  font-weight: 100 900;
  font-style: oblique 0deg 12deg;
}

for when a slant axis is present.

References: You can see all of this in action on the guide I wrote on the MDN site (it currently uses the syntax that @litherum suggested in the italics section) https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide

And there's a CodePen of just the italics set up without the font-style declaration so you can see it the other way and edit as you see fit. https://codepen.io/jpamental/pen/EeOyyZ

svgeesus commented 4 years ago

Related:

davelab6 commented 3 years ago

What needs to happen to move this forwards? :)

svgeesus commented 3 years ago

@litherum the specification as written conflates the slnt and ital axes, and assumes ital is a boolean. Do we want to fix that? If so how, and if not, how do we handle a font with an actual variable ital axis?

The MDN site to which @jpamental contributed takes a strong position:

The Italic (ital) axis works differently in that it is either on or off; there is no in-between.

Do we want to enforce that? (it is common, but not universal).

LucianoGrossi commented 3 years ago

I don't understand what motivates designers to create 2 fonts "A", "G" and others, for italic letters. I'm just an end-user, but I CAN'T STAND having to keep checking, before downloading a font, if there is only ONE font, for both the ITALIC and NON-ITALIC options...),=

LucianoGrossi commented 3 years ago

I don't know if it was clear, but what bothers me the most is the existence, in the same font, TWO different letters "A", as well as letters "G" and others...),=

litherum commented 2 years ago

(Sorry for not replying on this until now.)

I can't really put replies inline, so I'll selectively pull quotes from above.

The font matching algorithm describes that after no match for italic is found, oblique 20deg and above should be tried next

This has now shifted down to 11deg (though the exact value doesn't affect the argument at hand).

This results in every browser I've tested rendering the text correctly

Right. The idea is that the @font-face block should simply report the capabilities of the font file. No more, no less. In this example, there's a font that supports weights from 100 to 900 that isn't italic, and there's another font that supports weights from 100 to 900 that is italic. So far so good.

Where this breaks down is when you have a single variable font file that contains both upright and italics.

This is indeed the crux of the issue. When I designed this feature, I was assured that it was meaningless to say "0.5 ital", and that italics are, therefore, a boolean toggle. I did, however, neglect to handle the case where a font can support both values of the boolean toggle.

I was also assured that it would be a Bad Thing™ to make it easy for an author to request both oblique and italics at the same time. I think this is an orthogonal concern, though, as it affects the grammar of the font-style property, and not the font-style descriptor within @font-face.

I think @jpamental's proposal makes sense, as it agrees with the spirit of making the @font-face descriptor simply report the capabilities of the font file.

We do have a choice, though, whether we want a font to be able to advertise that it supports both italics and obliques. Do any such fonts exist?

If the answer is "yes," then this is probably the right grammar:

auto | [ normal || italic || [ oblique [ <angle>{1,2} ]? ] ]

If the answer is "no," then this is probably the right grammar:

auto | [ normal || italic ] | oblique [ <angle>{1,2} ]?
dev-nicolaos commented 8 months ago

I did, however, neglect to handle the case where a font can support both values of the boolean toggle.

Is this still the case or has there been any progress on this issue?

It's very strange not to be able to use font-style as a descriptor for a variable font file that contains both roman/upright and italic glyphs. If all my reading trying to understand this is correct, it sounds like the only way to use the italic glyphs from such a file is to use font-variation-settings property when applying the font? But that runs counter to the guidance to "use high level properties" where relevant, and also forces you to re-declare any other font variation settings since resetting the property will overwrite cascaded values for other axes.

Am I understanding this correctly?

svgeesus commented 7 months ago

@litherum wrote:

We do have a choice, though, whether we want a font to be able to advertise that it supports both italics and obliques. Do any such fonts exist?

It seems we need a good answer to that question, before designing a solution.

nicksherman commented 7 months ago

Several years ago, @djrrb made a version of his Roslindale typeface where the italic axis is separate from the slant axis (which is used for oblique settings). He talks about it here: https://djr.com/notes/roslindale-variable-italic-font-of-the-month/

You can also preview how it works with a live demo here: https://v-fonts.com/fonts/roslindale-variable-italic

djrrb commented 7 months ago

That Roslindale Variable Italic was tricky, because ideally I wanted font-style: italic to implement both the cursive forms of the ital axis and the slant of the slnt axis.

I know it’s weird, but maybe also worth mentioning that I also wanted the ital axis to be able to go past 1, so that I could have cursive forms such as the single-story lowercase g that were even more extreme than what I wanted to appear in the default Italic appearance accessible via font-style: italic.

Because of the technical limitations of ital+slnt, the full version of the Roslindale family currently blends them both into the slnt axis, preserving the gradual onset of the cursive forms as the slant increases. The cursive forms are also available as OpenType stylistic sets so that users can still access “sloped roman” and “upright italic” modes, even without the second axis. However, if the ital+slnt combo was well-supported, I would happily move back to that approach.

It’s also worth mentioning @arrowtype’s Recursive does a similar thing, but uses a custom CRSV axis to implement the italic cursive forms instead of the ital axis.

kontur commented 7 months ago

What if font-style could accept multiple values, mirroring that the font file is capable of covering several "styles"?

@font-face {
  /* The font is expected to provide an 'ital' axis (it may or may not be a visually slanted italic or not, up to the font vendor) */
  font-style: normal italic [ital value];

  /* The font is expected to provide a 'slnt' axis (it is expected to be visually slanted) */
  font-style: normal oblique [slnt angle];

  /* The font has both 'ital' and 'slnt' */
  font-style: normal italic [ital value] oblique [slnt angle];
}

* {
  font-style: normal|italic [ital value] oblique [slnt angle];

  /* equals font-variation-settings: 'ital' 0, 'slnt' 0 */
  font-style: normal; 

  /* equals font-variation-settings: 'ital' 1 [, 'slnt' 0] */
  font-style: italic; 

  /* equals font-variation-settings: 'ital' 0.5 [, 'slnt' 0] */
  font-style: italic 0.5; 

  /* equals font-variation-settings: 'ital' 0, 'slnt' 10 */
  font-style: normal oblique -10; 

  /* equals font-variation-settings: 'ital' 1, 'slnt' 10 */
  font-style: italic oblique -10; 
}

(I don't think a case should be made for "normal 0.5" manipulating the 'ital' axis akin to "italic 0.5", although it could be specced like that — it is more natural to define an "amount of italicness" via the "italic" attribute, imo.)

A variable font covering uprights, cursive forms triggered by "ital" and slanted italics covered by "slnt" could thus be used. It would even allow for gradual triggering of cursive forms using the specced ital range 0 to 1. The obvious inconsistency the two specs make regarding slant/oblique angle direction is already in existence, unfortunately.

AmeliaBR commented 7 months ago

On Mastodon, @svgeesus asked for examples of real variable fonts that provide oblique and italic options independently. However, based on the discussion both here & there, it seems font designers are avoiding that for technical reasons, rather than design ones. So I started imagining a design that would include both.

So here is my rough design spec (samples below), for a handwriting font family with two continuous variable axes: SLNT (slant angle) and CRSV (cursive style, from block print through connected cursive). I want easy access to six named instances: Normal, Oblique, Italic, Upright Italic, Cursive, and Upright Cursive. The slant angle for the named oblique style is different from the slant angle used in the named italic and cursive styles. I may or may not make masters for the higher slant angle in the cursive styles, but otherwise assume that both the change in slant angle and the change in cursive style are continuous between the shown samples. The variable font could also have other axes, like weight and width.

A scanned, hand-lettered table. Columns labelled SLNT=0, 20deg, and 25deg. Rows labelled CRSV=0, 1, and 2. The cells contain the words "The fancy text" written in different lettering styles, and some cells are also labelled in a different colour. The cell labelled Normal, at SLNT and CRSV 0, is written in a geometric upright block handwriting, similar to what would be used to teach young children how to print letters neatly. The sample labelled Italic is at SLNT=20deg and CRSV=1; the letters are slanted and many have curved flourishes at the ends of the strokes. The sample labelled Cursive is at SLNT=20deg and CRSV=2; it has similar flourishes and slant, but now in a fully connected script. The cells with SLNT=0 and CRSV=1 or 2 are labelled Upright Italic and Upright Cursive, respectively. They have the same flourishes and connectedness as their slanted equivalents, but with the vertical orientation of strokes maintained. The cell labelled Oblique is at SLNT=25deg and CRSV=0; it has the simple geometric shapes of the normal style, but at a sharp slant. An oblique sample with CRSV=0 and SLNT=20deg is also given but not labelled.

My question: How should this be implemented in OpenType? How should the styles be accessed in CSS?

Requirements:

Ideally (in my opinion):

kontur commented 7 months ago

@AmeliaBR Note that the VF spec allows arbitrary axes and convention is to uppercase those arbitrary tags (e.g. "CRSV"), as opposed to lower casing registered axes’ tags. A CSS spec should probably pertain only to registered axes and their ranges so ital (0 – 1) and slnt (-90 – 90).

Adapter would be one more variable font with both axes, albeit ital is a defacto binary one (e.g. note the "a" changing on ital = 1). Mind you cursive forms also apply to other scripts, e.g. Adapter supports this also for some Cyrillic, and it is perfectly thinkable to have meaning for Greek, Arabic, and many other scripts.

If simplification is the goal one could argue font-style: italic should instruct the font to use absolute maximum values forital and slnt, any of which are found present. This is very often the intent of the design, meaning for a font with:

(Mind you, font vendors can choose to implement axes in a way that they implicitly trigger another, so say a VF might be instructed to show ital = 1 cursive glyph shapes if slnt > x via opentype features. You could argue that activating ital for given slnt values is up to the font vendor, but even here even this sample of three fonts yields three different implementations, stemming surely from the ambiguity of this very issue.)

If control is the goal, my suggestion from the previous post is applicable, at the expense of complexity.

arrowtype commented 7 months ago

It’s also worth mentioning @arrowtype’s Recursive does a similar thing, but uses a custom CRSV axis to implement the italic cursive forms instead of the ital axis.

It was my original intent to use both slnt and ital in one font, using the ital axis for cursive form control instead of making a custom CRSV axis. However, Google Fonts folks suggested not using slnt+ital in the same font – I forget if it was their opinion that this shouldn’t happen or because of an expectation that it might work better in browsers.

davelab6 commented 7 months ago

We (I) asked you to do that because the behavior was underspecified, iirc.

https://arrowtype.github.io/vf-slnt-test was also great work you did at that time :)