Repository navigation
Conversation
7811917 to
d17b4dd
Compare
Add an explainer for WebKit proposal for Canvas Text shaping. Another proposal has been discussed here[1]. [1] https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md * CanvasTextShaping/CanvasTextShaping.md: Added. * CanvasTextShaping/drawing-colored-clusters-display.png: Added. * CanvasTextShaping/drawing-colored-justified-words-display.png: Added. * CanvasTextShaping/drawing-text.png: Added. * CanvasTextShaping/drawing-text-with-styles.png: Added. * CanvasTextShaping/drawing-text-with-styles-display.png: Added. * CanvasTextShaping/drawing-wrapped-justified-text-with-styled-display.png: Added.
d17b4dd to
ca43216
Compare
Add an explainer for WebKit proposal for Canvas Text shaping. Another proposal from Google has been discussed here[1]. [1] https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md Reviewed by NOBODY (OOPS!). Explanation of why this fixes the bug (OOPS!). * CanvasTextShaping/CanvasTextShaping.md: Added. * CanvasTextShaping/drawing-colored-clusters-display.png: Added. * CanvasTextShaping/drawing-colored-justified-words-display.png: Added. * CanvasTextShaping/drawing-text.png: Added. * CanvasTextShaping/drawing-text-with-styles.png: Added. * CanvasTextShaping/drawing-text-with-styles-display.png: Added. * CanvasTextShaping/drawing-wrapped-justified-text-with-styled-display.png: Added.
| } | ||
| ``` | ||
|
|
||
| ## Examples |
There was a problem hiding this comment.
Could you add an example of text-on-path? I'm having trouble seeing how you would draw a sequence of GlyphRun along a non-linear path.
There was a problem hiding this comment.
It can be something like this:
function fillClustersOnCircle(ctx, text, centerX, centerY, radius)
{
const clusters = ctx.splitText(text);
const clustersWidth = clusters.reduce((width, cluster) => width + cluster.textMetrics().width, 0);
let distance = 0;
for (const cluster of clusters) {
const ratio = distance / clustersWidth;
const angle = 2 * Math.PI * ratio;
const x = radius * Math.cos(angle) + centerX;
const y = radius * Math.sin(angle) + centerY;
ctx.save();
ctx.translate(x, y);
ctx.rotate(angle + Math.PI / 2);
ctx.fillGlyphRun(cluster, 0, 0);
ctx.restore();
distance += cluster.textMetrics().width;
}
}
There was a problem hiding this comment.
Having fillText(), fillGlyphRuns() and fillGlyphRun() give freedom to do different scenarios:
fillText()can be used when no special breaking or styling is required. All text can be displayed in one call.fillGlyphRuns()can be used when special breaking (like clusters or word boundaries) or styling is required. But the text is displayed in the increasing horizontal direction of the context.fillGlyphRun()can be used similar tofillGlyphRuns()but the display position is customizable.
Add an explainer for WebKit proposal for Canvas Text shaping. Another proposal from Google has been discussed here[1]. [1] https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md Reviewed by NOBODY (OOPS!). * CanvasTextShaping/CanvasTextShaping.md: Added. * CanvasTextShaping/drawing-clusters-on-circle-display.png: Added. * CanvasTextShaping/drawing-colored-clusters-display.png * CanvasTextShaping/drawing-colored-justified-words-display.png * CanvasTextShaping/drawing-text-with-styles-display.png * CanvasTextShaping/drawing-text-with-styles.png * CanvasTextShaping/drawing-text.png * CanvasTextShaping/drawing-wrapped-justified-text-with-styled-display.png
| // GlyphRun drawing | ||
| // --------------------------------------------------------- | ||
| double fillGlyphRun(GlyphRun run, double x, double y); | ||
| double strokeGlyphRun(GlyphRun run, double x, double y); |
There was a problem hiding this comment.
What is returned from these methods?
There was a problem hiding this comment.
They return the width of the drawn GlyphRun. So this code
for (const glyphRun of glyphRuns)
x += ctx.fillGlyphRun(glyphRun, x, y);
is equivalent to this:
for (const glyphRun of glyphRuns) {
ctx.fillGlyphRun(glyphRun, x, y);
x += glyphRun.textMetrics().width;
}
| ``` | ||
| interface TextRun { | ||
| readonly attribute unsigned long start; | ||
| readonly attribute DOMString text; |
There was a problem hiding this comment.
Is there a strong case for providing a text substring instead of an end index? The downsides I see:
- API friction: I posit most clients want an
endindex, not the substring content. Looking at the code samples, the only uses fortextare to get its length to calculate the end index. - Allocation overhead: implementers may need to allocate separate storage for each substring (as opposed to cheap views). This could introduce measurable overhead for tight shaping/cluster loops.
- Ambiguity: at fist glance, it's unclear what the relationship is between
textand the input string, or which onestartapplies to.start/endare self-documenting. - If needed, clients can always track the input themselves, and substring as needed based on
start/endindices.
| readonly attribute DOMString text; | ||
| }; | ||
|
|
||
| Intl.Segmenter.breakText(string) |
There was a problem hiding this comment.
Intl.Segmenter already offers a well defined API for grapheme/word/sentence segmentation. Why not extend that API for line granularity instead of introducing a new entry point?
Also, segmentation most likely needs to be performed via instance methods, to observe the segmenter instance properties (mainly the locale).
|
|
||
| ``` | ||
| interface GlyphRun { | ||
| readonly attribute TextRun textRun; |
There was a problem hiding this comment.
Assuming TextRun is updated to store start/end indices, it would make sense to expose those directly in GlyphRun also, instead of adding a limited-value wrapper. That would avoid allocating TextRun objects for clients that don't interact with the higher-level functionality (only care about basic text shaping and glyph clusters).
| // Text APIs | ||
| // --------------------------------------------------------- | ||
| // Text analysis - `segments` is used to apply custom slicing | ||
| sequence<GlyphRun> shapeText(DOMString text, optional sequence<unsigned long> segments, optional StyleCallback? styleCallback); |
There was a problem hiding this comment.
Have you considered returning a wrapper object (e.g. ShapedText) instead of a plain GlyphRun sequence? I can think of a couple of reasons for preferring that:
- Implementation-wise, the result must retain some context that captures the shaped text. If we return a glyph run sequence, then each glyph run must hold a reference to this context; if we return a wrapper, only the wrapper needs to retain that context reference (cutting down glyph run's size).
- A wrapper offers a natural anchor point for related APIs (hit testing, selection rects, etc), so we could avoid adding those to the crowded rendering context.
| ``` | ||
| interface GlyphRun { | ||
| readonly attribute TextRun textRun; | ||
|
|
There was a problem hiding this comment.
We should also surface glyph run/cluster positioning info (x, y) such that clients can access that directly instead of being forced to use an accumulator loop. I believe that's needed for random-order processing.
It might also be worthwhile exposing the advance (width) at this level, to avoid per-cluster calls to textMetrics() in hot loops.
Add an explainer for WebKit proposal for Canvas Text shaping. Another proposal has been discussed here[1].
[1] https://github.com/fserb/canvas2D/blob/master/spec/enhanced-textmetrics.md