Take letter spacing into account when linebreaking and initial implementation of GetRectsForRange().

Change-Id: Id7fa1b5b60c988cb73ba182241b3d8b3c998e07c
This commit is contained in:
Gary Qian
2017-07-07 17:17:34 -07:00
parent 3a0422f761
commit ba43d1e989
5 changed files with 78 additions and 24 deletions

View File

@@ -166,7 +166,7 @@ class LineBreaker {
// is having some kind of callback (or virtual class, or maybe even template), which could
// easily be instantiated with Minikin's Layout. Future work for when needed.
float addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
FontStyle style, size_t start, size_t end, bool isRtl);
FontStyle style, size_t start, size_t end, bool isRtl, double letterSpacing = 0);
void addReplacement(size_t start, size_t end, float width);
@@ -230,6 +230,7 @@ class LineBreaker {
icu::Locale mLocale;
std::vector<uint16_t>mTextBuf;
std::vector<float>mCharWidths;
std::vector<float>mCharSpacing;
Hyphenator* mHyphenator;
std::vector<HyphenationType> mHyphBuf;

View File

@@ -113,7 +113,7 @@ static bool isLineEndSpace(uint16_t c) {
// This method finds the candidate word breaks (using the ICU break iterator) and sends them
// to addCandidate.
float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCollection>& typeface,
FontStyle style, size_t start, size_t end, bool isRtl) {
FontStyle style, size_t start, size_t end, bool isRtl, double letterSpacing) {
float width = 0.0f;
int bidiFlags = isRtl ? kBidi_Force_RTL : kBidi_Force_LTR;
@@ -138,6 +138,10 @@ float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCo
}
}
for (size_t i = start; i < end; ++i) {
mCharSpacing.push_back(letterSpacing);
}
size_t current = (size_t)mWordBreaker.current();
size_t afterWord = start;
size_t lastBreak = start;
@@ -155,7 +159,7 @@ float LineBreaker::addStyleRun(MinikinPaint* paint, const std::shared_ptr<FontCo
mStrategy = kBreakStrategy_Greedy;
} else {
if (isWordSpace(c)) mSpaceCount += 1;
mWidth += mCharWidths[i];
mWidth += mCharWidths[i] + mCharSpacing[i];
if (!isLineEndSpace(c)) {
postBreak = mWidth;
postSpaceCount = mSpaceCount;
@@ -341,7 +345,7 @@ void LineBreaker::pushBreak(int offset, float width, uint8_t hyphenEdit) {
void LineBreaker::addReplacement(size_t start, size_t end, float width) {
mCharWidths[start] = width;
std::fill(&mCharWidths[start + 1], &mCharWidths[end], 0.0f);
addStyleRun(nullptr, nullptr, FontStyle(), start, end, false);
addStyleRun(nullptr, nullptr, FontStyle(), start, end, false, 0);
}
// Get the width of a space. May return 0 if there are no spaces.

View File

@@ -135,7 +135,8 @@ void Paragraph::AddRunsToLineBreaker(
breaker_.addStyleRun(&paint,
font_collection_->GetMinikinFontCollectionForFamily(
run.style.font_family),
font, run.start, run.end, false);
font, run.start, run.end, false,
run.style.letter_spacing);
}
}
@@ -171,6 +172,8 @@ void Paragraph::Layout(double width, bool force) {
lines_ = 0;
line_widths_ = std::vector<double>();
line_heights_ = std::vector<double>();
// Set padding elements to have a minimum point.
line_heights_.push_back(0);
glyph_position_x_ = std::vector<std::vector<double>>();
glyph_position_x_.push_back(std::vector<double>());
@@ -283,6 +286,7 @@ void Paragraph::Layout(double width, bool force) {
// Check if we should remove trailing whitespace of blobs.
size_t trailing_length = 0;
while (
paragraph_style_.text_align == TextAlign::justify &&
minikin::isWordSpace(
text_[character_index + blob_length - trailing_length - 1]) &&
layout_end == next_break) {
@@ -613,7 +617,43 @@ void Paragraph::PaintWavyDecoration(SkCanvas* canvas,
std::vector<SkRect> Paragraph::GetRectsForRange(size_t start,
size_t end) const {
return std::vector<SkRect>();
std::vector<SkRect> rects;
end = fmin(end, text_.size() - 1);
while (start <= end) {
SkIPoint word_bounds = GetWordBoundary(start);
word_bounds.fY = fmin(end + 1, word_bounds.fY);
word_bounds.fX = fmax(start, word_bounds.fX);
start = word_bounds.fY;
SkRect left_limits = GetCoordinatesForGlyphPosition(word_bounds.fX + 1);
SkRect right_limits = GetCoordinatesForGlyphPosition(word_bounds.fY);
if (left_limits.top() < right_limits.top()) {
rects.push_back(SkRect::MakeLTRB(
0, right_limits.top(), right_limits.right(), right_limits.bottom()));
} else {
rects.push_back(SkRect::MakeLTRB(left_limits.left(), left_limits.top(),
right_limits.right(),
right_limits.bottom()));
}
}
return rects;
}
SkRect Paragraph::GetCoordinatesForGlyphPosition(size_t pos) const {
size_t remainder = fmin(pos, text_.size());
size_t line = 1;
for (line = 1; line < line_heights_.size() - 1; ++line) {
if (remainder > glyph_position_x_[line].size() - 2) {
remainder -= glyph_position_x_[line].size() - 2;
} else {
break;
}
}
return SkRect::MakeLTRB(glyph_position_x_[line][remainder],
line_heights_[line - 1],
remainder < glyph_position_x_[line].size() - 2
? glyph_position_x_[line][remainder + 1]
: line_widths_[line - 1],
line_heights_[line]);
}
size_t Paragraph::GetGlyphPositionAtCoordinate(double dx, double dy) const {
@@ -643,7 +683,8 @@ size_t Paragraph::GetGlyphPositionAtCoordinate(double dx, double dy) const {
}
SkIPoint Paragraph::GetWordBoundary(size_t offset) const {
return SkIPoint::Make(0, 0);
// TODO(garyq): Implement.
return SkIPoint::Make(0, offset + 1);
}
int Paragraph::GetLineCount() const {

View File

@@ -61,6 +61,8 @@ class Paragraph {
size_t GetGlyphPositionAtCoordinate(double dx, double dy) const;
SkRect GetCoordinatesForGlyphPosition(size_t pos) const;
SkIPoint GetWordBoundary(size_t offset) const;
int GetLineCount() const;

View File

@@ -303,6 +303,9 @@ TEST_F(RenderTest, LeftAlignParagraph) {
paragraph->Layout(GetTestCanvasWidth() - 100);
paragraph->Paint(GetCanvas(), 0, 0);
ASSERT_TRUE(Snapshot());
ASSERT_EQ(paragraph->text_.size(), std::string{text}.length());
for (size_t i = 0; i < u16_text.length(); i++) {
ASSERT_EQ(paragraph->text_[i], u16_text[i]);
@@ -343,11 +346,9 @@ TEST_F(RenderTest, LeftAlignParagraph) {
// Tests for GetGlyphPositionAtCoordinate()
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0, 0), 0ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 1), 0ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 30), 74ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 60), 142ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(2000, 30), 141ull);
ASSERT_TRUE(Snapshot());
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 30), 68ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 60), 134ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(2000, 30), 133ull);
}
TEST_F(RenderTest, RightAlignParagraph) {
@@ -856,7 +857,7 @@ TEST_F(RenderTest, DISABLED_ArabicParagraph) {
TEST_F(RenderTest, GetGlyphPositionAtCoordinateParagraph) {
const char* text =
"12345 67890 12345 67890 12345 67890 12345 67890 12345 67890 12345 "
"67890";
"67890 12345";
auto icu_text = icu::UnicodeString::fromUTF8(text);
std::u16string u16_text(icu_text.getBuffer(),
icu_text.getBuffer() + icu_text.length());
@@ -880,27 +881,32 @@ TEST_F(RenderTest, GetGlyphPositionAtCoordinateParagraph) {
builder.Pop();
auto paragraph = builder.Build();
paragraph->Layout(GetTestCanvasWidth() - 500);
paragraph->Layout(550);
paragraph->Paint(GetCanvas(), 0, 0);
ASSERT_TRUE(Snapshot());
// Tests for GetGlyphPositionAtCoordinate()
// NOTE: resulting values can be a few off from their respective positions in
// the original text because the final trailing whitespaces are sometimes not
// drawn and therefore are not active glyphs.
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-10000, -10000), 0ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-1, -1), 0ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(0, 0), 0ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(3, 3), 0ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 1), 1ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 20), 16ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 80), 33ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 80), 17ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 160), 34ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 160), 50ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(70, 160), 36ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 270), 51ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 80), 18ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 10000), 68ull);
ASSERT_TRUE(Snapshot());
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 20), 17ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(100000, 80), 35ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(-100000, 80), 18ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(20, -80), 0ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 80), 18ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 160), 36ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 160), 53ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(70, 160), 38ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(1, 270), 54ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(35, 80), 19ull);
ASSERT_EQ(paragraph->GetGlyphPositionAtCoordinate(10000, 10000), 77ull);
}
} // namespace txt