whatwg / html

HTML Standard
https://html.spec.whatwg.org/multipage/
Other
7.87k stars 2.58k forks source link

OpenType Features and Variable Fonts for Canvas #3571

Open drott opened 6 years ago

drott commented 6 years ago

Font styling for the 2D Context of Canvas is currently limited to the font state of the context object.

I would like to start a discussion on extending Canvas' capabilities for font styling and OpenType feature activation. The reason being that a number of advancements in typographic quality on the web are not accessible through the font property of 2D context.

In regular CSS activating OpenType features such as ligatures, small caps, contextual alternates, additional number/fraction/ordinal forms is performed through using font-variant-* properties, and font-feature-settings.

The second limitation is the usage of Variable Fonts (Introducing Variable Fonts, Introduction to Variable Fonts on the Web, Variable Fonts Exploration).

In regular CSS, controlling parameters for the rendering of variable fonts is done through the font-weight, font-stretch, and font-style properties, which can be specified as part of the font property/shorthand form on canvas. However, activating variation axes outside these three canonical axes is not possible, preventing a whole set of more artistic and creative fonts to be used on canvas. Control of the additional axes is done through the font-optical-sizing property as well as the low level font-variation-settings.

I believe it would be beneficial to to find a way for all of the tools and control that the CSS Fonts Specification offers to be applicable to the 2D context of Canvas.

drott commented 6 years ago

Perhaps one approach would be to expose a style-like object on the 2D context and define a subset of style properties relevant for font-styling to be permitted, such as font-family, font-(stretch|style|weight), font-variant-*, font-feature-settings, font-variation-settings etc.

annevk commented 6 years ago

Could we use https://drafts.csswg.org/css-font-loading/#fontface-interface somehow? Abstracting fonts into a reusable/mutable object rather than exposing various properties to poke at it seems more reasonable.

cc @tabatkins

drott commented 6 years ago

While the FontFace interface is getting close, I find it confusing to use FontFace in a dual purpose: In one sense as a representation of a @font-face rule that style rules are being matched against, and in a second sense as a collection of style attributes covering font styling, meaning one concrete particular instance of a font.

Especially so for variable fonts, where a @font-face can mean a spectrum of available font instances, e.g. weight ranging from 100 to 700, stretch ranging from 100% to 150%, slant ranging from 0 deg to 20 deg.

Also, @litherum commented in a separate thread that accessing/assigning only one such instance to the 2D context does not provide a means of specifying a cascade for fallback, as we would usually find it in the font-family stack of fonts.

annevk commented 6 years ago

We could make it a sequence of FontFace objects, but we could also support a CSSFontFaceRule object, if that matches things better. It just seems that at some point we should have an underlying primitive here rather than keep adding properties to the 2D context.

kojiishi commented 6 years ago

/cc @jfkthame @FremyCompany

litherum commented 6 years ago

We could make it a sequence of FontFace objects, but we could also support a CSSFontFaceRule object, if that matches things better. It just seems that at some point we should have an underlying primitive here rather than keep adding properties to the 2D context.

The underlying primitive for font selection is CSS properties.

  1. In the past, canvas's interaction with fonts has maintained the invariant that drawing text in a canvas has the same visual result as the browser drawing native text. Matching native rendering is desirable for sites like Flipboard which implemented their entire site using canvas.
  2. Keep in mind that this has to be implementable - all browsers already have code to select fonts using CSS properties and draw the result. Requiring an entirely new font selection mechanism would likely limit implementor's interest in implementing (it would certainly limit our interest).

Perhaps one approach would be to expose a style-like object on the 2D context and define a subset of style properties relevant for font-styling to be permitted

I think @drott's suggestion is a good one. Making the canvas API match the existing font selection facilities is best for developers, users, and implementors. Indeed, this is already how the existing canvas API was designed, and we should not deviate from that design.

litherum commented 6 years ago

Oh, a point I missed before - this issue is bringing up a valuable request. The ability to use great fonts should be ubiquitous, and included in every subsystem that interacts with text.

tabatkins commented 6 years ago

Note that there are two very distinct possibilities for a font-related interface (the confusion of which resulted in a very mixed-up TypedOM section that I eventually had to remove):

  1. Explicitly providing font faces to something for use - these should use FontFace. (CSSFontFaceRule is roughly equivalent, but it's tied more to the stylesheet and its loading behavior; it shouldn't be used directly by APIs.)
  2. Activating the system's font-selection capability - this is what canvas currently does with its font attribute.

The latter is currently only exposed thru CSS properties; at some point in a future level of TypedOM there will be an object representing a font-selection query (for 'font' to reify to), but that doesn't exist yet.

You probably want to continue canvas's current approach, and continue just doing font queries, rather than explicitly providing faces.

FremyCompany commented 6 years ago

+1 to what @tabatkins said

litherum commented 5 years ago

Yes, @tabatkins is right.

darshanbib commented 2 years ago

+1 this would be a great feature to add.

rsheeter commented 2 years ago

