Re: [css-fonts] Issues with font matching algorithm when weights are missing

Hi Lea,

As Jonathan has already pointed out, the problem with the Gill Sans
family on Windows is an issue with the way the DirectWrite API
synthesizes font family groupings. It's an issue tied to historical
baggage with Windows font API's. Firefox and IE both use hacks to
workaround the issue. Chrome, which recently switched to DirectWrite,
hasn't implemented a workaround but there is an issue logged. [*] I've
included more about the history of this problem below.

As to the larger issue of how font weights are matched, CSS has always
defined a simple prioritization scheme for font matching, going back to
CSS1. A family is selected from the fontlist and a face is selected from
the faces available for that family, using the width, slant and weight
attributes of faces in that order.

Other priorization schemes such as the ones you suggest are possible
but, no matter what, choosing a font outside the one an author intended
to use is an imperfect art. The attributes assigned a specific face are
the choice of the type designer, there's no absolute emperical scale
here. One type designer's medium is another's demi-bold. ;)

> The font matching algorithm outlined in [1] results in cases of very
> odd matching when it comes to missing font weights and it should be
> changed. For example, if the requested font weight is 100 (extra
> light) and only 900 is available (which is black) or vice versa, this
> one will be used instead of falling back to the next family in the
> font stack. When the difference between desired and available weights
> is so dramatic, falling back would be a more appropriate choice in
> most cases.

A font family represents a set of faces constructed with a similar
design aesthetic. Faces generally differ in a known way based on the
width, slant and weight attributes assigned them. However, there's no
way for user agents to reliably assess whether a separate family is
"closer" to a particular set of attributes or not. In general, it rarely
is, although you can always construct combinations of families
containing sparse numbers of faces for which it might be (e.g. the Gill
Sans case under DirectWrite on Windows). But that's not
a good basis for general font matching rules.

> In addition, even when the difference between available and requested
> weights is smaller, the current algorithm can still result in
> suboptimal matching (i.e. picking weights that are not the closest
> possible to the requested weight).

When choosing the font weight, if the desired weight is not available,
in general a lighter font is chosen for weights less than normal and a
heavier weight is chosen for weights above normal (see the algorithm for
the specific mappings). For desired weights other than normal this makes
it so that a weight *other* than normal is chosen. If the "optimal"
choice is defined as simply the closest weight to the desired weight,
text spans with differing weights will more frequently end up displayed
using the same face.

Example:

  p    { font-weight: normal }
  span { font-weight: 300 }

  <p>This is a <span>subtle</span> distinction in my opinion</p>

The current font matching algorithm will select a lighter face if any
lighter face is available. Using the face with the closest weight
distance may or may not depending upon the specific faces available.

Regards,

John Daggett
Mozilla Japan

[*] https://code.google.com/p/chromium/issues/detail?id=408093

Background on the DirectWrite Gill Sans problem
==============================
=================

The problem with the Gill Sans family on Windows is actually a problem
with how the DirectWrite API on Windows swizzles around and creates
synthetic font family groupings. Prior to DirectWrite, Windows
applications used GDI which restricted font families to four basic
faces, Regular, Bold, Italic and Bold Italic. Due to this restriction,
font vendors sometimes shipped fonts which the style name included in
the family name. Some commonly used Microsoft application suites bundled
a pair of fonts with a family name of "Gill Sans Ultra Bold", along with
some other fonts labeled with a family name of "Gill Sans MT". Under GDI
these are only accessible using family names of "Gill Sans Ultra Bold"
and "Gill Sans MT".

In an attempt to create larger font families such as those that exist
naturally under OSX, DirectWrite tries to fold in fonts that use this
sort of pattern into larger families. The gory details are in the "WPF
font selection model" white paper [1] which details this process. Look
through the section labeled "Font Differentiation" starting on page 6.
The net result of this process is that DirectWrite will take a font with
a font family name of "Gill Sans Ultra Bold", strip out the "Ultra Bold"
weight name and group these fonts into a family labeled "Gill Sans".
But this produces a family only containing ultra bold faces! Since OSX
ships with a normal set of Gill Sans faces, authors who like this family
often include it in their fontlists (Hi Bert!). But under DirectWrite on
Windows this produces the results you've noted.

