Skip to content

Commit

Permalink
Implement ctx.fillTextCluster()
Browse files Browse the repository at this point in the history
This change creates a new API method for the CanvasRenderingContext2d
that enables the rendering of the TextCluster objects returned by the
API implemented on the previous CL in the stack.

The new method receives a TextCluster object, an x value, and a y value.
This coordinates represent by how much to shift the rendered text from
the x and y attributes of the TextCluster object. In practice, this
means that calling fillText() from a point p will have the same result
as measuring that text and calling fillTextCluster() on each cluster
passing the same point as parameter. This is true provided that the
textAlign and textBaseline values for the rendering context are set to
the same values as passed to TextMetrics::getTextClustersForRange().

The implementation required a new Font::SubRunWidth to calculate the
width and the bounds of the TextCluster object to be rendered. It runs
the bidi algorithm to get the visual runs, and once it finds the correct
one, uses GetCharacterRange() to obtain these values.

The new paths are only used when rendering a TextCluster to avoid
potential performance issues for the existing ctx.fillText() and
ctx.StrokeText() methods.

The new API was enabled under the `ExtendedTextMetrics` flag.

Bug: 341213359
Change-Id: I2f58b3ef958704910aedaa450923ed3751a77bdf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5854270
Reviewed-by: Jean-Philippe Gravel <jpgravel@chromium.org>
Commit-Queue: Andres Ricardo Perez <andresrperez@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1370047}
  • Loading branch information
AndresRPerez12 authored and chromium-wpt-export-bot committed Oct 17, 2024
1 parent b9d1a97 commit 0af1c92
Show file tree
Hide file tree
Showing 19 changed files with 1,616 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<meta charset="UTF-8">
<title>Canvas test: 2d.text.measure.text-clusters-range.tentative</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/html/canvas/resources/canvas-tests.js"></script>
<link rel="stylesheet" href="/html/canvas/resources/canvas-tests.css">
<style>
@font-face {
font-family: CanvasTest;
src: url("/fonts/CanvasTest.ttf");
}
</style>
<body class="show_output">

<h1>2d.text.measure.text-clusters-range.tentative</h1>
<p class="desc">Test that getTextClusters() and fillTextCluster() correctly render different ranges of the input text.</p>


<span style="font-family: CanvasTest; position: absolute; visibility: hidden">A</span>
<p class="output">Actual output:</p>
<canvas id="c" class="output" width="400" height="300"><p class="fallback">FAIL (fallback content)</p></canvas>

<ul id="d"></ul>
<script>
promise_test(async t => {

var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');

// Renders all the clusters in the list from position (x, y).
function renderClusters(clusters, x, y) {
for (const cluster of clusters) {
ctx.fillTextCluster(cluster, x, y);
}
}

await document.fonts.ready;

ctx.font = '50px CanvasTest';
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
const text = 'EEEEE';
let tm = ctx.measureText(text);

// Background color.
ctx.fillStyle = '#f00';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.fillStyle = '#0f0';

// Without the first character.
renderClusters(tm.getTextClusters(1, 5), 0, 0);
_assertPixelApprox(canvas, 5,5, 255,0,0,255, 2);
_assertPixelApprox(canvas, 55,5, 0,255,0,255, 2);
_assertPixelApprox(canvas, 105,5, 0,255,0,255, 2);
_assertPixelApprox(canvas, 155,5, 0,255,0,255, 2);
_assertPixelApprox(canvas, 205,5, 0,255,0,255, 2);
// Without the last character.
renderClusters(tm.getTextClusters(0, 4), 0, 100);
_assertPixelApprox(canvas, 5,105, 0,255,0,255, 2);
_assertPixelApprox(canvas, 55,105, 0,255,0,255, 2);
_assertPixelApprox(canvas, 105,105, 0,255,0,255, 2);
_assertPixelApprox(canvas, 155,105, 0,255,0,255, 2);
_assertPixelApprox(canvas, 205,105, 255,0,0,255, 2);
// Only the middle character.
renderClusters(tm.getTextClusters(2, 3), 0, 150);
_assertPixelApprox(canvas, 5,155, 255,0,0,255, 2);
_assertPixelApprox(canvas, 55,155, 255,0,0,255, 2);
_assertPixelApprox(canvas, 105,155, 0,255,0,255, 2);
_assertPixelApprox(canvas, 155,155, 255,0,0,255, 2);
_assertPixelApprox(canvas, 205,155, 255,0,0,255, 2);

}, "Test that getTextClusters() and fillTextCluster() correctly render different ranges of the input text.");
</script>

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<meta charset="UTF-8">
<title>Canvas test: 2d.text.measure.text-clusters-rendering-align.tentative</title>
<h1 style="font-size: 20px;">2d.text.measure.text-clusters-rendering-align.tentative</h1>
<p class="desc">Test that fillTextCluster() correctly positions the text, taking into account the textAlign from the context at the time the text was measured.</p>

<div style="display: grid; grid-gap: 4px;
grid-template-columns: repeat(3, max-content);
font-size: 13px; text-align: center;">
<span>
<div>ctx_align_left</div>
<canvas id="canvas0" width="250" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas0");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = canvas.width / 2;
const y = canvas.height / 2;

