Ellipsizing implementation for libtxt (flutter/engine#4048)
This commit is contained in:
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user