Chrome recently switched to using DirectWrite and that's why your seeing
the problem now. Users have already logged this as an issue [*]. When
Firefox switched to using DirectWrite in 2010 we came across this issue
and we worked with the IE team to implement an explicit hack to work
around this problem [2]. The result is that Firefox DirectWrite font
handling code explicitly pushes these Gill Sans ultra bold faces into
the Gill Sans MT family [3]. Pure ick but that's life in the world of
fonts sometimes. Chrome hasn't implemented this hack.

[1]
http://blogs.msdn.com/b/text/archive/2007/04/23/wpf-font-selection-model.aspx
[2] https://bugzilla.mozilla.org/show_bug.cgi?id=551313
[3]
http://mxr.mozilla.org/mozilla-central/source/gfx/thebes/gfxDWriteFontList.cpp#998

On Mon, Dec 29, 2014 at 3:12 AM, Lea Verou <lea@verou.me> wrote:

> The font matching algorithm outlined in [1] results in cases of very odd
> matching when it comes to missing font weights and it should be changed.
> For example, if the requested font weight is 100 (extra light) and only 900
> is available (which is black) or vice versa, this one will be used instead
> of falling back to the next family in the font stack. When the difference
> between desired and available weights is so dramatic, falling back would be
> a more appropriate choice in most cases.
>
> This issue was brought to my attention when a colleague sent me [2],
> asking why the different behavior between Firefox & Chrome. Upon further
> investigation, it appeared that he only had UltraBold for Gill Sans and
> Chrome used that for all weights to avoid falling back to Gill Sans MT. One
> would expect that Chrome’s behavior is the buggy one here, but according to
> the existing algorithm [2], Chrome is perfectly correct and Firefox is
> buggy!
>
> In addition, even when the difference between available and requested
> weights is smaller, the current algorithm can still result in suboptimal
> matching (i.e. picking weights that are not the closest possible to the
> requested weight). Observe the following examples:
>
> Desired | Available     | Matched       | Better option
> --------+---------------+---------------+----------------
> 100     | 900           | 900 (+800)    | Fall back!
> 900     | 100           | 100 (-800)    | Fall back!
> 400     | 100, 600      | 100 (-300)    | 600 (+200)
> 500     | 300, 900      | 900 (+400)    | 300 (-200)
> 600     | 500, 900      | 900 (+300)    | 500 (-100)
> 300     | 100, 400      | 100 (-300)    | 400 (+100)
>
> I believe it would be better to alternately try to match weights with an
> offset of -100, +100, -200, +200, ... up to a certain maximum (by absolute
> value) offset to avoid the issue shown in [2] (200 or 300 looks like a good
> candidate). I’ve written a simple JS function [3] so you can test this out
> with different parameters (the possible combinations of missing weights are
> more than you might think: as many as 9*2^8=2304).
> Not only this algorithm always results in matching the closest possible
> weight (results perfectly match the “Better option” column above), but it
> avoids having to special case 400 and 500 like the existing one.
>
> Currently it prioritizes lighter fonts over bolder fonts when two weights
> exist with the same distance, but that’s an arbitrary choice. Since it’s
> arbitrary, we could easily start from the positive offset if the weight is
> divisible by 200, which results in matching 500 when the desired weight is
> 400 and 400 when the desired weight is 500, just like the existing special
> casing which checks 500 first for 400, and 500 first for 400.
>
> Thoughts?
>
> [1]: http://dev.w3.org/csswg/css-fonts/#font-matching-algorithm
> [2]: (URL: cssfontstack.com/Gill-Sans, OS: Windows 8)
> http://lea.verou.me/images/font-matching.png
> [3]: https://gist.github.com/LeaVerou/adbc2e4750ad809b6aef
>
> Lea Verou ✿ http://lea.verou.me ✿ @leaverou
>
>
>
>
>
>
>
>

Received on Tuesday, 6 January 2015 01:27:56 UTC