Skip to content

Commit

Permalink
Ellipsizing implementation for libtxt (flutter#4048)
Browse files Browse the repository at this point in the history
  • Loading branch information
jason-simmons authored Sep 1, 2017
1 parent 8b3e746 commit 1c6433f
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 15 deletions.
15 changes: 9 additions & 6 deletions lib/ui/text/paragraph_builder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ PassRefPtr<RenderStyle> decodeParagraphStyle(RenderStyle* parentStyle,
const std::string& fontFamily,
double fontSize,
double lineHeight,
const std::string& ellipsis) {
const std::u16string& ellipsis) {
FTL_DCHECK(encoded.num_elements() == 5);

RefPtr<RenderStyle> style = RenderStyle::create();
Expand Down Expand Up @@ -155,8 +155,10 @@ PassRefPtr<RenderStyle> decodeParagraphStyle(RenderStyle* parentStyle,
if (mask & psMaxLinesMask)
style->setMaxLines(encoded[psMaxLinesIndex]);

if (mask & psEllipsisMask)
style->setEllipsis(AtomicString::fromUTF8(ellipsis.c_str()));
if (mask & psEllipsisMask) {
style->setEllipsis(
AtomicString(reinterpret_cast<const UChar*>(ellipsis.c_str())));
}

return style.release();
}
Expand Down Expand Up @@ -193,7 +195,7 @@ ftl::RefPtr<ParagraphBuilder> ParagraphBuilder::create(
const std::string& fontFamily,
double fontSize,
double lineHeight,
const std::string& ellipsis) {
const std::u16string& ellipsis) {
return ftl::MakeRefCounted<ParagraphBuilder>(encoded, fontFamily, fontSize,
lineHeight, ellipsis);
}
Expand All @@ -202,7 +204,7 @@ ParagraphBuilder::ParagraphBuilder(tonic::Int32List& encoded,
const std::string& fontFamily,
double fontSize,
double lineHeight,
const std::string& ellipsis) {
const std::u16string& ellipsis) {
if (!Settings::Get().using_blink) {
int32_t mask = encoded[0];
txt::ParagraphStyle style;
Expand Down Expand Up @@ -232,8 +234,9 @@ ParagraphBuilder::ParagraphBuilder(tonic::Int32List& encoded,
if (mask & psMaxLinesMask)
style.max_lines = encoded[psMaxLinesIndex];

if (mask & psEllipsisMask)
if (mask & psEllipsisMask) {
style.ellipsis = ellipsis;
}

m_paragraphBuilder = std::make_unique<txt::ParagraphBuilder>(
style, blink::FontCollection::ForProcess().GetFontCollection());
Expand Down
4 changes: 2 additions & 2 deletions lib/ui/text/paragraph_builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class ParagraphBuilder : public ftl::RefCountedThreadSafe<ParagraphBuilder>,
const std::string& fontFamily,
double fontSize,
double lineHeight,
const std::string& ellipsis);
const std::u16string& ellipsis);

~ParagraphBuilder() override;

Expand All @@ -55,7 +55,7 @@ class ParagraphBuilder : public ftl::RefCountedThreadSafe<ParagraphBuilder>,
const std::string& fontFamily,
double fontSize,
double lineHeight,
const std::string& ellipsis);
const std::u16string& ellipsis);

void createRenderView();

Expand Down
57 changes: 52 additions & 5 deletions third_party/txt/src/txt/paragraph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ void Paragraph::Layout(double width, bool force) {
std::vector<const SkTextBlobBuilder::RunBuffer*> buffers;
std::vector<size_t> buffer_sizes;
int word_count = 0;
size_t max_lines = paragraph_style_.max_lines;

auto postprocess_line = [this, &x_queue, &y]() -> void {
size_t record_index = 0;
Expand Down Expand Up @@ -326,22 +327,68 @@ void Paragraph::Layout(double width, bool force) {

size_t layout_start = run.start;
// Layout until the end of the run or too many lines.
while (layout_start < run.end && lines_ < paragraph_style_.max_lines) {
while (layout_start < run.end && lines_ < max_lines) {
const size_t next_break = (break_index > breaks_count - 1)
? std::numeric_limits<size_t>::max()
: breaks[break_index];
const size_t layout_end = std::min(run.end, next_break);

bool bidiFlags = paragraph_style_.rtl;
std::shared_ptr<minikin::FontCollection> minikin_font_collection =
font_collection_->GetMinikinFontCollectionForFamily(
run.style.font_family);

uint16_t* text_ptr = text.data() + layout_start;
size_t text_count = layout_end - layout_start;
std::vector<uint16_t> ellipsized_text;

// Apply ellipsizing if the run was not completely laid out and this
// is the last line (or lines are unlimited).
const std::u16string& ellipsis = paragraph_style_.ellipsis;
if (ellipsis.length() && !isinf(width_) && run.end != layout_end &&
(lines_ == max_lines - 1 ||
max_lines == std::numeric_limits<size_t>::max())) {
float ellipsis_width = layout.measureText(
reinterpret_cast<const uint16_t*>(ellipsis.data()),
0, ellipsis.length(), ellipsis.length(), bidiFlags,
font, minikin_paint, minikin_font_collection, nullptr);

std::vector<float> text_advances(text_count);
float text_width = layout.measureText(
text.data() + layout_start, 0, text_count, text_count,
bidiFlags, font, minikin_paint, minikin_font_collection,
text_advances.data());

// Truncate characters from the text until the ellipsis fits.
size_t truncate_count = 0;
while (truncate_count < text_count &&
text_width + ellipsis_width > width_) {
text_width -= text_advances[text_count - truncate_count - 1];
truncate_count++;
}

ellipsized_text.reserve(text_count - truncate_count + ellipsis.length());
ellipsized_text.insert(ellipsized_text.begin(),
text.begin() + layout_start,
text.begin() + layout_end - truncate_count);
ellipsized_text.insert(ellipsized_text.end(),
ellipsis.begin(), ellipsis.end());
text_ptr = ellipsized_text.data();
text_count = ellipsized_text.size();

// If there is no line limit, then skip all lines after the ellipsized
// line.
if (max_lines == std::numeric_limits<size_t>::max())
max_lines = lines_ + 1;
}

// Minikin Layout doLayout() 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 paragraphs. It is
// currently recommended to break up very long paragraphs (10k+
// characters) to ensure speedy layout.
layout.doLayout(text.data() + layout_start, 0, layout_end - layout_start,
layout_end - layout_start, bidiFlags, font, minikin_paint,
font_collection_->GetMinikinFontCollectionForFamily(
run.style.font_family));
layout.doLayout(text_ptr, 0, text_count, text_count,
bidiFlags, font, minikin_paint, minikin_font_collection);
FillWhitespaceSet(layout_start, layout_end,
minikin::getHbFontLocked(layout.getFont(0)));

Expand Down
1 change: 1 addition & 0 deletions third_party/txt/src/txt/paragraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ class Paragraph {
FRIEND_TEST(ParagraphTest, EmojiParagraph);
FRIEND_TEST(ParagraphTest, HyphenBreakParagraph);
FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);
FRIEND_TEST(ParagraphTest, Ellipsize);

// Starting data to layout.
std::vector<uint16_t> text_;
Expand Down
4 changes: 2 additions & 2 deletions third_party/txt/src/txt/paragraph_style.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ class ParagraphStyle {
double font_size = 14;

TextAlign text_align = TextAlign::left;
size_t max_lines = UINT_MAX;
size_t max_lines = std::numeric_limits<size_t>::max();
double line_height = 1.0;
std::string ellipsis = "...";
std::u16string ellipsis;
// Default strategy is kBreakStrategy_Greedy. Sometimes,
// kBreakStrategy_HighQuality will produce more desireable layouts (eg, very
// long words are more likely to be reasonably placed).
Expand Down
1 change: 1 addition & 0 deletions third_party/txt/src/txt/styled_runs.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class StyledRuns {
FRIEND_TEST(ParagraphTest, KernParagraph);
FRIEND_TEST(ParagraphTest, HyphenBreakParagraph);
FRIEND_TEST(ParagraphTest, RepeatLayoutParagraph);
FRIEND_TEST(ParagraphTest, Ellipsize);

struct IndexedRun {
size_t style_index = 0;
Expand Down
32 changes: 32 additions & 0 deletions third_party/txt/tests/paragraph_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1522,4 +1522,36 @@ TEST_F(ParagraphTest, RepeatLayoutParagraph) {
ASSERT_TRUE(Snapshot());
}

TEST_F(ParagraphTest, Ellipsize) {
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. ";
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.ellipsis = u"\u2026";
txt::ParagraphBuilder builder(paragraph_style, GetTestFontCollection());

txt::TextStyle text_style;
text_style.color = SK_ColorBLACK;
builder.PushStyle(text_style);
builder.AddText(u16_text);

builder.Pop();

auto paragraph = builder.Build();
paragraph->Layout(GetTestCanvasWidth());

paragraph->Paint(GetCanvas(), 0, 0);

ASSERT_TRUE(Snapshot());

// Check that the ellipsizer limited the text to one line and did not wrap
// to a second line.
ASSERT_EQ(paragraph->records_.size(), 1ull);
}

} // namespace txt

0 comments on commit 1c6433f

Please sign in to comment.