Pardon my density but I'm a little fuzzy on how this issue can be moved forward? - in addition to ot features and variations I'd love to be able to use font-palette and font-palette-values (https://www.w3.org/TR/css-fonts-4/#color-font-support) on text in a canvas, and of course to have a setup where the default for new font capabilities is to work on canvas.

litherum commented 2 years ago

I think we're just missing a concrete proposal.

macnmm commented 1 year ago

using measuretext to measure text on canvas using a variable font is among the concrete needs people have...

RoelN commented 1 year ago

using measuretext to measure text on canvas using a variable font is among the concrete needs people have...

Indeed. If a font has a opsz axis, the canvas will apply the optical size matching the font size, with no way to override it. So measureText.width for 160px will not be 10x the value of measureText.width for 16px.

nornagon commented 1 year ago

Based on @tabatkins' comment from 2018, it seems like the way forward is to add additional properties to CanvasRenderingContext2D mirroring the CSS font-{weight,stretch,style,size,variant-*,kerning,palette} properties (and probably more that I've missed in this list). It looks like this has already happened for fontKerning, fontStretch and fontVariantCaps, so adding the rest would be consistent.

nornagon commented 1 year ago

Looking a little deeper into the above, let me parse out each individual font-* CSS property and comment on them separately.

If all these were added, we'd have a bunch of new DOMString attributes on CanvasRenderingContext2D to specify font settings. Some of these would interact with the value of the font attribute (in the way that e.g. setting fontVariantCaps = "small-caps" causes the font attribute to have the value 'small-caps 10px sans-serif', or setting font = "small-caps 10px sans-serif" causes the value of fontVariantCaps to be 'small-caps'), while others would not. Specifically, this would add:

attribute DOMString fontWeight;
attribute DOMString fontStyle;
attribute DOMString fontSize;
attribute DOMString fontVariant;
attribute DOMString fontSynthesis;
attribute DOMString fontFeatureSettings;
attribute DOMString fontVariationSettings;
attribute DOMString fontLanguageOverride;
attribute DOMString fontOpticalSizing;
attribute DOMString fontPalette;
attribute DOMString fontSizeAdjust;

to the CanvasRenderingContext2D interface. This would bring the full set of font control features from CSS Fonts Level 4 to <canvas>.

tabatkins commented 1 year ago

I'd suggest mirroring everything that can be selected by 'font', so that the .font attribute can behave entirely like a shorthand property in CSS (and akin to the .style.font attribute in CSSOM). So that would imply adding .fontFamily too, at least. (I'm not checking the full set of 'font' sub-properties right now.)

This would allow you to do useful things like just changing the family while leaving the other settings intact; right now you'd have to set .font and then re-set all the sub-settings.

nornagon commented 1 year ago

Yeah, that makes sense. I guess this would almost exactly mirror the .style.font* in CSSOM, with the exception that inherit, smaller and similar values that imply an element tree to refer to would be excluded from allowable values.

annevk commented 1 year ago

They could use the canvas element itself to resolve those values. That's what we do elsewhere. (For OffscreenCanvas we would have to define what happens in the absence of an element.)

Lorp commented 1 year ago

Ideally we’d be able to style fonts for canvas just as we style them for HTML: the canvas would somehow “import” the font instance definition from an element defined with CSS (potentially the \<canvas> element itself). This would make it very convenient for authors to switch freely between rendering text as HTML elements or on the canvas, which seems a very desirable goal. In documents that use both models, authors don’t want to worry whether they’ve correctly matched font instance definitions.

Here’s some JavaScript showing current and imagined ways of cloning an HTML element’s font style to a canvas, which may be useful for discussion:

The current method is how we do things now, where only a limited number of font properties can be replicated in canvas.

The future-A method imagines expanding the font shorthand property to include variations (and a string syntax for that), being able to retrieve complete shorthand property values from getComputedStyle(), and the canvas context able to ingest that.

The future-B method imagines a currentFont (psuedo-)property, a simpler way of getting the font shorthand property, and the canvas context able to ingest that.

The future-C method imagines a new Window.getComputedFont() method, which returns an object that a canvas context can ingest.

// set up div
let mydiv = document.getElementById("mydiv");
mydiv.style.fontFamily = "Amstelvar";
mydiv.style.fontSize = "48px";
mydiv.style.fontVariationSettings = `"XOPQ" 130, "YOPQ" 120, "XTRA" 420`;
let computedStyle = window.getComputedStyle(mydiv);

// set up canvas
let canvas = document.getElementById("mycanvas");
let ctx = canvas.getContext("2d");

// choose font selection tech for canvas
let tech = "current";
switch (tech) {

  case "current": 
    ctx.font = `${computedStyle.fontSize} ${computedStyle.fontFamily}`;
    break;

  case "future-A":
    ctx.font = computedStyle.font;
    break;

  case "future-B":
    ctx.font = mydiv.style.currentFont;
    break;

  case "future-C":
    ctx.font = window.getComputedFont(mydiv.style);
    break;

}

ctx.fillText("Test variable font instance in <canvas>", 0, 100);
Lorp commented 1 year ago

@tabatkins:

I'd suggest mirroring everything that can be selected by 'font', so that the .font attribute can behave entirely like a shorthand property in CSS…

Don’t forget that font-variation-settings, font-feature-settings, font-palette, font-variant-* and several other font properties are not settable via the font shorthand. Maybe they should be, and that would imply a canvas solution something like my "future-A" suggestion above.