未验证 提交 affa4e7c 编写于 作者: J Jason Simmons 提交者: GitHub

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
上级 7e4df308
......@@ -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));
......
......@@ -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;
GlyphPosition(double s, double a, size_t cu)
: start(s), advance(a), code_units(cu) {}
Range<size_t> code_units;
Range<double> x_pos;
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);
......
......@@ -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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册