diff --git a/src/paint_record.h b/src/paint_record.h index cc5b82807ab..c34fd9d1628 100644 --- a/src/paint_record.h +++ b/src/paint_record.h @@ -56,14 +56,14 @@ class PaintRecord { const TextStyle& style() const { return style_; } - double line() const { return line_; } + size_t line() const { return line_; } private: TextStyle style_; SkPoint offset_; sk_sp text_; SkPaint::FontMetrics metrics_; - int line_; + size_t line_; FTL_DISALLOW_COPY_AND_ASSIGN(PaintRecord); }; diff --git a/src/paragraph.cc b/src/paragraph.cc index e8de72d3992..549de3af7ba 100644 --- a/src/paragraph.cc +++ b/src/paragraph.cc @@ -24,6 +24,7 @@ #include #include "lib/ftl/logging.h" +#include "lib/txt/libs/minikin/LayoutUtils.h" #include "lib/txt/src/font_collection.h" #include "lib/txt/src/font_skia.h" #include "minikin/LineBreaker.h" @@ -96,7 +97,7 @@ void GetFontAndMinikinPaint(const TextStyle& style, *font = minikin::FontStyle(GetWeight(style), GetItalic(style)); paint->size = style.font_size; paint->letterSpacing = style.letter_spacing; - paint->wordSpacing = style.word_spacing; // Likely not working yet. + paint->wordSpacing = style.word_spacing; // TODO(abarth): word_spacing. } @@ -148,8 +149,9 @@ void Paragraph::Layout(double width, width_ = width; - breaker_.setLineWidths(0.0f, 0, width); + breaker_.setLineWidths(0.0f, 0, width_); AddRunsToLineBreaker(rootdir); + breaker_.setJustified(paragraph_style_.text_align == TextAlign::justify); size_t breaks_count = breaker_.computeBreaks(); const int* breaks = breaker_.getBreaks(); @@ -171,12 +173,12 @@ void Paragraph::Layout(double width, SkScalar y = y_offset; size_t break_index = 0; double letter_spacing_offset = 0.0f; - double word_spacing_offset = 0.0f; double max_line_spacing = 0.0f; double max_descent = 0.0f; double prev_max_descent = 0.0f; double line_width = 0.0f; std::vector x_queue; + size_t character_index = 0; auto flush = [this, &x_queue, &y]() -> void { for (size_t i = 0; i < x_queue.size(); ++i) { @@ -209,28 +211,44 @@ void Paragraph::Layout(double width, int bidiFlags = 0; layout.doLayout(text_.data(), layout_start, layout_end - layout_start, text_.size(), bidiFlags, font, minikin_paint, collection); - const size_t glyph_count = layout.nGlyphs(); size_t blob_start = 0; - // Each word/blob. + // Each blob. + std::vector buffers; + std::vector buffer_sizes; + int word_count = 0; while (blob_start < glyph_count) { const size_t blob_length = GetBlobLength(layout, blob_start); + buffer_sizes.push_back(blob_length); // TODO(abarth): Precompute when we can use allocRunPosH. paint.setTypeface(GetTypefaceForGlyph(layout, blob_start)); - auto buffer = builder.allocRunPos(paint, blob_length); + buffers.push_back(&builder.allocRunPos(paint, blob_length)); letter_spacing_offset += run.style.letter_spacing; // Each Glyph/Letter. + bool whitespace_ended = true; for (size_t blob_index = 0; blob_index < blob_length; ++blob_index) { const size_t glyph_index = blob_start + blob_index; - buffer.glyphs[blob_index] = layout.getGlyphId(glyph_index); + buffers.back()->glyphs[blob_index] = layout.getGlyphId(glyph_index); + // Check if the current Glyph is a whitespace and handle multiple + // whitespaces in a row. + if (minikin::isWordSpace(text_[character_index])) { + // Only increment word_count if it is the first in a series of + // whitespaces. + if (whitespace_ended) + ++word_count; + whitespace_ended = false; + } else { + whitespace_ended = true; + } + ++character_index; const size_t pos_index = 2 * blob_index; - buffer.pos[pos_index] = layout.getX(glyph_index) + - letter_spacing_offset + word_spacing_offset; - buffer.pos[pos_index + 1] = layout.getY(glyph_index); + buffers.back()->pos[pos_index] = + layout.getX(glyph_index) + letter_spacing_offset; + buffers.back()->pos[pos_index + 1] = layout.getY(glyph_index); letter_spacing_offset += run.style.letter_spacing; } @@ -240,22 +258,21 @@ void Paragraph::Layout(double width, // removed depending on the specifications for letter spacing. // letter_spacing_offset -= run.style.letter_spacing; - word_spacing_offset += run.style.word_spacing; - max_intrinsic_width_ += layout.getX(blob_start - 1) + letter_spacing_offset; } - // Subtract word offset to avoid big gap at end of run. This my be - // removed depending on the specificatins for word spacing. - word_spacing_offset -= run.style.word_spacing; - // TODO(abarth): We could keep the same SkTextBlobBuilder as long as the // color stayed the same. // TODO(garyq): Ensure that the typeface does not change throughout a // run. SkPaint::FontMetrics metrics; paint.getFontMetrics(&metrics); + // Apply additional word spacing if the text is justified. + if (paragraph_style_.text_align == TextAlign::justify && + buffer_sizes.size() > 0) { + JustifyLine(buffers, buffer_sizes, word_count, character_index); + } records_.push_back( PaintRecord{run.style, builder.make(), metrics, lines_}); line_width += @@ -293,7 +310,7 @@ void Paragraph::Layout(double width, max_descent = 0.0f; x = 0.0f; letter_spacing_offset = 0.0f; - word_spacing_offset = 0.0f; + word_count = 0; line_width = 0.0f; // TODO(abarth): Use the line height, which is something like the max // font_size for runs in this line times the paragraph's line height. @@ -314,6 +331,45 @@ void Paragraph::Layout(double width, height_ = y + max_descent; } +// Amends the buffers to incorporate justification. +void Paragraph::JustifyLine( + std::vector& buffers, + std::vector& buffer_sizes, + int word_count, + size_t character_index) { + // TODO(garyq): Add letter_spacing_offset back in. It is Temporarily + // removed. + double justify_spacing = + (width_ - breaker_.getWidths()[lines_]) / (word_count - 1); + word_count = 0; + // Set up index to properly access text_ because minikin::isWordSpace() + // takes uint_16 instead of GlyphIDs. + size_t line_character_index = character_index; + for (size_t i = 0; i < buffers.size(); ++i) + line_character_index -= buffer_sizes[i]; + bool whitespace_ended = true; + for (size_t i = 0; i < buffers.size(); ++i) { + for (size_t glyph_index = 0; glyph_index < buffer_sizes[i]; ++glyph_index) { + // Check if the current Glyph is a whitespace and handle multiple + // whitespaces in a row. + if (minikin::isWordSpace(text_[line_character_index])) { + // Only increment word_count and add justification spacing to + // whitespace if it is the first in a series of whitespaces. + if (whitespace_ended) { + ++word_count; + buffers[i]->pos[glyph_index * 2] += justify_spacing * word_count; + } + whitespace_ended = false; + } else { + // Add justification spacing for all non-whitespace glyphs. + buffers[i]->pos[glyph_index * 2] += justify_spacing * word_count; + whitespace_ended = true; + } + ++line_character_index; + } + } +} + const ParagraphStyle& Paragraph::GetParagraphStyle() const { return paragraph_style_; } @@ -346,8 +402,8 @@ void Paragraph::SetParagraphStyle(const ParagraphStyle& style) { } void Paragraph::Paint(SkCanvas* canvas, double x, double y) { - SkPaint paint; for (const auto& record : records_) { + SkPaint paint; paint.setColor(record.style().color); SkPoint offset = record.offset(); // TODO(garyq): Fix alignment for paragraphs with multiple styles per line. @@ -355,15 +411,15 @@ void Paragraph::Paint(SkCanvas* canvas, double x, double y) { case TextAlign::left: break; case TextAlign::right: { - offset.offset(width_ - line_widths_[record.line()], 0); + offset.offset(width_ - breaker_.getWidths()[record.line()], 0); break; } case TextAlign::center: { - offset.offset((width_ - line_widths_[record.line()]) / 2, 0); + offset.offset((width_ - breaker_.getWidths()[record.line()]) / 2, 0); break; } case TextAlign::justify: { - // TODO(garyq): implement justify. + // Justify is performed in the Layout(). break; } } @@ -371,12 +427,6 @@ void Paragraph::Paint(SkCanvas* canvas, double x, double y) { PaintDecorations(canvas, x + offset.x(), y + offset.y(), record.style(), record.metrics(), record.text()); } - - paint.setStyle(SkPaint::kFill_Style); - paint.setAntiAlias(true); - paint.setStrokeWidth(4); - paint.setColor(0xffFE938C); - canvas->drawCircle(x, y, 3, paint); } void Paragraph::PaintDecorations(SkCanvas* canvas, diff --git a/src/paragraph.h b/src/paragraph.h index 93b28b6f02d..f71b16be11a 100644 --- a/src/paragraph.h +++ b/src/paragraph.h @@ -68,7 +68,10 @@ class Paragraph { FRIEND_TEST(RenderTest, RainbowParagraph); FRIEND_TEST(RenderTest, DefaultStyleParagraph); FRIEND_TEST(RenderTest, BoldParagraph); - FRIEND_TEST(RenderTest, LinebreakParagraph); + FRIEND_TEST(RenderTest, LeftAlignParagraph); + FRIEND_TEST(RenderTest, RightAlignParagraph); + FRIEND_TEST(RenderTest, CenterAlignParagraph); + FRIEND_TEST(RenderTest, JustifyAlignParagraph); FRIEND_TEST(RenderTest, ItalicsParagraph); std::vector text_; @@ -80,7 +83,7 @@ class Paragraph { // TODO(garyq): Height of the paragraph after Layout(). SkScalar height_ = 0.0f; double width_ = 0.0f; - int lines_ = 1; + size_t lines_ = 0; double max_intrinsic_width_ = 0.0f; double min_intrinsic_width_ = 0.0f; double alphabetic_baseline_ = FLT_MAX; @@ -93,6 +96,11 @@ class Paragraph { void AddRunsToLineBreaker(const std::string& rootdir = ""); + void JustifyLine(std::vector& buffers, + std::vector& buffer_sizes, + int word_count, + size_t character_index); + void PaintDecorations(SkCanvas* canvas, double x, double y, diff --git a/src/paragraph_style.h b/src/paragraph_style.h index e1730287a61..45f1dd745d6 100644 --- a/src/paragraph_style.h +++ b/src/paragraph_style.h @@ -32,7 +32,7 @@ class ParagraphStyle { FontStyle font_style = FontStyle::normal; std::string font_family = ""; double font_size = 14; - int max_lines = 1; + size_t max_lines = 1; double line_height = 1.0; std::string ellipsis; }; diff --git a/src/styled_runs.h b/src/styled_runs.h index 8b757658a44..4942b9cc20a 100644 --- a/src/styled_runs.h +++ b/src/styled_runs.h @@ -60,7 +60,10 @@ class StyledRuns { FRIEND_TEST(RenderTest, RainbowParagraph); FRIEND_TEST(RenderTest, DefaultStyleParagraph); FRIEND_TEST(RenderTest, BoldParagraph); - FRIEND_TEST(RenderTest, LinebreakParagraph); + FRIEND_TEST(RenderTest, LeftAlignParagraph); + FRIEND_TEST(RenderTest, RightAlignParagraph); + FRIEND_TEST(RenderTest, CenterAlignParagraph); + FRIEND_TEST(RenderTest, JustifyAlignParagraph); FRIEND_TEST(RenderTest, ItalicsParagraph); struct IndexedRun { diff --git a/tests/txt/paragraph_unittests.cc b/tests/txt/paragraph_unittests.cc index 463d18e5b00..e18cc23a784 100644 --- a/tests/txt/paragraph_unittests.cc +++ b/tests/txt/paragraph_unittests.cc @@ -252,28 +252,132 @@ TEST_F(RenderTest, BoldParagraph) { ASSERT_TRUE(Snapshot()); } -TEST_F(RenderTest, LinebreakParagraph) { +TEST_F(RenderTest, LeftAlignParagraph) { const char* text = "This is a very long sentence to test if the text will properly wrap " "around and go to the next line. Sometimes, short sentence. Longer " - "sentences are okay too because they are nessecary. Very short." + "sentences are okay too because they are nessecary. Very short. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " + "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " + "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum."; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + paragraph_style.max_lines = 14; + paragraph_style.text_align = TextAlign::left; + txt::ParagraphBuilder builder(paragraph_style); + + txt::TextStyle text_style; + text_style.font_size = 26; + text_style.letter_spacing = 1; + text_style.word_spacing = 5; + text_style.color = SK_ColorBLACK; + text_style.height = 1.15; + text_style.decoration = txt::TextDecoration(0x1); + text_style.decoration_color = SK_ColorBLACK; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(GetTestCanvasWidth() - 100, txt::GetFontDir()); + + paragraph->Paint(GetCanvas(), 0, 0); + 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]); + } + ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull); + ASSERT_EQ(paragraph->runs_.styles_.size(), 1ull); + ASSERT_TRUE(paragraph->runs_.styles_[0].equals(text_style)); + ASSERT_EQ(paragraph->records_[0].style().color, text_style.color); + ASSERT_TRUE(Snapshot()); +} + +TEST_F(RenderTest, RightAlignParagraph) { + const char* text = "This is a very long sentence to test if the text will properly wrap " "around and go to the next line. Sometimes, short sentence. Longer " - "sentences are okay too because they are nessecary. Very short." + "sentences are okay too because they are nessecary. Very short. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " - "mollit anim id est laborum." + "mollit anim id est laborum. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " - "mollit anim id est laborum." + "mollit anim id est laborum."; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + paragraph_style.max_lines = 14; + paragraph_style.text_align = TextAlign::right; + txt::ParagraphBuilder builder(paragraph_style); + + txt::TextStyle text_style; + text_style.font_size = 26; + text_style.letter_spacing = 1; + text_style.word_spacing = 5; + text_style.color = SK_ColorBLACK; + text_style.height = 1.15; + text_style.decoration = txt::TextDecoration(0x1); + text_style.decoration_color = SK_ColorBLACK; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(GetTestCanvasWidth() - 100, txt::GetFontDir()); + + paragraph->Paint(GetCanvas(), 0, 0); + 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]); + } + ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull); + ASSERT_EQ(paragraph->runs_.styles_.size(), 1ull); + ASSERT_TRUE(paragraph->runs_.styles_[0].equals(text_style)); + ASSERT_EQ(paragraph->records_[0].style().color, text_style.color); + ASSERT_TRUE(Snapshot()); +} + +TEST_F(RenderTest, CenterAlignParagraph) { + const char* text = + "This is a very long sentence to test if the text will properly wrap " + "around and go to the next line. Sometimes, short sentence. Longer " + "sentences are okay too because they are nessecary. Very short. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " + "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum. " "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " @@ -292,8 +396,8 @@ TEST_F(RenderTest, LinebreakParagraph) { txt::TextStyle text_style; text_style.font_size = 26; - // Letter spacing not yet implemented - text_style.letter_spacing = 0; + text_style.letter_spacing = 1; + text_style.word_spacing = 5; text_style.color = SK_ColorBLACK; text_style.height = 1.15; text_style.decoration = txt::TextDecoration(0x1); @@ -307,8 +411,64 @@ TEST_F(RenderTest, LinebreakParagraph) { auto paragraph = builder.Build(); paragraph->Layout(GetTestCanvasWidth() - 100, txt::GetFontDir()); - paragraph->Paint(GetCanvas(), 0, 30.0); + paragraph->Paint(GetCanvas(), 0, 0); + 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]); + } + ASSERT_EQ(paragraph->runs_.runs_.size(), 1ull); + ASSERT_EQ(paragraph->runs_.styles_.size(), 1ull); + ASSERT_TRUE(paragraph->runs_.styles_[0].equals(text_style)); + ASSERT_EQ(paragraph->records_[0].style().color, text_style.color); + ASSERT_TRUE(Snapshot()); +} +TEST_F(RenderTest, JustifyAlignParagraph) { + const char* text = + "This is a very long sentence to test if the text will properly wrap " + "around and go to the next line. Sometimes, short sentence. Longer " + "sentences are okay too because they are nessecary. Very short. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " + "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim " + "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea " + "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate " + "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint " + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt " + "mollit anim id est laborum."; + auto icu_text = icu::UnicodeString::fromUTF8(text); + std::u16string u16_text(icu_text.getBuffer(), + icu_text.getBuffer() + icu_text.length()); + + txt::ParagraphStyle paragraph_style; + paragraph_style.max_lines = 14; + paragraph_style.text_align = TextAlign::justify; + txt::ParagraphBuilder builder(paragraph_style); + + txt::TextStyle text_style; + text_style.font_size = 26; + text_style.letter_spacing = 1; + text_style.word_spacing = 5; + text_style.color = SK_ColorBLACK; + text_style.height = 1.15; + text_style.decoration = txt::TextDecoration(0x1); + text_style.decoration_color = SK_ColorBLACK; + builder.PushStyle(text_style); + + builder.AddText(u16_text); + + builder.Pop(); + + auto paragraph = builder.Build(); + paragraph->Layout(GetTestCanvasWidth() - 100, txt::GetFontDir()); + + paragraph->Paint(GetCanvas(), 0, 0); 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]); @@ -321,7 +481,7 @@ TEST_F(RenderTest, LinebreakParagraph) { } TEST_F(RenderTest, ItalicsParagraph) { - const char* text = "I am Italicized!"; + const char* text = "I am Italicized! "; auto icu_text = icu::UnicodeString::fromUTF8(text); std::u16string u16_text(icu_text.getBuffer(), icu_text.getBuffer() + icu_text.length()); diff --git a/tests/txt/render_test.cc b/tests/txt/render_test.cc index 68b45e98ac4..4b87a06bbb9 100644 --- a/tests/txt/render_test.cc +++ b/tests/txt/render_test.cc @@ -47,7 +47,7 @@ bool RenderTest::Snapshot() { } size_t RenderTest::GetTestCanvasWidth() const { - return 800; + return 1000; } size_t RenderTest::GetTestCanvasHeight() const {