Ellipsizing implementation for libtxt (flutter/engine#4048)

This commit is contained in:
Jason Simmons
2017-09-01 14:17:04 -07:00
committed by GitHub
parent 2f758c5e95
commit dd2b2543f6
7 changed files with 99 additions and 15 deletions

View File

@@ -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();
@@ -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();
}
@@ -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);
}
@@ -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;
@@ -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());

View File

@@ -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;
@@ -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();

View File

@@ -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;
@@ -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)));

View File

@@ -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_;

View File

@@ -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).

View File

@@ -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;

View File

@@ -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