ctx.textAlign = 'left';

// Rendering all clusters with the same (x, y) parameters must be
// equivalent to a fillText() call at (x, y).
ctx.fillText(text, x, y);
</script>
</span>

<span>
<div>ctx_align_center</div>
<canvas id="canvas1" width="250" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas1");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = canvas.width / 2;
const y = canvas.height / 2;

ctx.textAlign = 'center';

// Rendering all clusters with the same (x, y) parameters must be
// equivalent to a fillText() call at (x, y).
ctx.fillText(text, x, y);
</script>
</span>

<span>
<div>ctx_align_right</div>
<canvas id="canvas2" width="250" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas2");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = canvas.width / 2;
const y = canvas.height / 2;

ctx.textAlign = 'right';

// Rendering all clusters with the same (x, y) parameters must be
// equivalent to a fillText() call at (x, y).
ctx.fillText(text, x, y);
</script>
</span>

</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<meta charset="UTF-8">
<link rel="match" href="2d.text.measure.text-clusters-rendering-align.tentative-expected.html">
<title>Canvas test: 2d.text.measure.text-clusters-rendering-align.tentative</title>
<h1 style="font-size: 20px;">2d.text.measure.text-clusters-rendering-align.tentative</h1>
<p class="desc">Test that fillTextCluster() correctly positions the text, taking into account the textAlign from the context at the time the text was measured.</p>

<div style="display: grid; grid-gap: 4px;
grid-template-columns: repeat(3, max-content);
font-size: 13px; text-align: center;">
<span>
<div>ctx_align_left</div>
<canvas id="canvas0" width="250" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas0");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = canvas.width / 2;
const y = canvas.height / 2;

ctx.textAlign = 'left';
let tm = ctx.measureText(text);
const clusters = tm.getTextClusters();

// Rendering all clusters with the same (x, y) parameters must be
// equivalent to a fillText() call at (x, y).
for (const cluster of clusters) {
ctx.fillTextCluster(cluster, x, y);
}
</script>
</span>

<span>
<div>ctx_align_center</div>
<canvas id="canvas1" width="250" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas1");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = canvas.width / 2;
const y = canvas.height / 2;

ctx.textAlign = 'center';
let tm = ctx.measureText(text);
const clusters = tm.getTextClusters();

// Rendering all clusters with the same (x, y) parameters must be
// equivalent to a fillText() call at (x, y).
for (const cluster of clusters) {
ctx.fillTextCluster(cluster, x, y);
}
</script>
</span>

<span>
<div>ctx_align_right</div>
<canvas id="canvas2" width="250" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas2");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = canvas.width / 2;
const y = canvas.height / 2;

ctx.textAlign = 'right';
let tm = ctx.measureText(text);
const clusters = tm.getTextClusters();

// Rendering all clusters with the same (x, y) parameters must be
// equivalent to a fillText() call at (x, y).
for (const cluster of clusters) {
ctx.fillTextCluster(cluster, x, y);
}
</script>
</span>

</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!DOCTYPE html>
<!-- DO NOT EDIT! This test has been generated by /html/canvas/tools/gentest.py. -->
<meta charset="UTF-8">
<title>Canvas test: 2d.text.measure.text-clusters-rendering-baseline.tentative</title>
<h1 style="font-size: 20px;">2d.text.measure.text-clusters-rendering-baseline.tentative</h1>
<p class="desc">Test that fillTextCluster() correctly positions the text, taking into account the textBaseline from the context at the time the text was measured.</p>

<div style="display: grid; grid-gap: 4px;
grid-template-columns: repeat(4, max-content);
font-size: 13px; text-align: center;">
<span>
<div>ctx_baseline_top</div>
<canvas id="canvas0" width="180" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas0");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = 20;
const y = canvas.height / 2;

ctx.textBaseline = 'top';
ctx.fillText(text, x, y);
</script>
</span>

<span>
<div>ctx_baseline_middle</div>
<canvas id="canvas1" width="180" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas1");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = 20;
const y = canvas.height / 2;

ctx.textBaseline = 'middle';
ctx.fillText(text, x, y);
</script>
</span>

<span>
<div>ctx_baseline_bottom</div>
<canvas id="canvas2" width="180" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas2");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = 20;
const y = canvas.height / 2;

ctx.textBaseline = 'bottom';
ctx.fillText(text, x, y);
</script>
</span>

<span>
<div>ctx_baseline_alphabetic</div>
<canvas id="canvas3" width="180" height="43" style="outline: 1px solid">
<p class="fallback">FAIL (fallback content)</p>
</canvas>
<script type="module">
const canvas = document.getElementById("canvas3");
const ctx = canvas.getContext('2d');

ctx.font = '20px serif';
const text = 'Test ☺️ א';
const x = 20;
const y = canvas.height / 2;

ctx.textBaseline = 'alphabetic';
ctx.fillText(text, x, y);
</script>
</span>

</div>
Loading

0 comments on commit 0af1c92

Please sign in to comment.