Skip to content

Commit

Permalink
Separate TextMetrics advances from CharacterRanges (ligatures fixed).
Browse files Browse the repository at this point in the history
Moving code to a separate function to avoid conflicting with svg code
that uses CharacterRanges. Also fixed advances for ligatures and other
edge cases. Similar to text selection approach, for ligatures the
advances per character are distributed evenly along the glyph's
actual width. In the case that one character has multiple glyphs,
the advance for the next character will be the sum of all it's
glyphs' widths.

Bug:853376,277215

Cq-Include-Trybots: luci.chromium.try:linux_layout_tests_layout_ng
Change-Id: I3a69d57dbb9f9c5cbc263da2d91f522f00ae4080
Reviewed-on: https://chromium-review.googlesource.com/1160874
Commit-Queue: David Quiroz Marin <davidqu@chromium.org>
Reviewed-by: Fernando Serboncini <fserb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#583778}
  • Loading branch information
David Quiroz Marin authored and Commit Bot committed Aug 16, 2018
1 parent 4098a12 commit 3c61aca
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
{ text: "ليس في اسمنا", rtl: true },
{ text: "\u202EHello", rtl: true },
{ text: "\u202EHello World", rtl: true },
// TODO(davidqu): Fix the following edge cases:
//{ text: "الله", rtl: true }, // Special ligatures.
//{ text: "🏁", rtl: false }, // One glyph with two "characters".
//{ text: "एक आम भाषा", rtl: false }, // Special post-modifying characters.
//{ text: "a\u0301", rtl: true }, // Combining diacritical marks
{ text: "الله", rtl: true }, // Special ligatures.
{ text: "اين المكتبة؟", rtl: true }, // Special ligatures.
{ text: "🏁🎶🏁", rtl: false }, // One glyph with two "characters".
{ text: "एक आम भाषा", rtl: false }, // Special post-modifying characters.
{ text: "a\u0301", rtl: false }, // Combining diacritical marks
]

function forEachExample(fn) {
Expand Down Expand Up @@ -75,7 +75,7 @@
});
}

function testNonIncreasing(ctx) {
function testNonIncreasingForRtl(ctx) {
forEachExample((ex, align) => {
if (ex.rtl) {
const tm = getTextMetrics(ctx, ex.text, align, ex.rtl ? "rtl" : "ltr");
Expand All @@ -85,9 +85,9 @@
});
}

function testNonDecreasing(ctx) {
function testNonDecreasingForLtr(ctx) {
forEachExample((ex, align) => {
if (ex.ltr) {
if (! ex.rtl) {
const tm = getTextMetrics(ctx, ex.text, align, ex.rtl ? "rtl" : "ltr");
assert_true(isNonDecreasing(tm.advances),
"LTR advances must be non-decreasing (" + ex.text + ")");
Expand All @@ -106,7 +106,8 @@
function testAdvances(ctx) {
testEmptyStringReturnsEmptyAdvances(ctx);
testAllPositive(ctx);
testNonDecreasing(ctx);
testNonIncreasingForRtl(ctx);
testNonDecreasingForLtr(ctx);
testLastAdvanceLessThanWith(ctx);
}

Expand Down
6 changes: 1 addition & 5 deletions third_party/blink/renderer/core/html/canvas/text_metrics.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,7 @@ 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;
}
advances_ = font.IndividualCharacterAdvances(text_run);

// x direction
width_ = bbox.Width();
Expand Down
6 changes: 6 additions & 0 deletions third_party/blink/renderer/platform/fonts/font.cc
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,12 @@ Vector<CharacterRange> Font::IndividualCharacterRanges(
return ranges;
}

Vector<double> Font::IndividualCharacterAdvances(const TextRun& run) const {
FontCachePurgePreventer purge_preventer;
CachingWordShaper shaper(*this);
return shaper.IndividualCharacterAdvances(run);
}

void Font::ExpandRangeToIncludePartialGlyphs(const TextRun& text_run,
int* from,
int* to) const {
Expand Down
5 changes: 5 additions & 0 deletions third_party/blink/renderer/platform/fonts/font.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ class PLATFORM_EXPORT Font {
unsigned to) const;
Vector<CharacterRange> IndividualCharacterRanges(const TextRun&) const;

// Returns a vector of same size as TextRun.length() with advances measured
// in pixels from the left bounding box of the full TextRun to the left bound
// of the glyph rendered by each character. Values should always be positive.
Vector<double> IndividualCharacterAdvances(const TextRun&) const;

void ExpandRangeToIncludePartialGlyphs(const TextRun&,
int* from,
int* to) const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ Vector<CharacterRange> CachingWordShaper::IndividualCharacterRanges(
const TextRun& run) {
ShapeResultBuffer buffer;
float total_width = ShapeResultsForRun(GetShapeCache(), &font_, run, &buffer);

auto ranges = buffer.IndividualCharacterRanges(run.Direction(), total_width);
// The shaper can fail to return glyph metrics for all characters (see
// crbug.com/613915 and crbug.com/615661) so add empty ranges to ensure all
Expand All @@ -121,6 +120,14 @@ Vector<CharacterRange> CachingWordShaper::IndividualCharacterRanges(
return ranges;
}

Vector<double> CachingWordShaper::IndividualCharacterAdvances(
const TextRun& run) {
ShapeResultBuffer buffer;
float total_width = ShapeResultsForRun(GetShapeCache(), &font_, run, &buffer);
return buffer.IndividualCharacterAdvances(run.ToStringView(), run.Direction(),
total_width);
}

Vector<ShapeResult::RunFontData> CachingWordShaper::GetRunFontData(
const TextRun& run) const {
ShapeResultBuffer buffer;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class PLATFORM_EXPORT CachingWordShaper final {
void FillResultBuffer(const TextRunPaintInfo&, ShapeResultBuffer*);
CharacterRange GetCharacterRange(const TextRun&, unsigned from, unsigned to);
Vector<CharacterRange> IndividualCharacterRanges(const TextRun&);
Vector<double> IndividualCharacterAdvances(const TextRun&);

Vector<ShapeResult::RunFontData> GetRunFontData(const TextRun&) const;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,86 @@ Vector<CharacterRange> ShapeResultBuffer::IndividualCharacterRanges(
return ranges;
}

void ShapeResultBuffer::AddRunInfoAdvances(const ShapeResult::RunInfo& run_info,
float offset,
Vector<double>& advances) {
const unsigned num_glyphs = run_info.glyph_data_.size();
const unsigned num_chars = run_info.num_characters_;

if (run_info.Rtl())
offset += run_info.width_;

double current_width = 0;
for (unsigned glyph_id = 0; glyph_id < num_glyphs; glyph_id++) {
unsigned gid = run_info.Rtl() ? num_glyphs - glyph_id - 1 : glyph_id;
unsigned next_gid =
run_info.Rtl() ? num_glyphs - glyph_id - 2 : glyph_id + 1;
const HarfBuzzRunGlyphData& glyph = run_info.glyph_data_[gid];

unsigned char_id = glyph.character_index;
unsigned next_char_id =
(glyph_id + 1 == num_glyphs)
? num_chars
: run_info.glyph_data_[next_gid].character_index;

current_width += glyph.advance;

if (char_id == next_char_id)
continue;

unsigned num_graphemes = run_info.NumGraphemes(char_id, next_char_id);

for (unsigned i = char_id; i < next_char_id; i++) {
if (run_info.Rtl()) {
advances.push_back(offset - (current_width / num_graphemes));
} else {
advances.push_back(offset);
}

if (num_graphemes == next_char_id - char_id) {
offset += (current_width / num_graphemes) * (run_info.Rtl() ? -1 : 1);
}
}

if (num_graphemes != next_char_id - char_id) {
offset += current_width * (run_info.Rtl() ? -1 : 1);
}

current_width = 0;
}
}

Vector<double> ShapeResultBuffer::IndividualCharacterAdvances(
const StringView& text,
TextDirection direction,
float total_width) const {
unsigned character_offset = 0;
Vector<double> advances;
float current_x = direction == TextDirection::kRtl ? total_width : 0;

for (const scoped_refptr<const ShapeResult> result : results_) {
unsigned run_count = result->runs_.size();

result->EnsureGraphemes(
StringView(text, character_offset, result->NumCharacters()));

if (result->Rtl()) {
for (int index = run_count - 1; index >= 0; index--) {
current_x -= result->runs_[index]->width_;
AddRunInfoAdvances(*result->runs_[index], current_x, advances);
}
} else {
for (unsigned index = 0; index < run_count; index++) {
AddRunInfoAdvances(*result->runs_[index], current_x, advances);
current_x += result->runs_[index]->width_;
}
}

character_offset += result->NumCharacters();
}
return advances;
}

int ShapeResultBuffer::OffsetForPosition(
const TextRun& run,
float target_x,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ class PLATFORM_EXPORT ShapeResultBuffer {
unsigned to) const;
Vector<CharacterRange> IndividualCharacterRanges(TextDirection,
float total_width) const;
Vector<double> IndividualCharacterAdvances(const StringView&,
TextDirection,
float total_width) const;

static CharacterRange GetCharacterRange(scoped_refptr<const ShapeResult>,
const StringView& text,
Expand All @@ -68,6 +71,9 @@ class PLATFORM_EXPORT ShapeResultBuffer {
unsigned from,
unsigned to);

static void AddRunInfoAdvances(const ShapeResult::RunInfo& run_info,
float offset,
Vector<double>& advances);
static void AddRunInfoRanges(const ShapeResult::RunInfo&,
float offset,
Vector<CharacterRange>&);
Expand Down

0 comments on commit 3c61aca

Please sign in to comment.