-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Canvas TextMetrics additions for editing and text styling #10677
Comments
I think |
The However it's unclear why it's mutable indeed, nor why the Also the |
It seems this intends to cover some of the same use cases as #10650. cc @whatwg/canvas @khushalsagar |
You're right. We originally thought of as a interface since the underlying object has to save references to other objects and it felt more natural, but having it be a dictionary is more useful from the user's side. Plus, a standard dictionary would allow to create modified copies via the spread syntax. I'll update it.
Indeed, that is the idea. In the current prototype in Chromium (CL is currently under review), the
We think it can be useful to allow the creation of
The idea for the Thanks for your comments! |
I fail to see how this could work. As per your previous point, authors would also need to define all of the
I think that'd be the first such positioning argument that defaults to |
Sorry, I'm a bit confused. Dictionaries can't save references to other objects, so if that's indeed needed, then staying with the current interface design makes the most sense. |
Yes, the issues are related in that they came out of the same discussions and prior proposals for improving canvas text. The editing aspects of this proposal could be covered by just inserting HTML content and editing that, but the proposal here is simpler from both an implementation and author perspective. The access to text cluster information is unique to this proposal and really to a canvas context where the author has direct control of placement of everything. |
For modification, the main use case we have thought of so far would be to actually draw the cluster at the position For the manual creation of
After discussing I realized I mixed this with another default. This default is just a remanent of how my first prototype was implemented. I agree the |
What does I think what you are trying to do is a job of The name of this method is a little bit confusing:
First it does not return a |
Yes, I agree there is some potential for confusion in the arguments differing from the DOM version, but there is some discoverability benefit to having the same name. Would |
Post WHATWG meeting action items:
|
I think |
cc @whatwg/i18n |
"index" is exactly what I was thinking after the meeting yesterday. The behavior here accounts for graphemes the same way that document.caretLocationFromPoint does (generally we use the same underlying code, though not quite in the Chromium case). It's also the code that decides the boundaries of a selection range. It tries to split ligatures but gives the first or last index of a grapheme cluster depending on the bidi direction and whether this point is the start or end of the selection range. For a single point query we take the start of the cluster in the bidi direction. Don't even get me started on figuring out an index from point in a mixed bidi string. The best logic there is highly context dependent and there is no existing spec language to cover it, nor do I think there should be. |
Regarding compatibility with the TC399 It definitely seems worth considering the use of JS for segmentation and change the The biggest challenge to this proposal is that the |
I've compared the results of the Intl.Segmenter We could have It doesn't seem worth it right now. |
The Explainer has been updated with changes to the text cluster methods. The related IDL now looks like this: dictionary TextClusterOptions {
DOMString align;
DOMString baseline;
double x;
double y;
};
[Exposed=(Window,Worker)]
interface TextCluster {
readonly attribute double x;
readonly attribute double y;
readonly attribute unsigned long begin;
readonly attribute unsigned long end;
readonly attribute DOMString align;
readonly attribute DOMString baseline;
};
[Exposed=(Window,Worker)] interface TextMetrics {
// ... extended from current TextMetrics.
unsigned long getIndexFromOffset(double offset);
sequence<DOMRectReadOnly> getSelectionRects(unsigned long start, unsigned long end);
DOMRectReadOnly getActualBoundingBox(unsigned long start, unsigned long end);
sequence<TextCluster> getTextClusters(unsigned long start, unsigned long end, optional TextClusterOptions options);
};
In addition, a new method on CanvasRenderingContext2D supports filling grapheme clusters:
interface CanvasRenderingContext2D {
// ... extended from current CanvasRenderingContext2D.
void fillTextCluster(TextCluster textCluster, double x, double y, optional TextClusterOptions options);
}; The options to getTextClusters give parameters to use when computing the cluster positions. The same options to fillTextCluster override the values used when the set of clusters was generated with getTextClusters. There is an example using these options to render text on a circle. Note also the rename for getIndexFromOffset. |
Regarding localization. The Control of localization in JS calls seems to me to be more targeted toward non-bindings situations, like node.js. This html confirms that, in Chrome at least, the lang attribute on the canvas influences the text metrics: <!DOCTYPE html>
<html>
<body>
<canvas id="en" width="300px" height="300px" lang="en"></canvas>
<canvas id="bg" width="300px" height="300px" lang="bg"></canvas>
<script>
let en_context = document.getElementById("en").getContext("2d");
let bg_context = document.getElementById("bg").getContext("2d");
function drawText(context) {
context.font = "20px Commissioner";
let text = "абвгд";
context.color = "black";
context.fillText(text, 50, 50);
let metrics = context.measureText(text);
console.log(metrics.width);
}
let myFont = new FontFace(
"Commissioner",
"url(https://fonts.gstatic.com/s/commissioner/v20/tDbw2o2WnlgI0FNDgduEk4jAhwgumbU1SVfU5BD8OuRL8OstC6KOhgvBYWSFJ-Mgdrgiju6fF8m0bkXaexs.woff2)"
);
myFont.load().then((font) => {
document.fonts.add(font);
drawText(en_context);
drawText(bg_context);
});
</script>
</body>
</html> |
The |
A bit more context on the options parameter for This came about as an answer to concerns expressed here about why weren't all the attributes of a We agree that it's better to have all attributes as Please let us know what you think! |
Offscreen canvas should be fixed to accept lang, because it also renders text and one would not expect the offscreen canvas font choices to differ from the canvas you are putting the offscreen content into. That would be adding an optional locale to the constructor for OffscreenCanvas. If the offscreen is created from a canvas element it would get the canvas element's locale. I suppose that's a separate issue. |
Created #10862 for adding a |
What problem are you trying to solve?
Selection and caret position are two building blocks for editing text in canvas content. Consider the sequence of dragging out a text selection with a mouse or touch, then copying and pasting into a new location. Determining which characters are part of the selection requires mapping a point onto a string, then to a caret position in the text. Drawing the selected region requires the selection area. Inserting again requires mapping a point into a location within a character string. It should be easy for authors to implement editing behavior in canvas.
In addition, we’ve seen increased demand for better text animation and control in canvas. Of particular concern are text strings where the mapping from character positions to rendered characters is complex or not known at the time of authoring due to font localization.
The use cases include:
Users should be able to express advanced artistic/animated text rendered into canvas, in a wide array of fonts and languages, comparable to SVG text support.
What solutions exist today?
The existing TextMetrics APIs give an approximation of the bounding box for a string. This can be used in Javascript to implement the necessary functionality for editing, to a first approximation. Bounds are approximate, however. Furthermore, determining the caret position within a text string corresponding to a hit point requires binary search or similar over the set of strings. i.e am I in the left or right half of the string, recursively requiring log(n) TextMetrics construction and measurement calls. Each of these is relatively expensive.
There is currently way to know which characters in a string correspond to individual glyphs rendered to screen, short of incorporating complete BIDI and font glyph analysis into you app. Trying to lay out characters along a path, or apply per-glyph styling, impossible without knowledge of which characters combine to form which glyphs.
How would you solve it?
Please see the full explainer, including demos, at https://github.com/Igalia/explainers/blob/main/canvas-formatted-text/text-metrics-additions.md
We propose four new functions on the
TextMetrics
interface:In addition, a new method on
CanvasRenderingContext2D
supports filling grapheme clusters:The
caretPositionFromPoint
method returns the character offset for the character at the givenoffset
distance from the start position of the text run (accounting fortextAlign
andtextBaseline
) with offset always increasingleft to right (so negative offsets are valid). Values to the left or right of the text bounds will return 0 or
num_characters
depending on the writing direction. The functionality is similar but not identical todocument.caretPositionFromPoint
. In particular, there is no need to return the element containing the caret and offsets beyond the boundaries of the string are acceptable.The other functions operate in character ranges and return bounding boxes relative to the text’s origin (i.e.,
textBaseline
/textAlign
is taken into account).getSelectionRects()
returns the set of rectangles that the UA would render as the selection background when a particular character range is selected.getActualBoundingBox()
returns the equivalent toTextMetric.actualBoundingBox
restricted to the given range. That is, the bounding rectangle for the drawing of that range. Notice that this can be (and usually is) different from the selection rect, as the latter is about the flow and advance of the text. A font that is particularly slanted or whose accents go beyond the flow of text will have a different paint bounding box. For example: if you select this: W you may see that the end of the W is outside the selection highlight, which would be covered by the paint (actual bounding box) area.getTextClusters()
provides the ability to render minimal grapheme clusters (in conjunction with a new method for the canvas rendering context, more on that later). That is, for the character range given as in input, it returns the minimal logical units of text, each of which can be rendered, along with their corresponding positional data. The position is calculated with the original anchor point for the text as reference, while thetext_align
andtext_baseline
parameters determine the desired alignment of each cluster.To render these clusters on the screen, a new method for the rendering context is proposed:
fillTextCluster()
. It renders the cluster with thetext_align
andtext_baseline
stored in the object, ignoring the values set in the context. Additionally, to guarantee that the rendered cluster is accurate with the measured text, the rest of the CanvasTextDrawingStyles must be applied as they were when ctx.measureText() was called, regardless of any changes in these values on the context since. Note that to guarantee that the shaping of each cluster is indeed the same as it was when measured, it's necessary to use the whole string as context when rendering each cluster.For
text_align
specifically, the position is calculated in regards of the advance of said grapheme cluster in the text. For example: if thetext_align
passed to the function iscenter
, for the letter T in the string Test, the position returned will be not exactly be in the middle of the T. This is because the advance is reduced by the kerning between the first two letters, making it less than the width of a T rendered on its own.Anything else?
A very minimalist editor built on this functionality is at https://blogs.igalia.com/schenney/html/editing-canvas-demo.html
See https://blogs.igalia.com/schenney/canvas-text-editing/ for details on which browser version and flags are required.
The text was updated successfully, but these errors were encountered: