Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a lang IDL Attribute to CanvasTextDrawingStyles, and clarify "direction" on same #10873

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

schenney-chromium
Copy link
Contributor

@schenney-chromium schenney-chromium commented Dec 19, 2024

There is no way to localize the fonts used in HTML Canvas text rendering and metrics when using an OffscreenCanvas, and there is no information in the spec about how to determine the primary language even for regular 2D canvas (though implementations seem to agree to use the canvas element's lang attribute and use the default locale from the HTTP header for Offscreen). This proposal adds a lang IDL attribute to CanvasTextDrawingStyles, which is mixed into both CanvasRenderingContext2D and OffscreenCanvasRenderingContext2D.

The changes in this PR define how the language for font resolution should be chosen for canvas contexts and applies the same treatment to the inherit value for the direction IDL attribute which is currently not well defined.

See the discussion in issue #10862.


/acknowledgements.html ( diff )
/canvas.html ( diff )

@schenney-chromium schenney-chromium marked this pull request as draft December 19, 2024 00:49
@schenney-chromium
Copy link
Contributor Author

@AndresRPerez12, @annevk, @fserb, @Kaiido
Hoping to take an initial look at this in the Dec 19 meeting. Failing that, the first meeting in January that I can attend.

Note that currently Chromium does not resolve direction: "inherit" in the manner I have proposed. It just looks at the direction style property on the canvas element, not the dir attribute on the canvas. This seems wrong given the preference for using the dir attribute to specify document writing mode rather than style.

Copy link
Member

@Kaiido Kaiido left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this!

I personally think it's great this hidden inheritance is finally made explicit, and having a way to set it on the context directly is a big plus.

source Outdated Show resolved Hide resolved
source Outdated
incorrect (including using property-independent style sheet syntax like 'inherit' or 'initial'),
then it must be ignored, without assigning a new font value. <ref>CSS</ref></p>
must be assigned to the context, with the primary language set to the <span
data-x="dom-context-2d-lang-used-value">used-value</span> for <code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this work to define the used value here? What if I don't ever set the font attribute, but set the lang one. Will my default sans-serif font be able to get this used value?
Would it work better if it was called in text preparation algo instead?

+Nit: might be better as "used value" (without the "-") for easing search in page

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will take a look at what happens in Chromium for a non-defined font, but regardless I will update this to make it clear what happens when there is no defined font. And yes to moving it to the text preparation algorithm (and the direction resolution) because it does indeed need to be dynamic. I was wondering about the right way to ensure that dynamic changes were handled, and you've answered my question.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this some more, this section going through getters and setters defines how the font is determined, while the text preparation algorithm says
Let font be the current font of target, as given by that object's font attribute.

The current font is never really defined. With that in mind, given that the lang is an input into the font selection process (at least in Chromium), I think the description of how to determine the lang belongs here, just after the font source determination method.

I will add text to clarify that the language applies even for the default font (which it does in Chromium), and also that any inherited value is snapshot when an offscreen canvas is created and for canvas elements is snapshot when the font or lang attributes are set.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the new version.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be aware that https://whatpr.org/html/10873/canvas.html doesn't reflect the last commit's changes. Don't know why.

source Outdated
and with system fonts being computed to explicit values. If the new value is syntactically
incorrect (including using property-independent style sheet syntax like 'inherit' or 'initial'),
then it must be ignored, without assigning a new font value. <ref>CSS</ref></p>
must be assigned to the context, with the primary language set to the <span
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "primary language" here the same as CSS's content language?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was intending it to mean the "primary language" as used in https://html.spec.whatwg.org/multipage/dom.html#the-lang-and-xml:lang-attributes. The solution is to convert that linked reference to a definition and then refer to the definition in the canvas change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This concept has been cleaned up.

source Outdated
attribute for="CanvasTextDrawingStyles"><code data-x="dom-context-2d-lang">lang</code></dfn>
IDL attribute. On setting, <code data-x="dom-context-2d-lang">lang</code> must be
a <span data-x="attr-lang">valid BCP 47 language tag</span> or the string "inherit".
To determine the <dfn data-x="dom-context-2d-lang-used-value">used value</dfn>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: It's a bit confusing to have this algo so close to the setter step. (Actually inside of it since there is no rendered return). It makes it look like it's called at that time only and thus doesn't make "inherit" dynamic.

Copy link
Contributor Author

@schenney-chromium schenney-chromium Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I'll move it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decided against a move because of the way the font setting is described, and because this is input to the font, not the text preparation steps.

source Show resolved Hide resolved
@schenney-chromium
Copy link
Contributor Author

Thanks for the very quick first round of review. I'll make the changes and update the PR.

@schenney-chromium schenney-chromium self-assigned this Dec 20, 2024
source Outdated
@@ -66410,15 +66426,56 @@ worker.postMessage(offscreenCanvas, [offscreenCanvas]);</code></pre>
Notice that the font is only loaded inside the worker, and not in the document context.</p>
</div>

<p>The choice of fonts and glyphs within a font may vary according to the <dfn
attribute for="CanvasTextDrawingStyles"><code data-x="dom-context-2d-lang">lang</code></dfn>
IDL attribute. On setting, <code data-x="dom-context-2d-lang">lang</code> must be

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be mentioned explicitly that if the string used for setting is invalid, then the stored value in the context doesn't change? For most other CanvasTextDrawingStyles it isn't needed as they are defined using IDL enums, but for the attributes that need parsing, like letterSpacing, the setter steps are outlined and therefore it's clear that an invalid value results in the attribute not being changed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the HTML lang attribute it is explicitly said that "unknown" values should be passed through unchanged. I believe that's because it's hard to write validation for locale strings. Anyways, I think here we also need to just accept whatever is given.

@schenney-chromium
Copy link
Contributor Author

I plan to get this updated in the coming week. One issue is a lack of testing coverage for offscreen canvas objects transferred from HTML canvas, needed to the the "inherit" attribute value. I filed web-platform-tests/wpt#49987 to get that added to wpt, and I think I'll try to do the work for it.

source Outdated
<ol>
<li><p>If <var>object</var>'s <span>font style source object</span> is a <code>canvas</code>
element, then return the element's <span data-x="attr-lang">lang atribute
value</span>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should return https://html.spec.whatwg.org/#language. The lang attribute can be specified in the ancestor chain.

source Outdated

<li><p>If <var>global</var> is a <code>Window</code> object, then return the
<span data-x="attr-lang">lang attribute</span> from <var>global</var>'s
<span data-x="concept-document-window">associated <code>Document</code></span>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates race conditions. We could snapshot the language when we create the worker, but I suspect it's not worth the complexity. On the fence as to whether it's worth doing when we create an OffscreenCanvas from a <canvas>.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's easy enough to snapshot the language when the OffscreenCanvas is created, at least in Chromium. I think we should snapshot for all OffscreenCanvas objects to keep things easy to understand. And we should only update the value for a canvas element when the lang attribute is set, plus at creation.

It's moderately expensive to look up the language because it requires walking up the DOM tree evaluated a bunch of conditionals. So snapshotting is also a performance win.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the new version for snapshotting language.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates race conditions. We could snapshot the language when we create the worker, but I suspect it's not worth the complexity. On the fence as to whether it's worth doing when we create an OffscreenCanvas from a <canvas>.

Why would that create race conditions? Could you please give an example.

source Outdated Show resolved Hide resolved
@schenney-chromium
Copy link
Contributor Author

I've finally updated the PR. Please take another look. The WHATNOT meeting 2 weeks from now is my target to discuss this, and hopefully approve the algorithms, if not the exact language.

<p>Returns the current language setting.</p>

<p>Can be set, to change the language used when resolving fonts. The syntax and valid values
are the same as those for the <span data-x="attr-lang">lang</span> element attribute, or the
Copy link
Member

@mbrodesser-Igalia mbrodesser-Igalia Jan 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: seems more correct to refer to something like dom-context-2d-lang-inherit. The same below.

@Kaiido
Copy link
Member

Kaiido commented Jan 22, 2025

The whole snapshot thing seems super complex and probably surprising for web-authors.
I, for one, would definitely expect that if I set it explicitly to "inherit" at some point and the <canvas>'s lang changes afterwards, the context's inner value would get updated, just like it is for direction.

Given that "inherit" doesn't really inherit but instead does ctx.lang = getComputedStyle(ctx.canvas)?.lang, I wonder how useful it is to have it at all. If the issue is to have a default value, maybe we can set it at creation time to the inherited value, if any, and let the getter return that value.

Note: The current default behavior doesn't seem interoperable, where Chrome actually uses the updated lang value, while Firefox and Safari seem to keep the one at creation time. I take it that Chrome is fine changing to the inherit-at-creation behavior.

@schenney-chromium
Copy link
Contributor Author

The whole snapshot thing seems super complex and probably surprising for web-authors. I, for one, would definitely expect that if I set it explicitly to "inherit" at some point and the <canvas>'s lang changes afterwards, the context's inner value would get updated, just like it is for direction.

I agree it's somewhat confusing, but every time I tried to come up with something more intuitive I failed. In particular, for things that affect the font, Chromium currently re-resolves the font each time one of those things changes. Hence Chrome is currently updating for the lang value (though I would have to look at how invalidation works in that case in the current code). Codifying that is hard because there's no definitive time when the font gets resolved. Maybe "changes in the inherited lang must be applied before the font is next used to render text or metrics" which is essentially the direction behavior.

Given that "inherit" doesn't really inherit but instead does ctx.lang = getComputedStyle(ctx.canvas)?.lang, I wonder how useful it is to have it at all. If the issue is to have a default value, maybe we can set it at creation time to the inherited value, if any, and let the getter return that value.

There is no easy way for authors to get the "computed value" for the language of an element. That's Issue #7039 that Anne linked to. In practice , WebKit and Chromium both have the CSS -webkit-locale property that can be queried, but that's not even close to standardized (though I guess we could).

Removing "inherit" gets into a problem with initial values. We want sane initial values for direction and the language, and "inherit" is the best initial value in my opinion. So if we were to remove it as a valid value to set for lang or direction, we would still need the spec text for how to compute the initial value. The CSS direction attribute has this problem where the spec does not match any browser implementation, because browsers all push the directionality of the element into the direction property for the initial value, not ltr as the spec says. I plan to put up a PR clarifying that behavior.

And then if you allow a special initial value, then how do you reset to that initial value? Drop "inherit" and add "initial"? I could get behind that.

Note: The current default behavior doesn't seem interoperable, where Chrome actually uses the updated lang value, while Firefox and Safari seem to keep the one at creation time. I take it that Chrome is fine changing to the inherit-at-creation behavior.

I just changed Chrome to use inherit-at-creation for direction in offscreen, but left the element canvas behavior the same. There's no real performance cost to Chromium's behavior for direction because changing it does not change the font. But for lang there's the performance hit of re-resolving the font that apparently Chromium is willing to take.

then set the <span data-x="dom-context-2d-font-language">language for the font</span> to
the source canvas element's <span data-x="language">language</span>. The source
canvas's language should be snapshot at the time of transfer and the value used
whenever the <code data-x="dom-context-2d-lang">lang</code> value is "inherit".</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't want this kind of specification style (or "COME FROM"). This makes the model very hard to understand. Instead we should propagate this state in the same place where you'd implement it, the transferControlToOffscreen() definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be sure I understand, I would modify the algorithm for creating offscreen canvas objects to include the steps to capture the direction and language when the object is created. Is that right? It sure makes sense to me and addresses something I thought was not well explained.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah and you'll have to store them somewhere so you also have to create some internal fields on OffscreenCanvas to capture the values in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, I think.

source Outdated
<p>The choice of fonts and glyphs within a font may vary according to the <dfn
attribute for="CanvasTextDrawingStyles"><code data-x="dom-context-2d-lang">lang</code></dfn>
IDL attribute. On setting, <code data-x="dom-context-2d-lang">lang</code> must be
a <span data-x="attr-lang">valid BCP 47 language tag</span> or the string "inherit".
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually define what the setter does.

I recommend looking at letterSpacing and wordSpacing. We want the same kind of language for lang and direction with the distinction between public API and internal state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, I think.

@schenney-chromium
Copy link
Contributor Author

New version up. Thanks, as always, for the reviews.

@@ -65339,6 +65339,14 @@ callback <dfn callback>BlobCallback</dfn> = undefined (<span>Blob</span>? blob);
<li><p>Set this <code>canvas</code> element's <span data-x="concept-canvas-context-mode">context
mode</span> to <span data-x="concept-canvas-placeholder">placeholder</span>.</p></li>

<li><p>Set the <span data-x="offscreencanvas-inherited-lang">inherited language</span> of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (to be applied throughout this PR):

Set offscreenCanvas's inherited language to this canvas element's language.

(There's further modernization to be done here, but let's consider that out-of-scope.)

<dt><code data-x=""><var>context</var>.<span subdfn data-x="dom-context-2d-font">font</span> [ = <var>value</var> ]</code></dt>
<dt><code data-x=""><var>styles</var>.<span data-x="dom-context-2d-font">font</span> [ = <var>value</var> ]</code></dt>
<dd>
<p>Returns the current font settings.</p>

<p>Can be set, to change the font. The syntax is the same as for the CSS <span>'font'</span>
property; values that cannot be parsed as CSS font values are ignored.</p>
property; values that cannot be parsed as CSS font values are ignored. The default is "10px sans-serif".</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wrap at a 100 columns.

<p>Objects that implement the <code>CanvasTextDrawingStyles</code> interface have a <dfn attribute
for="CanvasTextDrawingStyles"><code data-x="dom-context-2d-lang">lang</code></dfn> attribute
that controls localization of the font. The <code data-x="dom-context-2d-lang">lang</code> IDL
attribute, on getting, must return the current value. The <code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couple issues here:

  1. "The lang getter steps are to return ..."
  2. It needs to return the value of some internal concept, similar to what letterSpacing does.
  3. It should probably be its own paragraph. We no longer really define getters and setters and concepts all in one paragraph.

data-x="dom-context-2d-lang">lang</code> setter steps are:</p>

<ol>
<li><p>Change the current value to the new value.</p></li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to manipulate an internal concept.

Comment on lines +66728 to +66729
<li><p>Set the <dfn data-x="dom-context-2d-font-language">language for the font</dfn> using the
following steps:</p>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement seems redundant with subsequent statements. I suggest we drop it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps this is meant to be the internal language concept? But if so we shouldn't define it inline in the algorithm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

5 participants