css-font-rendering
css-font-rendering copied to clipboard
Bedrock for CSS font-rendering?
The CSS font-rendering proposal is an attempt to explain the bit of magic that goes on around web font rendering in browsers:
Browser timeout fallback swap Chrome 35+ 3 seconds yes yes Opera 3 seconds yes yes Firefox 3 seconds yes yes Internet Explorer 0 seconds yes yes Safari N/A N/A N/A
- Chrome and Firefox have a 3 second timeout after which the text is shown with the fallback font. Eventually, a swap occurs: the text is re-rendered with the intended font once it becomes available.
- Internet Explorer has a 0 second timeout which results in immediate text rendering: if the requested font is not yet available, fallback is used, and text is rerendered later once the requested font becomes available.
- Safari has no timeout behavior (or at least nothing beyond a baseline network timeout of 60(?) seconds)
A discussion around the adequacy of the name "font-rendering" lead to the realization that we have a hard time identifying/naming the underlying system involved here. This suggests that CSS font-rendering might not be ambitious enough or might not explain the actual magic but a specific manifestation of it. I think this is worth investigating in parallel with making progress on the proposal and adjust as we learn more.
Relation to Houdini to/ @shans @bfgeek
To start, we should find out if Houdini (bedrock for CSS) would impact this proposal, otherwise we run the risk of turning CSS font-rendering into The Magic Trick That Fooled Houdini ಠ_ಠ
Is the CSS font-rendering use case (a specific manifestation of the magic that goes into holding paint) in scope for Houdini? Anything started in this area?
CSS font-rendering example
@font-face {
font-family: 'Open Sans';
font-style: normal;
font-weight: 400;
src: url(//example.com/opensans/normal400.woff2) format('woff2');
/*
set default font-rendering strategy
- don't block text rendering on this font
- abandon progressive rendering after 2s
*/
font-rendering: swap 2s;
}
body { font-family: Open Sans; }
#header {
/* block rendering until the desired font is available (due to branding requirements).
if branding font is not ready within 500ms, use fallback */
font-rendering: block 500ms;
}
#headline {
/* immediately render the headlines, if the desired font is available, great...
and if not, use fallback and don't rerender to minimize reflow */
font-rendering: optional;
}
#main-content {
/* don't hold rendering, but rerender with the desired font once available */
/* give up on progressive strategy after 150ms due to UX+perf requirements */
font-rendering: swap 150ms;
}
#footer {
/* inherits font-rendering: swap */
}
cc/ @natduca @igrigorik @tabatkins
@KenjiBaheux yeah that's pretty magical.
Does this block just paint or layout as well? For example can I query offsetWidth of an element which doesn't have a loaded font yet?
Re: Houdini, nothing in the spec w.r.t. holding paint at the moment, however we've had discussions regarding when to paint (I.e. if blocking is a reasonable option) in addition to blocking layout. Element optionality and asynchronous in-flow elements (if we end up doing them) will need similar ideas.
If we had some kind of paint blocking primitive then font-rendering could be implemented entirely as a user-land property on top of Houdini.
Do we have anything else which can block rendering on the page? For example:
- Parsing Stylesheet?
- Loading Font
Are there things that we should allow blocking rendering? For example:
- Loading sprite image for icons?
Ian
@bfgeek Before a font loads, we're either rendering with a fallback, or with some sort of "default" metrics (depending on whether we're in the block or swap period). So nothing is blocked; we can paint immediately, query things immediately, etc. (In other words, during the "block" period, you fallback to an invisible default font which has no glyphs.)
@bfgeek @tabatkins 's description is more accurate.
Perhaps, "custom paint" and "custom layout" are more relevant then?
If we can explain all font-rendering behaviors via custom paint and custom layout callbacks, can we consider this issue resolved?
Naively, I imagine that it can be achieved through a combination of:
- CSS Font Loading API (check, ready.then)
- user timing API and window.performance.now()
- style introspection
Let me try a few
For an element whose style is set with font-rendering: optional:
Have a custom layout callback where:
- Font Loading's check() is used to find out if the desired font is ready
- if it's not ready then change the style to the fallback font
- let the UA handle the actual layout with this new style
For an element whose style is set with font-rendering: block infinite:
Have a custom paint callback where:
- use Font Loading's ready().then to:
- draw the text with the desired font (assuming that the re-layouting happens naturally as the font becomes ready).
For an element whose style is set with font-rendering: block 0s swap 500ms:
Have a custom layout callback where:
- use Font Loading's check() to find out if the desired font is ready.
- if it's not then change the font to the fallback font
- set a race between a 500ms timeout and a Font loading check() to find out if the desired font is ready before 500ms are elapsed,
- if the desired font is ready, update the element's style to use it
- layout and paint naturally kick in again.
- if the desired font is ready, update the element's style to use it
- let the UA handle the actual layout
Have a custom paint callback
- ask to recompute the layout (font metrics have changed)
- draw the text with the desired font
How does that sound?
cc/ @igrigorik
@KenjiBaheux I think that's spot on. My only nitpick would be with the last 500ms timer logic:
set a race between a (500ms - performance.now()) timeout and a Font Loading check() to find out if the desired font is ready before timer elapsed...
That is, I think we should think of the specified "500ms" timer as counting from navigationStart, instead of requestStart, because requestStart is a moving target and hard to reason about.
That is, I think we should think of the specified "500ms" timer as counting from navigationStart, instead of requestStart, because requestStart is a moving target and hard to reason about.
Is that consistent with what browsers actually do? My spec currently dictates that the timer starts at requestStart, because it doesn't seem to make much sense for a font that isn't used on load to have its timer already-expired when it is finally invoked.
@tabatkins consistent with respect to what? I think both start times are valid (navStart, requestStart) and have their respective use cases. That said, I think navigationStart is what developers would expect/want in the context of optimizing rendering behavior of the page...
The problem with fetchStart is that you actually have no idea when the request will start, so "3s" from fetchStart is actually 3s + unknown-fetch-start-delay, where the unknown delay can be easily measured in seconds -- e.g. style calc is blocked on slow response of a script, other css file, etc. As such, this is not terribly useful: an optional font may still be blocked for some time; all timers have an unknown value added to them. If my goal is to deliver a reliable 1000ms render (with desired font, or fallback), I can't do that with font-rendering that uses fetchStart, since I don't know when fetchStart will fire.
Making font load behavior depend on document load time, so that a timer can expire before you ever even attempt to load the font file doesn't sound useful. If you think there is a use-case, coudl you elaborate?
You seem to be thinking about the difference in time between "I've activated the style that applies the font" and "the font load is started", but it's actually the difference in time between "the page is loaded" and "the font load is started". If I only use a particular font on error messages, and the user doesn't cause an error message until they've spent a minute or two on the site, that's 100s or more of difference between the two times.
You're right, the timer business is complicated.
My primary goal is to ensure that we enable developers to deliver the fastest possible experience in their application. In the case of text-rendering, this means giving them control over which text rendering strategy is chosen when the text is first ready to paint - i.e. should the UA paint it now and swap in the font, should the UA block and paint later, or should it paint with whatever is available and leave it. This decision / property is independent of any timers.
Some concrete applications of this are:
- As a developer I want to deliver a 1000ms time to glass on initial page load. The timer starts ticking from the start of the navigation.
- Later, I add some content to the page, or trigger a rule that activates another font: the text is ready to paint immediately, but I need control over whether rendering will get blocked on the font fetch, or if it should swap / be optional. The timer here starts ticking from the point when font fetch starts.
Stepping back, perhaps the timer semantics can be separated from the control of the chosen font-rendering strategy? As in, block / swap / optional are properties on the text, whereas timer is a property of the font? Hand~wavy example...
@font-face {
font-family: 'Open Sans';
...
font-timeout: 3s;
}
.content { font-rendering: swap; font-family: Open Sans; }
.other { font-rendering: block; font-family Open Sans; }
The font-rendering controls which strategy is chosen when text is first ready to paint, and timeout is controlled by the @font-face property? This model has its limitations as well, but it feels "more right"?
On Wed, Apr 8, 2015 at 1:18 PM, Tab Atkins Jr. [email protected] wrote: (www-style) In the call today, we agreed to initially give the spec only the keyword values, with an issue discussing whether or not to allow explicit timeouts.
Hmm, I think this is effectively what I'm proposing above, except that font-rendering applies to @font-face as well.. perhaps that's unnecessary?
Why can't the timeout be done in js? We set a timer to change the style...
What do y'all think about a shared google doc for "can font rendering be done with houdini?" Its easier to collaborate on a proposal in that format, ask questions, share around to other people, than with a centithread.
I think the FontLoader API + setTimeout is enough for JS to deal with this. +1 to discussing it in a doc.
FontLoader API and setTimeout being enough is missing the point.
https://lists.w3.org/Archives/Public/www-style/2014Oct/0505.html
Why can't the timeout be done in js?
Because you don't know when the timer started? It starts when we first attempt to use the font. To get the same behavior, you have to not use the font at all withing your CSS at first, and instead invert your design so that the element pings some observer about what font it needs when it first knows it's going to render (somehow, perhaps a more explicit "display me" API, which means you're avoiding use of the 'display' property too...).
In general, all of this stuff can be done with the Font Loading API, to some degree of fidelity. It's not trivial, but it's doable. However, attempting to argue that this means we don't need anything in CSS is completely wrong-headed, for several reasons:
- The default behavior today, blocking until loaded or timeout, is bad for users. Saying that the only way to fix it is to do a moderately difficult bit of work in JS just means that it'll continue being bad for most users.
- This problem is particularly bad for fonts used immediately, on page load. Requiring authors to handle font-loading behavior in JS means that you have to wait for the JS to load, meaning you're blocking font loading behind at least another RTT of network delay, which is the exact opposite of what we want. (Or you're requiring that authors embed font-loading JS directly in their HTML, early in the response.)
- Doing this with any degree of fidelity requires much larger changes to the page architecture than you're probably imagining, with elements taking over much more responsibility for displaying themselves than CSS offers. This is basically reinventing a bunch of CSS, violating the "don't make people rebuild from the bottom" aesthetic that all the "explain the platform" stuff is trying to maintain. It's possible to add more API that makes this less bad - for example, some way to observe when an element is about to paint for the first time - but I don't think we should forced to invoke Custom Paint on every element on the page just to make fonts not block rendering.