Commit 65d210ea authored by David Quiroz Marin's avatar David Quiroz Marin Committed by Commit Bot

Expose glyph advances in TextMetrics

Adding a new attribute to the TextMetrics object to expose glyph
advances for a given text. This will be a list of pixel widths for each
glyph in the text measured in pixels from the text alignment origin in
the text direction (rtl or ltr).

Added some basic test and commented some edge cases that we still
need to resolve.

Bug: 853376, 277215
Change-Id: I7df30ce493c843032d974ca79ca793c89b318d82
Reviewed-on: https://chromium-review.googlesource.com/1134202Reviewed-by: default avatarJustin Novosad <junov@chromium.org>
Reviewed-by: default avatarFernando Serboncini <fserb@chromium.org>
Commit-Queue: David Quiroz Marin <davidqu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#575470}
parent 0948203d
<!DOCTYPE HTML>
<head>
<meta charset="UTF-8">
<style>
@font-face {
font-family: Libertine;
src: url('../../third_party/Libertine/LinLibertine_R.woff');
}
</style>
</head>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
const kAligns = [ "left", "right", "center", "start", "end" ];
const kTexts = [
{ text: "Hello", rtl: false },
{ text: "傳統是假的", rtl: false },
{ text: "フェミニズム", rtl: false },
{ text: "ليس", rtl: true },
{ text: "\u202EHello", rtl: true },
// TODO(davidqu): Fix the following edge cases:
//{ text: "الله‎", rtl: true }, // Special ligatures.
//{ text: "ليس في اسمنا", rtl: true }, // RTL only works for single "words".
//{ text: "\u202EHello World", rtl: true },
//{ text: "🏁", rtl: false }, // One glyph with two "characters".
//{ text: "एक आम भाषा", rtl: false }, // Special post-modifying characters.
//{ text: "a\u0301", rtl: true }, // Combining diacritical marks
]
function forEachExample(fn) {
for (const ex of kTexts) {
for (const align of kAligns) {
fn(ex, align);
}
}
}
function isNonDecreasing(array) {
for (var i = 1; i < array.length; i++) {
if (array[i] < array[i-1]) {
return false;
}
}
return true;
}
function getTextMetrics(ctx, text, align="left", direction="ltr") {
ctx.font = '25px Libertine';
ctx.textAlign = align;
ctx.direction = direction;
return ctx.measureText(text);
}
function testEmptyStringReturnsEmptyAdvances(ctx) {
const tm = getTextMetrics(ctx, "");
assert_array_equals(tm.advances, [], "Empty string must return empty advances");
}
function testAllPositive(ctx) {
forEachExample((ex, align) => {
const tm = getTextMetrics(ctx, ex.text, align, ex.rtl ? "rtl" : "ltr", );
assert_true(tm.advances.every(function isPositive(i) {return i >= 0; }),
"Advances must be all positive (" + ex.text + ")");
});
}
function testNonDecreasing(ctx) {
forEachExample((ex, align) => {
const tm = getTextMetrics(ctx, ex.text, align, ex.rtl ? "rtl" : "ltr");
assert_true(isNonDecreasing(tm.advances),
"Advances must be non-decreasing (" + ex.text + ")");
});
}
function testLastAdvanceLessThanWith(ctx) {
forEachExample((ex, align) => {
const tm = getTextMetrics(ctx, ex.text, align, ex.rtl ? "rtl" : "ltr");
assert_less_than(tm.advances.slice(-1)[0], tm.width,
"Last advance must be strictly less than total width (" + ex.text + ")");
});
}
function testAdvances(ctx) {
testEmptyStringReturnsEmptyAdvances(ctx);
testAllPositive(ctx);
testNonDecreasing(ctx);
testLastAdvanceLessThanWith(ctx);
}
async_test(t => {
var canvas = document.createElement('canvas');
canvas.width = 100;
canvas.height = 100;
var ctx = canvas.getContext('2d');
// Kick off loading of the font
ctx.font = '50px Libertine';
ctx.fillText(" ", 0, 0);
document.fonts.ready.then(t.step_func_done(testAdvances(ctx)));
}, "Test TextMetrics advances.");
</script>
......@@ -7097,6 +7097,7 @@ interface TextMetrics
getter actualBoundingBoxDescent
getter actualBoundingBoxLeft
getter actualBoundingBoxRight
getter advances
getter alphabeticBaseline
getter emHeightAscent
getter emHeightDescent
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
#include "third_party/blink/renderer/core/html/canvas/text_metrics.h"
#include "third_party/blink/renderer/platform/fonts/character_range.h"
namespace blink {
......@@ -64,6 +65,12 @@ void TextMetrics::Update(const Font& font,
FloatRect bbox = font.BoundingBox(text_run);
const FontMetrics& font_metrics = font_data->GetFontMetrics();
Vector<CharacterRange> ranges = font.IndividualCharacterRanges(text_run);
advances_.resize(ranges.size());
for (unsigned i = 0; i < ranges.size(); i++) {
advances_[i] = ranges[i].start;
}
// x direction
width_ = bbox.Width();
......
......@@ -51,6 +51,7 @@ class CORE_EXPORT TextMetrics final : public ScriptWrappable {
}
double width() const { return width_; }
const Vector<double>& advances() const { return advances_; }
double actualBoundingBoxLeft() const { return actual_bounding_box_left_; }
double actualBoundingBoxRight() const { return actual_bounding_box_right_; }
double fontBoundingBoxAscent() const { return font_bounding_box_ascent_; }
......@@ -77,6 +78,7 @@ class CORE_EXPORT TextMetrics final : public ScriptWrappable {
// x-direction
double width_ = 0.0;
Vector<double> advances_;
double actual_bounding_box_left_ = 0.0;
double actual_bounding_box_right_ = 0.0;
......
......@@ -29,6 +29,7 @@
interface TextMetrics {
// x-direction
readonly attribute float width; // advance width
[RuntimeEnabled=ExtendedTextMetrics] readonly attribute FrozenArray<double> advances;
[RuntimeEnabled=ExtendedTextMetrics] readonly attribute double actualBoundingBoxLeft;
[RuntimeEnabled=ExtendedTextMetrics] readonly attribute double actualBoundingBoxRight;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment