mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
libtxt: handle lines containing both LTR and RTL text runs (#4416)
* Call the ICU bidi API to divide the text into LTR/RTL runs and merge them with the styled runs defined by the application * Maintain a list of glyph positions sorted by the order of the corresponding code units in the text, as well as a list of glyphs sorted by x/y coordinates in the layout
This commit is contained in:
parent
7e4df308a7
commit
affa4e7c46
@ -64,11 +64,11 @@ void ParagraphImplTxt::paint(Canvas* canvas, double x, double y) {
|
||||
std::vector<TextBox> ParagraphImplTxt::getRectsForRange(unsigned start,
|
||||
unsigned end) {
|
||||
std::vector<TextBox> result;
|
||||
std::vector<SkRect> rects = m_paragraph->GetRectsForRange(start, end);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
result.push_back(TextBox(
|
||||
rects[i], static_cast<TextDirection>(
|
||||
m_paragraph->GetParagraphStyle().text_direction)));
|
||||
std::vector<txt::Paragraph::TextBox> boxes =
|
||||
m_paragraph->GetRectsForRange(start, end);
|
||||
for (const txt::Paragraph::TextBox& box : boxes) {
|
||||
result.emplace_back(box.rect,
|
||||
static_cast<blink::TextDirection>(box.direction));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@ -76,14 +76,14 @@ std::vector<TextBox> ParagraphImplTxt::getRectsForRange(unsigned start,
|
||||
Dart_Handle ParagraphImplTxt::getPositionForOffset(double dx, double dy) {
|
||||
Dart_Handle result = Dart_NewList(2);
|
||||
txt::Paragraph::PositionWithAffinity pos =
|
||||
m_paragraph->GetGlyphPositionAtCoordinate(dx, dy, true);
|
||||
m_paragraph->GetGlyphPositionAtCoordinate(dx, dy);
|
||||
Dart_ListSetAt(result, 0, ToDart(pos.position));
|
||||
Dart_ListSetAt(result, 1, ToDart(static_cast<int>(pos.affinity)));
|
||||
return result;
|
||||
}
|
||||
|
||||
Dart_Handle ParagraphImplTxt::getWordBoundary(unsigned offset) {
|
||||
txt::Paragraph::Range point = m_paragraph->GetWordBoundary(offset);
|
||||
txt::Paragraph::Range<size_t> point = m_paragraph->GetWordBoundary(offset);
|
||||
Dart_Handle result = Dart_NewList(2);
|
||||
Dart_ListSetAt(result, 0, ToDart(point.start));
|
||||
Dart_ListSetAt(result, 1, ToDart(point.end));
|
||||
|
||||
384
third_party/txt/src/txt/paragraph.cc
vendored
384
third_party/txt/src/txt/paragraph.cc
vendored
@ -19,6 +19,7 @@
|
||||
#include <hb.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -31,6 +32,7 @@
|
||||
#include "minikin/LayoutUtils.h"
|
||||
#include "minikin/LineBreaker.h"
|
||||
#include "minikin/MinikinFont.h"
|
||||
#include "third_party/icu/source/common/unicode/ubidi.h"
|
||||
#include "third_party/skia/include/core/SkCanvas.h"
|
||||
#include "third_party/skia/include/core/SkPaint.h"
|
||||
#include "third_party/skia/include/core/SkTextBlob.h"
|
||||
@ -120,7 +122,7 @@ void GetPaint(const TextStyle& style, SkPaint* paint) {
|
||||
void FindWords(const std::vector<uint16_t>& text,
|
||||
size_t start,
|
||||
size_t end,
|
||||
std::vector<Paragraph::Range>* words) {
|
||||
std::vector<Paragraph::Range<size_t>>* words) {
|
||||
bool in_word = false;
|
||||
size_t word_start;
|
||||
for (size_t i = start; i < end; ++i) {
|
||||
@ -141,24 +143,26 @@ void FindWords(const std::vector<uint16_t>& text,
|
||||
|
||||
static const float kDoubleDecorationSpacing = 3.0f;
|
||||
|
||||
Paragraph::GlyphPosition::GlyphPosition(double x_start,
|
||||
double x_advance,
|
||||
size_t code_unit_index,
|
||||
size_t code_unit_width)
|
||||
: code_units(code_unit_index, code_unit_index + code_unit_width),
|
||||
x_pos(x_start, x_start + x_advance) {}
|
||||
|
||||
Paragraph::GlyphLine::GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu)
|
||||
: positions(std::move(p)), total_code_units(tcu) {}
|
||||
|
||||
const Paragraph::GlyphPosition& Paragraph::GlyphLine::GetGlyphPosition(
|
||||
size_t pos) const {
|
||||
FXL_DCHECK(pos < total_code_units);
|
||||
if (positions.size() == total_code_units)
|
||||
return positions[pos];
|
||||
|
||||
size_t unit_count = 0;
|
||||
for (const Paragraph::GlyphPosition& gp : positions) {
|
||||
if (pos < unit_count + gp.code_units)
|
||||
return gp;
|
||||
unit_count += gp.code_units;
|
||||
}
|
||||
|
||||
return positions.back();
|
||||
}
|
||||
Paragraph::CodeUnitRun::CodeUnitRun(std::vector<GlyphPosition>&& p,
|
||||
Range<size_t> cu,
|
||||
Range<double> x,
|
||||
size_t line,
|
||||
TextDirection dir)
|
||||
: positions(std::move(p)),
|
||||
code_units(cu),
|
||||
x_pos(x),
|
||||
line_number(line),
|
||||
direction(dir) {}
|
||||
|
||||
Paragraph::Paragraph() {
|
||||
breaker_.setLocale(icu::Locale(), nullptr);
|
||||
@ -251,6 +255,84 @@ bool Paragraph::ComputeLineBreaks() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Paragraph::ComputeBidiRuns() {
|
||||
bidi_runs_.clear();
|
||||
|
||||
auto ubidi_closer = [](UBiDi* b) { ubidi_close(b); };
|
||||
std::unique_ptr<UBiDi, decltype(ubidi_closer)> bidi(ubidi_open(),
|
||||
ubidi_closer);
|
||||
if (!bidi)
|
||||
return false;
|
||||
|
||||
UBiDiLevel paraLevel = (paragraph_style_.text_direction == TextDirection::rtl)
|
||||
? UBIDI_DEFAULT_RTL
|
||||
: UBIDI_DEFAULT_LTR;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
ubidi_setPara(bidi.get(), text_.data(), text_.size(), paraLevel, nullptr,
|
||||
&status);
|
||||
if (!U_SUCCESS(status))
|
||||
return false;
|
||||
|
||||
int32_t bidi_run_count = ubidi_countRuns(bidi.get(), &status);
|
||||
if (!U_SUCCESS(status))
|
||||
return false;
|
||||
|
||||
// Build a map of styled runs indexed by start position.
|
||||
std::map<size_t, StyledRuns::Run> styled_run_map;
|
||||
for (size_t i = 0; i < runs_.size(); ++i) {
|
||||
StyledRuns::Run run = runs_.GetRun(i);
|
||||
styled_run_map.emplace(std::make_pair(run.start, run));
|
||||
}
|
||||
|
||||
for (int32_t bidi_run_index = 0; bidi_run_index < bidi_run_count;
|
||||
++bidi_run_index) {
|
||||
int32_t bidi_run_start, bidi_run_length;
|
||||
UBiDiDirection direction = ubidi_getVisualRun(
|
||||
bidi.get(), bidi_run_index, &bidi_run_start, &bidi_run_length);
|
||||
if (!U_SUCCESS(status))
|
||||
return false;
|
||||
|
||||
// Exclude the leading bidi control character if present.
|
||||
UChar32 first_char;
|
||||
U16_GET(text_.data(), 0, bidi_run_start, static_cast<int>(text_.size()),
|
||||
first_char);
|
||||
if (u_hasBinaryProperty(first_char, UCHAR_BIDI_CONTROL)) {
|
||||
bidi_run_start++;
|
||||
bidi_run_length--;
|
||||
}
|
||||
if (bidi_run_length == 0)
|
||||
continue;
|
||||
|
||||
// Exclude the trailing bidi control character if present.
|
||||
UChar32 last_char;
|
||||
U16_GET(text_.data(), 0, bidi_run_start + bidi_run_length - 1,
|
||||
static_cast<int>(text_.size()), last_char);
|
||||
if (u_hasBinaryProperty(last_char, UCHAR_BIDI_CONTROL)) {
|
||||
bidi_run_length--;
|
||||
}
|
||||
if (bidi_run_length == 0)
|
||||
continue;
|
||||
|
||||
size_t bidi_run_end = bidi_run_start + bidi_run_length;
|
||||
TextDirection text_direction =
|
||||
direction == UBIDI_RTL ? TextDirection::rtl : TextDirection::ltr;
|
||||
|
||||
// Break this bidi run into chunks based on text style.
|
||||
size_t chunk_start = bidi_run_start;
|
||||
while (chunk_start < bidi_run_end) {
|
||||
auto styled_run_iter = styled_run_map.upper_bound(chunk_start);
|
||||
styled_run_iter--;
|
||||
const StyledRuns::Run& styled_run = styled_run_iter->second;
|
||||
size_t chunk_end = std::min(bidi_run_end, styled_run.end);
|
||||
bidi_runs_.emplace_back(chunk_start, chunk_end, text_direction,
|
||||
styled_run.style);
|
||||
chunk_start = chunk_end;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Paragraph::Layout(double width, bool force) {
|
||||
// Do not allow calling layout multiple times without changing anything.
|
||||
if (!needs_layout_ && width == width_ && !force) {
|
||||
@ -263,6 +345,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
if (!ComputeLineBreaks()) {
|
||||
return;
|
||||
}
|
||||
ComputeBidiRuns();
|
||||
|
||||
SkPaint paint;
|
||||
paint.setAntiAlias(true);
|
||||
@ -273,10 +356,10 @@ void Paragraph::Layout(double width, bool force) {
|
||||
records_.clear();
|
||||
line_heights_.clear();
|
||||
glyph_lines_.clear();
|
||||
code_unit_runs_.clear();
|
||||
|
||||
minikin::Layout layout;
|
||||
SkTextBlobBuilder builder;
|
||||
size_t run_index = 0;
|
||||
double y_offset = 0;
|
||||
double prev_max_descent = 0;
|
||||
double max_word_width = 0;
|
||||
@ -288,7 +371,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
const LineRange& line_range = line_ranges_[line_number];
|
||||
|
||||
// Break the line into words if justification should be applied.
|
||||
std::vector<Range> words;
|
||||
std::vector<Range<size_t>> words;
|
||||
double word_gap_width = 0;
|
||||
size_t word_index = 0;
|
||||
bool justify_line = (paragraph_style_.text_align == TextAlign::justify &&
|
||||
@ -303,23 +386,21 @@ void Paragraph::Layout(double width, bool force) {
|
||||
}
|
||||
|
||||
// Find the runs comprising this line.
|
||||
std::vector<StyledRuns::Run> line_runs;
|
||||
while (run_index < runs_.size()) {
|
||||
StyledRuns::Run run = runs_.GetRun(run_index);
|
||||
if (run.start >= line_range.end)
|
||||
break;
|
||||
line_runs.push_back(run);
|
||||
if (run.end > line_range.end)
|
||||
break;
|
||||
run_index++;
|
||||
std::vector<BidiRun> line_runs;
|
||||
for (const BidiRun& bidi_run : bidi_runs_) {
|
||||
if (bidi_run.start < line_range.end && bidi_run.end > line_range.start) {
|
||||
line_runs.emplace_back(std::max(bidi_run.start, line_range.start),
|
||||
std::min(bidi_run.end, line_range.end),
|
||||
bidi_run.direction, bidi_run.style);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<GlyphPosition> glyph_positions;
|
||||
std::vector<GlyphPosition> line_glyph_positions;
|
||||
double run_x_offset = GetLineXOffset(line_number);
|
||||
double justify_x_offset = 0;
|
||||
std::vector<PaintRecord> paint_records;
|
||||
|
||||
for (const StyledRuns::Run& run : line_runs) {
|
||||
for (const BidiRun& run : line_runs) {
|
||||
minikin::FontStyle font;
|
||||
minikin::MinikinPaint minikin_paint;
|
||||
GetFontAndMinikinPaint(run.style, &font, &minikin_paint);
|
||||
@ -330,10 +411,9 @@ void Paragraph::Layout(double width, bool force) {
|
||||
run.style.font_family);
|
||||
|
||||
// Lay out this run.
|
||||
size_t line_run_start = std::max(run.start, line_range.start);
|
||||
size_t line_run_end = std::min(run.end, line_range.end);
|
||||
uint16_t* text_ptr = text_.data() + line_run_start;
|
||||
size_t text_count = line_run_end - line_run_start;
|
||||
uint16_t* text_ptr = text_.data();
|
||||
size_t text_start = run.start;
|
||||
size_t text_count = run.end - run.start;
|
||||
int bidiFlags = (paragraph_style_.text_direction == TextDirection::rtl)
|
||||
? minikin::kBidi_RTL
|
||||
: minikin::kBidi_LTR;
|
||||
@ -352,8 +432,8 @@ void Paragraph::Layout(double width, bool force) {
|
||||
|
||||
std::vector<float> text_advances(text_count);
|
||||
float text_width = layout.measureText(
|
||||
text_ptr, 0, text_count, text_count, bidiFlags, font, minikin_paint,
|
||||
minikin_font_collection, text_advances.data());
|
||||
text_ptr, text_start, text_count, text_.size(), bidiFlags, font,
|
||||
minikin_paint, minikin_font_collection, text_advances.data());
|
||||
|
||||
// Truncate characters from the text until the ellipsis fits.
|
||||
size_t truncate_count = 0;
|
||||
@ -366,11 +446,12 @@ void Paragraph::Layout(double width, bool force) {
|
||||
ellipsized_text.reserve(text_count - truncate_count +
|
||||
ellipsis.length());
|
||||
ellipsized_text.insert(ellipsized_text.begin(),
|
||||
text_.begin() + line_run_start,
|
||||
text_.begin() + line_run_end - truncate_count);
|
||||
text_.begin() + run.start,
|
||||
text_.begin() + run.end - truncate_count);
|
||||
ellipsized_text.insert(ellipsized_text.end(), ellipsis.begin(),
|
||||
ellipsis.end());
|
||||
text_ptr = ellipsized_text.data();
|
||||
text_start = 0;
|
||||
text_count = ellipsized_text.size();
|
||||
|
||||
// If there is no line limit, then skip all lines after the ellipsized
|
||||
@ -381,8 +462,8 @@ void Paragraph::Layout(double width, bool force) {
|
||||
}
|
||||
}
|
||||
|
||||
layout.doLayout(text_ptr, 0, text_count, text_count, bidiFlags, font,
|
||||
minikin_paint, minikin_font_collection);
|
||||
layout.doLayout(text_ptr, text_start, text_count, text_.size(), bidiFlags,
|
||||
font, minikin_paint, minikin_font_collection);
|
||||
|
||||
if (layout.nGlyphs() == 0) {
|
||||
// This run is empty, so insert a placeholder paint record that captures
|
||||
@ -396,17 +477,25 @@ void Paragraph::Layout(double width, bool force) {
|
||||
}
|
||||
|
||||
// Break the layout into blobs that share the same SkPaint parameters.
|
||||
std::vector<Range> glyph_blobs;
|
||||
std::vector<Range<size_t>> glyph_blobs;
|
||||
for (size_t blob_start = 0; blob_start < layout.nGlyphs();) {
|
||||
size_t blob_len = GetBlobLength(layout, blob_start);
|
||||
glyph_blobs.emplace_back(blob_start, blob_start + blob_len);
|
||||
blob_start += blob_len;
|
||||
}
|
||||
|
||||
size_t code_unit_index = 0;
|
||||
std::vector<GlyphPosition> glyph_positions;
|
||||
size_t code_unit_index;
|
||||
if (run.is_rtl()) {
|
||||
code_unit_index = text_count;
|
||||
U16_BACK_1(text_ptr + text_start, 0, code_unit_index);
|
||||
} else {
|
||||
code_unit_index = 0;
|
||||
}
|
||||
double word_start_position = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
for (const Range& glyph_blob : glyph_blobs) {
|
||||
// Build a Skia text blob from each group of glyphs.
|
||||
for (const Range<size_t>& glyph_blob : glyph_blobs) {
|
||||
paint.setTypeface(GetTypefaceForGlyph(layout, glyph_blob.start));
|
||||
const SkTextBlobBuilder::RunBuffer& blob_buffer =
|
||||
builder.allocRunPos(paint, glyph_blob.end - glyph_blob.start);
|
||||
@ -422,7 +511,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
blob_buffer.pos[pos_index + 1] = layout.getY(glyph_index);
|
||||
|
||||
if (word_index < words.size() &&
|
||||
words[word_index].start == line_run_start + code_unit_index) {
|
||||
words[word_index].start == run.start + code_unit_index) {
|
||||
word_start_position = run_x_offset + glyph_x_offset;
|
||||
}
|
||||
|
||||
@ -431,44 +520,57 @@ void Paragraph::Layout(double width, bool force) {
|
||||
// The glyph may be a ligature. Determine how many input characters
|
||||
// are joined into this glyph. Note that each character may be
|
||||
// encoded as multiple UTF-16 code units.
|
||||
size_t prev_code_unit_index = code_unit_index;
|
||||
U16_FWD_1(text_ptr, code_unit_index, text_count);
|
||||
std::vector<size_t> subglyph_code_unit_counts{code_unit_index -
|
||||
prev_code_unit_index};
|
||||
while (code_unit_index < text_count) {
|
||||
if (layout.getCharAdvance(code_unit_index) != 0)
|
||||
break;
|
||||
prev_code_unit_index = code_unit_index;
|
||||
U16_FWD_1(text_ptr, code_unit_index, text_count);
|
||||
std::vector<size_t> subglyph_code_unit_counts;
|
||||
size_t next_code_unit_index = code_unit_index;
|
||||
if (run.is_rtl()) {
|
||||
U16_BACK_1(text_ptr + text_start, 0, next_code_unit_index);
|
||||
subglyph_code_unit_counts.push_back(code_unit_index -
|
||||
prev_code_unit_index);
|
||||
next_code_unit_index);
|
||||
} else {
|
||||
U16_FWD_1(text_ptr + text_start, next_code_unit_index, text_count);
|
||||
subglyph_code_unit_counts.push_back(next_code_unit_index -
|
||||
code_unit_index);
|
||||
while (next_code_unit_index < text_count) {
|
||||
if (layout.getCharAdvance(next_code_unit_index) != 0)
|
||||
break;
|
||||
size_t cur_code_unit_index = next_code_unit_index;
|
||||
U16_FWD_1(text_ptr + text_start, next_code_unit_index,
|
||||
text_count);
|
||||
subglyph_code_unit_counts.push_back(next_code_unit_index -
|
||||
cur_code_unit_index);
|
||||
}
|
||||
}
|
||||
float subglyph_advance =
|
||||
glyph_advance / subglyph_code_unit_counts.size();
|
||||
glyph_positions.emplace_back(run_x_offset + glyph_x_offset,
|
||||
subglyph_advance,
|
||||
subglyph_code_unit_counts[0]);
|
||||
|
||||
glyph_positions.emplace_back(
|
||||
run_x_offset + glyph_x_offset, subglyph_advance,
|
||||
run.start + code_unit_index, subglyph_code_unit_counts[0]);
|
||||
|
||||
// Compute positions for the additional characters in the ligature.
|
||||
for (size_t i = 1; i < subglyph_code_unit_counts.size(); ++i) {
|
||||
glyph_positions.emplace_back(
|
||||
glyph_positions.back().start + subglyph_advance,
|
||||
subglyph_advance, subglyph_code_unit_counts[i]);
|
||||
glyph_positions.back().x_pos.end, subglyph_advance,
|
||||
glyph_positions.back().code_units.start +
|
||||
subglyph_code_unit_counts[i - 1],
|
||||
subglyph_code_unit_counts[i]);
|
||||
}
|
||||
|
||||
if (word_index < words.size() &&
|
||||
words[word_index].end == line_run_start + code_unit_index) {
|
||||
words[word_index].end == run.start + next_code_unit_index) {
|
||||
if (justify_line)
|
||||
justify_x_offset += word_gap_width;
|
||||
word_index++;
|
||||
|
||||
if (!isnan(word_start_position)) {
|
||||
double word_width =
|
||||
glyph_positions.back().glyph_end() - word_start_position;
|
||||
glyph_positions.back().x_pos.end - word_start_position;
|
||||
max_word_width = std::max(word_width, max_word_width);
|
||||
word_start_position = std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
}
|
||||
|
||||
code_unit_index = next_code_unit_index;
|
||||
}
|
||||
|
||||
SkPaint::FontMetrics metrics;
|
||||
@ -478,6 +580,22 @@ void Paragraph::Layout(double width, bool force) {
|
||||
layout.getAdvance());
|
||||
}
|
||||
|
||||
line_glyph_positions.insert(line_glyph_positions.end(),
|
||||
glyph_positions.begin(),
|
||||
glyph_positions.end());
|
||||
|
||||
// Add a record of glyph positions sorted by code unit index.
|
||||
std::vector<GlyphPosition> code_unit_positions(glyph_positions);
|
||||
std::sort(code_unit_positions.begin(), code_unit_positions.end(),
|
||||
[](const GlyphPosition& a, const GlyphPosition& b) {
|
||||
return a.code_units.start < b.code_units.start;
|
||||
});
|
||||
code_unit_runs_.emplace_back(
|
||||
std::move(code_unit_positions), Range<size_t>(run.start, run.end),
|
||||
Range<double>(glyph_positions.front().x_pos.start,
|
||||
glyph_positions.back().x_pos.end),
|
||||
line_number, run.direction);
|
||||
|
||||
run_x_offset += layout.getAdvance();
|
||||
}
|
||||
|
||||
@ -519,7 +637,7 @@ void Paragraph::Layout(double width, bool force) {
|
||||
size_t next_line_start = (line_number < line_ranges_.size() - 1)
|
||||
? line_ranges_[line_number + 1].start
|
||||
: text_.size();
|
||||
glyph_lines_.emplace_back(std::move(glyph_positions),
|
||||
glyph_lines_.emplace_back(std::move(line_glyph_positions),
|
||||
next_line_start - line_range.start);
|
||||
}
|
||||
|
||||
@ -528,6 +646,11 @@ void Paragraph::Layout(double width, bool force) {
|
||||
max_intrinsic_width_ += line_width;
|
||||
}
|
||||
min_intrinsic_width_ = std::min(max_word_width, max_intrinsic_width_);
|
||||
|
||||
std::sort(code_unit_runs_.begin(), code_unit_runs_.end(),
|
||||
[](const CodeUnitRun& a, const CodeUnitRun& b) {
|
||||
return a.code_units.start < b.code_units.start;
|
||||
});
|
||||
}
|
||||
|
||||
double Paragraph::GetLineXOffset(size_t line) {
|
||||
@ -766,108 +889,105 @@ void Paragraph::PaintDecorations(SkCanvas* canvas,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<SkRect> Paragraph::GetRectsForRange(size_t start,
|
||||
size_t end) const {
|
||||
std::vector<SkRect> rects;
|
||||
std::vector<Paragraph::TextBox> Paragraph::GetRectsForRange(size_t start,
|
||||
size_t end) const {
|
||||
std::vector<TextBox> boxes;
|
||||
|
||||
if (end <= start || start == end)
|
||||
return rects;
|
||||
|
||||
size_t pos = 0;
|
||||
size_t line;
|
||||
for (line = 0; line < glyph_lines_.size(); ++line) {
|
||||
if (start < pos + glyph_lines_[line].total_code_units)
|
||||
for (const CodeUnitRun& run : code_unit_runs_) {
|
||||
if (run.code_units.start >= end)
|
||||
break;
|
||||
pos += glyph_lines_[line].total_code_units;
|
||||
}
|
||||
if (line == glyph_lines_.size())
|
||||
return rects;
|
||||
if (run.code_units.end <= start)
|
||||
continue;
|
||||
|
||||
if (end <= pos + glyph_lines_[line].total_code_units) {
|
||||
rects.push_back(GetRectForLineRange(line, start - pos, end - pos));
|
||||
return rects;
|
||||
}
|
||||
SkScalar top =
|
||||
(run.line_number > 0) ? line_heights_[run.line_number - 1] : 0;
|
||||
SkScalar bottom = line_heights_[run.line_number];
|
||||
|
||||
rects.push_back(GetRectForLineRange(line, start - pos,
|
||||
glyph_lines_[line].total_code_units));
|
||||
|
||||
while (true) {
|
||||
pos += glyph_lines_[line].total_code_units;
|
||||
line++;
|
||||
if (line == glyph_lines_.size())
|
||||
break;
|
||||
|
||||
if (end <= pos + glyph_lines_[line].total_code_units) {
|
||||
rects.push_back(GetRectForLineRange(line, 0, end - pos));
|
||||
break;
|
||||
SkScalar left, right;
|
||||
if (run.code_units.start >= start && run.code_units.end <= end) {
|
||||
left = run.x_pos.start;
|
||||
right = run.x_pos.end;
|
||||
} else {
|
||||
rects.push_back(
|
||||
GetRectForLineRange(line, 0, glyph_lines_[line].total_code_units));
|
||||
left = SK_ScalarMax;
|
||||
right = SK_ScalarMin;
|
||||
for (const GlyphPosition& gp : run.positions) {
|
||||
if (gp.code_units.start >= start && gp.code_units.end <= end) {
|
||||
left = std::min(left, static_cast<SkScalar>(gp.x_pos.start));
|
||||
right = std::max(right, static_cast<SkScalar>(gp.x_pos.end));
|
||||
}
|
||||
}
|
||||
if (left == SK_ScalarMax || right == SK_ScalarMin)
|
||||
continue;
|
||||
}
|
||||
boxes.emplace_back(SkRect::MakeLTRB(left, top, right, bottom),
|
||||
run.direction);
|
||||
}
|
||||
|
||||
return rects;
|
||||
}
|
||||
|
||||
SkRect Paragraph::GetRectForLineRange(size_t line,
|
||||
size_t start,
|
||||
size_t end) const {
|
||||
FXL_DCHECK(line < glyph_lines_.size());
|
||||
const GlyphLine& glyph_line = glyph_lines_[line];
|
||||
if (glyph_line.positions.empty())
|
||||
return SkRect::MakeEmpty();
|
||||
|
||||
FXL_DCHECK(start < glyph_line.total_code_units);
|
||||
SkScalar left = glyph_line.GetGlyphPosition(start).start;
|
||||
end = std::min(end, glyph_line.total_code_units);
|
||||
SkScalar right = glyph_line.GetGlyphPosition(end - 1).glyph_end();
|
||||
SkScalar top = (line > 0) ? line_heights_[line - 1] : 0;
|
||||
SkScalar bottom = line_heights_[line];
|
||||
return SkRect::MakeLTRB(left, top, right, bottom);
|
||||
return boxes;
|
||||
}
|
||||
|
||||
Paragraph::PositionWithAffinity Paragraph::GetGlyphPositionAtCoordinate(
|
||||
double dx,
|
||||
double dy,
|
||||
bool using_glyph_center_as_boundary) const {
|
||||
if (line_heights_.empty())
|
||||
double dy) const {
|
||||
if (line_heights_.empty() || dy < 0)
|
||||
return PositionWithAffinity(0, DOWNSTREAM);
|
||||
|
||||
size_t offset = 0;
|
||||
size_t y_index;
|
||||
for (y_index = 0; y_index < line_heights_.size() - 1; ++y_index) {
|
||||
if (dy < line_heights_[y_index])
|
||||
break;
|
||||
offset += glyph_lines_[y_index].total_code_units;
|
||||
}
|
||||
|
||||
const std::vector<GlyphPosition>& line_glyph_position =
|
||||
glyph_lines_[y_index].positions;
|
||||
if (line_glyph_position.empty()) {
|
||||
int line_start_index = std::accumulate(
|
||||
glyph_lines_.begin(), glyph_lines_.begin() + y_index, 0,
|
||||
[](const int a, const GlyphLine& b) { return a + b.total_code_units; });
|
||||
return PositionWithAffinity(line_start_index, DOWNSTREAM);
|
||||
}
|
||||
|
||||
size_t x_index;
|
||||
const GlyphPosition* gp = nullptr;
|
||||
for (x_index = 0; x_index < line_glyph_position.size(); ++x_index) {
|
||||
double glyph_end = (x_index < line_glyph_position.size() - 1)
|
||||
? line_glyph_position[x_index + 1].start
|
||||
: line_glyph_position[x_index].glyph_end();
|
||||
double boundary;
|
||||
if (using_glyph_center_as_boundary) {
|
||||
boundary = (line_glyph_position[x_index].start + glyph_end) / 2.0f;
|
||||
} else {
|
||||
boundary = glyph_end;
|
||||
}
|
||||
|
||||
if (dx < boundary)
|
||||
? line_glyph_position[x_index + 1].x_pos.start
|
||||
: line_glyph_position[x_index].x_pos.end;
|
||||
if (dx < glyph_end) {
|
||||
gp = &line_glyph_position[x_index];
|
||||
break;
|
||||
|
||||
offset += line_glyph_position[x_index].code_units;
|
||||
}
|
||||
}
|
||||
|
||||
if (gp == nullptr) {
|
||||
const GlyphPosition& last_glyph = line_glyph_position.back();
|
||||
return PositionWithAffinity(last_glyph.code_units.end, UPSTREAM);
|
||||
}
|
||||
|
||||
// Find the direction of the run that contains this glyph.
|
||||
TextDirection direction = TextDirection::ltr;
|
||||
for (const CodeUnitRun& run : code_unit_runs_) {
|
||||
if (gp->code_units.start >= run.code_units.start &&
|
||||
gp->code_units.end <= run.code_units.end) {
|
||||
direction = run.direction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
double glyph_center = (gp->x_pos.start + gp->x_pos.end) / 2;
|
||||
if ((direction == TextDirection::ltr && dx < glyph_center) ||
|
||||
(direction == TextDirection::rtl && dx >= glyph_center)) {
|
||||
return PositionWithAffinity(gp->code_units.start, DOWNSTREAM);
|
||||
} else {
|
||||
return PositionWithAffinity(gp->code_units.end, UPSTREAM);
|
||||
}
|
||||
return PositionWithAffinity(offset, x_index > 0 ? UPSTREAM : DOWNSTREAM);
|
||||
}
|
||||
|
||||
Paragraph::Range Paragraph::GetWordBoundary(size_t offset) const {
|
||||
Paragraph::Range<size_t> Paragraph::GetWordBoundary(size_t offset) const {
|
||||
// TODO(garyq): Consider punctuation as separate words.
|
||||
if (text_.size() == 0)
|
||||
return Range(0, 0);
|
||||
return Range(
|
||||
return Range<size_t>(0, 0);
|
||||
return Range<size_t>(
|
||||
minikin::getPrevWordBreakForCache(text_.data(), offset + 1, text_.size()),
|
||||
minikin::getNextWordBreakForCache(text_.data(), offset, text_.size()));
|
||||
}
|
||||
|
||||
84
third_party/txt/src/txt/paragraph.h
vendored
84
third_party/txt/src/txt/paragraph.h
vendored
@ -29,9 +29,7 @@
|
||||
#include "paragraph_style.h"
|
||||
#include "styled_runs.h"
|
||||
#include "third_party/gtest/include/gtest/gtest_prod.h"
|
||||
#include "third_party/skia/include/core/SkPoint.h"
|
||||
#include "third_party/skia/include/core/SkRect.h"
|
||||
#include "third_party/skia/include/core/SkTextBlob.h"
|
||||
#include "utils/WindowsUtils.h"
|
||||
|
||||
class SkCanvas;
|
||||
@ -64,14 +62,32 @@ class Paragraph {
|
||||
PositionWithAffinity(size_t p, Affinity a) : position(p), affinity(a) {}
|
||||
};
|
||||
|
||||
struct TextBox {
|
||||
const SkRect rect;
|
||||
const TextDirection direction;
|
||||
|
||||
TextBox(SkRect r, TextDirection d) : rect(r), direction(d) {}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct Range {
|
||||
Range(size_t s, size_t e) : start(s), end(e) {}
|
||||
size_t start, end;
|
||||
bool operator==(const Range& other) const {
|
||||
Range(T s, T e) : start(s), end(e) {}
|
||||
T start, end;
|
||||
bool operator==(const Range<T>& other) const {
|
||||
return start == other.start && end == other.end;
|
||||
}
|
||||
};
|
||||
|
||||
struct BidiRun {
|
||||
BidiRun(size_t s, size_t e, TextDirection d, const TextStyle& st)
|
||||
: start(s), end(e), direction(d), style(st) {}
|
||||
size_t start, end;
|
||||
TextDirection direction;
|
||||
const TextStyle& style;
|
||||
|
||||
bool is_rtl() const { return direction == TextDirection::rtl; }
|
||||
};
|
||||
|
||||
// Minikin Layout doLayout() and LineBreaker addStyleRun() has an
|
||||
// O(N^2) (according to benchmarks) time complexity where N is the total
|
||||
// number of characters. However, this is not significant for reasonably sized
|
||||
@ -121,28 +137,15 @@ class Paragraph {
|
||||
|
||||
// Returns a vector of bounding boxes that enclose all text between start and
|
||||
// end glyph indexes, including start and excluding end.
|
||||
std::vector<SkRect> GetRectsForRange(size_t start, size_t end) const;
|
||||
|
||||
// Returns a bounding box that encloses the given starting and ending
|
||||
// positions within a line.
|
||||
SkRect GetRectForLineRange(size_t line, size_t start, size_t end) const;
|
||||
std::vector<TextBox> GetRectsForRange(size_t start, size_t end) const;
|
||||
|
||||
// Returns the index of the glyph that corresponds to the provided coordinate,
|
||||
// with the top left corner as the origin, and +y direction as down.
|
||||
//
|
||||
// When using_glyph_center_as_boundary == true, coords to the + direction of
|
||||
// the center x-position of the glyph will be considered as the next glyph. A
|
||||
// typical use-case for this is when the cursor is meant to be on either side
|
||||
// of any given character. This allows the transition border to be middle of
|
||||
// each character.
|
||||
PositionWithAffinity GetGlyphPositionAtCoordinate(
|
||||
double dx,
|
||||
double dy,
|
||||
bool using_glyph_center_as_boundary = false) const;
|
||||
PositionWithAffinity GetGlyphPositionAtCoordinate(double dx, double dy) const;
|
||||
|
||||
// Finds the first and last glyphs that define a word containing the glyph at
|
||||
// index offset.
|
||||
Range GetWordBoundary(size_t offset) const;
|
||||
Range<size_t> GetWordBoundary(size_t offset) const;
|
||||
|
||||
// Returns the number of lines the paragraph takes up. If the text exceeds the
|
||||
// amount width and maxlines provides, Layout() truncates the extra text from
|
||||
@ -185,6 +188,7 @@ class Paragraph {
|
||||
// Starting data to layout.
|
||||
std::vector<uint16_t> text_;
|
||||
StyledRuns runs_;
|
||||
std::vector<BidiRun> bidi_runs_;
|
||||
ParagraphStyle paragraph_style_;
|
||||
std::shared_ptr<FontCollection> font_collection_;
|
||||
|
||||
@ -205,30 +209,45 @@ class Paragraph {
|
||||
bool did_exceed_max_lines_;
|
||||
|
||||
struct GlyphPosition {
|
||||
const double start;
|
||||
const double advance;
|
||||
const size_t code_units;
|
||||
Range<size_t> code_units;
|
||||
Range<double> x_pos;
|
||||
|
||||
GlyphPosition(double s, double a, size_t cu)
|
||||
: start(s), advance(a), code_units(cu) {}
|
||||
|
||||
double glyph_end() const { return start + advance; }
|
||||
GlyphPosition(double x_start,
|
||||
double x_advance,
|
||||
size_t code_unit_index,
|
||||
size_t code_unit_width);
|
||||
};
|
||||
|
||||
struct GlyphLine {
|
||||
// Glyph positions sorted by x coordinate.
|
||||
const std::vector<GlyphPosition> positions;
|
||||
const size_t total_code_units;
|
||||
|
||||
GlyphLine(std::vector<GlyphPosition>&& p, size_t tcu);
|
||||
};
|
||||
|
||||
// Return the GlyphPosition containing the given code unit index within
|
||||
// the line.
|
||||
const GlyphPosition& GetGlyphPosition(size_t pos) const;
|
||||
struct CodeUnitRun {
|
||||
// Glyph positions sorted by code unit index.
|
||||
std::vector<GlyphPosition> positions;
|
||||
Range<size_t> code_units;
|
||||
Range<double> x_pos;
|
||||
size_t line_number;
|
||||
TextDirection direction;
|
||||
|
||||
CodeUnitRun(std::vector<GlyphPosition>&& p,
|
||||
Range<size_t> cu,
|
||||
Range<double> x,
|
||||
size_t line,
|
||||
TextDirection dir);
|
||||
};
|
||||
|
||||
// Holds the laid out x positions of each glyph.
|
||||
std::vector<GlyphLine> glyph_lines_;
|
||||
|
||||
// Holds the positions of each range of code units in the text.
|
||||
// Sorted in code unit index order.
|
||||
std::vector<CodeUnitRun> code_unit_runs_;
|
||||
|
||||
// The max width of the paragraph as provided in the most recent Layout()
|
||||
// call.
|
||||
double width_ = -1.0f;
|
||||
@ -260,6 +279,9 @@ class Paragraph {
|
||||
// Break the text into lines.
|
||||
bool ComputeLineBreaks();
|
||||
|
||||
// Break the text into runs based on LTR/RTL text direction.
|
||||
bool ComputeBidiRuns();
|
||||
|
||||
// Calculate the starting X offset of a line based on the line's width and
|
||||
// alignment.
|
||||
double GetLineXOffset(size_t line);
|
||||
|
||||
163
third_party/txt/tests/paragraph_unittests.cc
vendored
163
third_party/txt/tests/paragraph_unittests.cc
vendored
@ -891,10 +891,10 @@ TEST_F(ParagraphTest, GetGlyphPositionAtCoordinateParagraph) {
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0, 0).position, 0ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(3, 3).position, 0ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 1).position, 1ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(300, 2).position, 10ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(301, 2.2).position, 10ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(302, 2.6).position, 10ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(301, 2.1).position, 10ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(300, 2).position, 11ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(301, 2.2).position, 11ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(302, 2.6).position, 11ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(301, 2.1).position, 11ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 20).position,
|
||||
18ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(450, 20).position, 16ull);
|
||||
@ -912,7 +912,7 @@ TEST_F(ParagraphTest, GetGlyphPositionAtCoordinateParagraph) {
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 90).position, 19ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 10000).position,
|
||||
77ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(85, 10000).position, 74ull);
|
||||
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(85, 10000).position, 75ull);
|
||||
}
|
||||
|
||||
TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) {
|
||||
@ -955,87 +955,89 @@ TEST_F(ParagraphTest, DISABLE_ON_WINDOWS(GetRectsForRangeParagraph)) {
|
||||
// NOTE: The base truth values may still need adjustment as the specifics
|
||||
// are adjusted.
|
||||
paint.setColor(SK_ColorRED);
|
||||
std::vector<SkRect> rects = paragraph->GetRectsForRange(0, 0);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
GetCanvas()->drawRect(rects[i], paint);
|
||||
std::vector<txt::Paragraph::TextBox> boxes =
|
||||
paragraph->GetRectsForRange(0, 0);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(rects.size(), 0ull);
|
||||
EXPECT_EQ(boxes.size(), 0ull);
|
||||
|
||||
rects = paragraph->GetRectsForRange(0, 1);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
GetCanvas()->drawRect(rects[i], paint);
|
||||
boxes = paragraph->GetRectsForRange(0, 1);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(rects.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(rects[0].left(), 0);
|
||||
EXPECT_FLOAT_EQ(rects[0].top(), 0);
|
||||
EXPECT_FLOAT_EQ(rects[0].right(), 28.417969);
|
||||
EXPECT_FLOAT_EQ(rects[0].bottom(), 59);
|
||||
EXPECT_EQ(boxes.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 28.417969);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);
|
||||
|
||||
paint.setColor(SK_ColorBLUE);
|
||||
rects = paragraph->GetRectsForRange(2, 8);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
GetCanvas()->drawRect(rects[i], paint);
|
||||
boxes = paragraph->GetRectsForRange(2, 8);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(rects.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(rects[0].left(), 56.835938);
|
||||
EXPECT_FLOAT_EQ(rects[0].top(), 0);
|
||||
EXPECT_FLOAT_EQ(rects[0].right(), 177.44922);
|
||||
EXPECT_FLOAT_EQ(rects[0].bottom(), 59);
|
||||
EXPECT_EQ(boxes.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 56.835938);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 177.44922);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);
|
||||
|
||||
paint.setColor(SK_ColorGREEN);
|
||||
rects = paragraph->GetRectsForRange(8, 21);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
GetCanvas()->drawRect(rects[i], paint);
|
||||
boxes = paragraph->GetRectsForRange(8, 21);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(rects.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(rects[0].left(), 177);
|
||||
EXPECT_FLOAT_EQ(rects[0].top(), 0);
|
||||
EXPECT_FLOAT_EQ(rects[0].right(), 506.08984);
|
||||
EXPECT_FLOAT_EQ(rects[0].bottom(), 59);
|
||||
EXPECT_EQ(boxes.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 177);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 506.08984);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);
|
||||
|
||||
paint.setColor(SK_ColorRED);
|
||||
rects = paragraph->GetRectsForRange(30, 100);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
GetCanvas()->drawRect(rects[i], paint);
|
||||
boxes = paragraph->GetRectsForRange(30, 100);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(rects.size(), 4ull);
|
||||
EXPECT_FLOAT_EQ(rects[0].left(), 210.83594);
|
||||
EXPECT_FLOAT_EQ(rects[0].top(), 59);
|
||||
EXPECT_FLOAT_EQ(rects[0].right(), 463.44922);
|
||||
EXPECT_FLOAT_EQ(rects[0].bottom(), 118);
|
||||
EXPECT_EQ(boxes.size(), 4ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 210.83594);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), 59);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 463.44922);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 118);
|
||||
|
||||
// TODO(garyq): The following set of vals are definetly wrong and
|
||||
// end of paragraph handling needs to be fixed in a later patch.
|
||||
EXPECT_FLOAT_EQ(rects[3].left(), 0);
|
||||
EXPECT_FLOAT_EQ(rects[3].top(), 236);
|
||||
EXPECT_FLOAT_EQ(rects[3].right(), 142.08984);
|
||||
EXPECT_FLOAT_EQ(rects[3].bottom(), 295);
|
||||
EXPECT_FLOAT_EQ(boxes[3].rect.left(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[3].rect.top(), 236);
|
||||
EXPECT_FLOAT_EQ(boxes[3].rect.right(), 142.08984);
|
||||
EXPECT_FLOAT_EQ(boxes[3].rect.bottom(), 295);
|
||||
|
||||
paint.setColor(SK_ColorBLUE);
|
||||
rects = paragraph->GetRectsForRange(19, 22);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
GetCanvas()->drawRect(rects[i], paint);
|
||||
boxes = paragraph->GetRectsForRange(19, 22);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(rects.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(rects[0].left(), 449.25391);
|
||||
EXPECT_FLOAT_EQ(rects[0].top(), 0);
|
||||
EXPECT_FLOAT_EQ(rects[0].right(), 519.44922);
|
||||
EXPECT_FLOAT_EQ(rects[0].bottom(), 59);
|
||||
EXPECT_EQ(boxes.size(), 1ull);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.left(), 449.25391);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.top(), 0);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.right(), 519.44922);
|
||||
EXPECT_FLOAT_EQ(boxes[0].rect.bottom(), 59);
|
||||
|
||||
paint.setColor(SK_ColorRED);
|
||||
rects = paragraph->GetRectsForRange(21, 21);
|
||||
for (size_t i = 0; i < rects.size(); ++i) {
|
||||
GetCanvas()->drawRect(rects[i], paint);
|
||||
boxes = paragraph->GetRectsForRange(21, 21);
|
||||
for (size_t i = 0; i < boxes.size(); ++i) {
|
||||
GetCanvas()->drawRect(boxes[i].rect, paint);
|
||||
}
|
||||
EXPECT_EQ(rects.size(), 0ull);
|
||||
EXPECT_EQ(boxes.size(), 0ull);
|
||||
|
||||
ASSERT_TRUE(Snapshot());
|
||||
}
|
||||
|
||||
SkRect GetCoordinatesForGlyphPosition(const txt::Paragraph& paragraph,
|
||||
size_t pos) {
|
||||
std::vector<SkRect> rects = paragraph.GetRectsForRange(pos, pos + 1);
|
||||
return !rects.empty() ? rects.front() : SkRect::MakeEmpty();
|
||||
std::vector<txt::Paragraph::TextBox> boxes =
|
||||
paragraph.GetRectsForRange(pos, pos + 1);
|
||||
return !boxes.empty() ? boxes.front().rect : SkRect::MakeEmpty();
|
||||
}
|
||||
|
||||
TEST_F(ParagraphTest, GetWordBoundaryParagraph) {
|
||||
@ -1077,35 +1079,42 @@ TEST_F(ParagraphTest, GetWordBoundaryParagraph) {
|
||||
SkRect rect = GetCoordinatesForGlyphPosition(*paragraph, 0);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(0), txt::Paragraph::Range(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(1), txt::Paragraph::Range(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(2), txt::Paragraph::Range(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(3), txt::Paragraph::Range(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(4), txt::Paragraph::Range(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(0), txt::Paragraph::Range<size_t>(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(1), txt::Paragraph::Range<size_t>(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(2), txt::Paragraph::Range<size_t>(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(3), txt::Paragraph::Range<size_t>(0, 5));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(4), txt::Paragraph::Range<size_t>(0, 5));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 5);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(5), txt::Paragraph::Range(5, 6));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(5), txt::Paragraph::Range<size_t>(5, 6));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 6);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(6), txt::Paragraph::Range(6, 7));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(6), txt::Paragraph::Range<size_t>(6, 7));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 7);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(7), txt::Paragraph::Range(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(8), txt::Paragraph::Range(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(9), txt::Paragraph::Range(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(10), txt::Paragraph::Range(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(11), txt::Paragraph::Range(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(7),
|
||||
txt::Paragraph::Range<size_t>(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(8),
|
||||
txt::Paragraph::Range<size_t>(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(9),
|
||||
txt::Paragraph::Range<size_t>(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(10),
|
||||
txt::Paragraph::Range<size_t>(7, 12));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(11),
|
||||
txt::Paragraph::Range<size_t>(7, 12));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 12);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(12), txt::Paragraph::Range(12, 13));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(12),
|
||||
txt::Paragraph::Range<size_t>(12, 13));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 13);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(13), txt::Paragraph::Range(13, 18));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(13),
|
||||
txt::Paragraph::Range<size_t>(13, 18));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 18);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
@ -1121,15 +1130,17 @@ TEST_F(ParagraphTest, GetWordBoundaryParagraph) {
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 30);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(30), txt::Paragraph::Range(30, 31));
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(30),
|
||||
txt::Paragraph::Range<size_t>(30, 31));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, 31);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, icu_text.length() - 5);
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
EXPECT_EQ(paragraph->GetWordBoundary(icu_text.length() - 1),
|
||||
txt::Paragraph::Range(icu_text.length() - 5, icu_text.length()));
|
||||
EXPECT_EQ(
|
||||
paragraph->GetWordBoundary(icu_text.length() - 1),
|
||||
txt::Paragraph::Range<size_t>(icu_text.length() - 5, icu_text.length()));
|
||||
rect = GetCoordinatesForGlyphPosition(*paragraph, icu_text.length());
|
||||
GetCanvas()->drawLine(rect.fLeft, rect.fTop, rect.fLeft, rect.fBottom, paint);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user