提交 8eab44c6 编写于 作者: J Jason Simmons 提交者: GitHub

Implement ellipsizing of text in the engine (#3056)

See https://github.com/flutter/flutter/issues/4478
上级 2d585bc4
......@@ -93,6 +93,15 @@ enum TextAlign {
center
}
/// How to handle text that overflows its bounds
enum TextOverflow {
/// Default
clip,
/// Render as a single line that is ellipsized if it is too wide to fit
ellipsis
}
/// A horizontal line used for aligning text
enum TextBaseline {
// The horizontal line used to align the bottom of glyphs for alphabetic characters
......@@ -375,13 +384,16 @@ class TextStyle {
//
// - Element 3: The enum index of the |fontStyle|.
//
// - Element 4: The enum index of the |textOverflow|.
//
Int32List _encodeParagraphStyle(TextAlign textAlign,
FontWeight fontWeight,
FontStyle fontStyle,
TextOverflow textOverflow,
String fontFamily,
double fontSize,
double lineHeight) {
Int32List result = new Int32List(4);
Int32List result = new Int32List(5);
if (textAlign != null) {
result[0] |= 1 << 1;
result[1] = textAlign.index;
......@@ -394,16 +406,20 @@ Int32List _encodeParagraphStyle(TextAlign textAlign,
result[0] |= 1 << 3;
result[3] = fontStyle.index;
}
if (fontFamily != null) {
if (textOverflow != null) {
result[0] |= 1 << 4;
result[4] = textOverflow.index;
}
if (fontFamily != null) {
result[0] |= 1 << 5;
// Passed separately to native.
}
if (fontSize != null) {
result[0] |= 1 << 5;
result[0] |= 1 << 6;
// Passed separately to native.
}
if (lineHeight != null) {
result[0] |= 1 << 6;
result[0] |= 1 << 7;
// Passed separately to native.
}
return result;
......@@ -423,12 +439,14 @@ class ParagraphStyle {
TextAlign textAlign,
FontWeight fontWeight,
FontStyle fontStyle,
TextOverflow textOverflow,
String fontFamily,
double fontSize,
double lineHeight
}) : _encoded = _encodeParagraphStyle(textAlign,
fontWeight,
fontStyle,
textOverflow,
fontFamily,
fontSize,
lineHeight),
......@@ -465,9 +483,10 @@ class ParagraphStyle {
'textAlign: ${ _encoded[0] & 0x02 == 0x02 ? TextAlign.values[_encoded[1]] : "unspecified"}, '
'fontWeight: ${ _encoded[0] & 0x04 == 0x04 ? FontWeight.values[_encoded[2]] : "unspecified"}, '
'fontStyle: ${ _encoded[0] & 0x08 == 0x08 ? FontStyle.values[_encoded[3]] : "unspecified"}, '
'fontFamily: ${ _encoded[0] & 0x10 == 0x10 ? _fontFamily : "unspecified"}, '
'fontSize: ${ _encoded[0] & 0x20 == 0x20 ? _fontSize : "unspecified"}, '
'lineHeight: ${ _encoded[0] & 0x40 == 0x40 ? "${_lineHeight}x" : "unspecified"}'
'textOverflow: ${ _encoded[0] & 0x10 == 0x10 ? TextOverflow.values[_encoded[4]] : "unspecified"}, '
'fontFamily: ${ _encoded[0] & 0x20 == 0x20 ? _fontFamily : "unspecified"}, '
'fontSize: ${ _encoded[0] & 0x40 == 0x40 ? _fontSize : "unspecified"}, '
'lineHeight: ${ _encoded[0] & 0x80 == 0x80 ? "${_lineHeight}x" : "unspecified"}'
')';
}
}
......
......@@ -95,13 +95,15 @@ const int tsHeightMask = 1 << tsHeightIndex;
const int psTextAlignIndex = 1;
const int psFontWeightIndex = 2;
const int psFontStyleIndex = 3;
const int psFontFamilyIndex = 4;
const int psFontSizeIndex = 5;
const int psLineHeightIndex = 6;
const int psTextOverflowIndex = 4;
const int psFontFamilyIndex = 5;
const int psFontSizeIndex = 6;
const int psLineHeightIndex = 7;
const int psTextAlignMask = 1 << psTextAlignIndex;
const int psFontWeightMask = 1 << psFontWeightIndex;
const int psFontStyleMask = 1 << psFontStyleIndex;
const int psTextOverflowMask = 1 << psTextOverflowIndex;
const int psFontFamilyMask = 1 << psFontFamilyIndex;
const int psFontSizeMask = 1 << psFontSizeIndex;
const int psLineHeightMask = 1 << psLineHeightIndex;
......@@ -241,7 +243,7 @@ ftl::RefPtr<Paragraph> ParagraphBuilder::build(tonic::Int32List& encoded,
const std::string& fontFamily,
double fontSize,
double lineHeight) {
FTL_DCHECK(encoded.num_elements() == 4);
FTL_DCHECK(encoded.num_elements() == 5);
int32_t mask = encoded[0];
if (mask) {
......@@ -282,6 +284,11 @@ ftl::RefPtr<Paragraph> ParagraphBuilder::build(tonic::Int32List& encoded,
if (mask & psLineHeightMask)
style->setLineHeight(Length(lineHeight * 100.0, Percent));
if (mask & psTextOverflowMask) {
style->setTextOverflow(
static_cast<TextOverflow>(encoded[psTextOverflowIndex]));
}
m_renderParagraph->setStyle(style.release());
}
......
......@@ -41,6 +41,7 @@ struct BidiRun : BidiCharacterRun {
{
// Stored in base class to save space.
m_hasHyphen = false;
m_hasAddedEllipsis = false;
}
BidiRun* next() { return static_cast<BidiRun*>(m_next); }
......
......@@ -296,6 +296,7 @@ public:
, m_hasEllipsisBoxOrHyphen(false)
, m_dirOverride(false)
, m_isText(false)
, m_hasAddedEllipsis(false)
, m_determinedIfNextOnLineExists(false)
, m_nextOnLineExists(false)
, m_expansion(0)
......@@ -326,6 +327,7 @@ public:
// for InlineTextBox
ADD_BOOLEAN_BITFIELD(dirOverride, DirOverride);
ADD_BOOLEAN_BITFIELD(isText, IsText); // Whether or not this object represents text with a non-zero height. Includes non-image list markers, text boxes.
ADD_BOOLEAN_BITFIELD(hasAddedEllipsis, HasAddedEllipsis)
private:
mutable unsigned m_determinedIfNextOnLineExists : 1;
......@@ -373,6 +375,8 @@ protected:
void setCanHaveLeadingExpansion(bool canHaveLeadingExpansion) { m_bitfields.setHasSelectedChildrenOrCanHaveLeadingExpansion(canHaveLeadingExpansion); }
signed expansion() { return m_bitfields.expansion(); }
void setExpansion(signed expansion) { m_bitfields.setExpansion(expansion); }
bool hasAddedEllipsis() const { return m_bitfields.hasAddedEllipsis(); }
void setHasAddedEllipsis(bool hasAddedEllipsis) { m_bitfields.setHasAddedEllipsis(hasAddedEllipsis); }
// For InlineFlowBox and InlineTextBox
bool extracted() const { return m_bitfields.extracted(); }
......
......@@ -450,9 +450,18 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
string.narrow(m_start, length);
maximumLength = renderer().textLength() - m_start;
StringBuilder charactersWithEllipsis;
if (hasAddedEllipsis()) {
charactersWithEllipsis.reserveCapacity(string.length() + 1);
charactersWithEllipsis.append(string);
charactersWithEllipsis.append(WTF::Unicode::horizontalEllipsis);
string = charactersWithEllipsis.toString().createView();
maximumLength = string.length();
}
StringBuilder charactersWithHyphen;
TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0);
if (hasHyphen())
if (hasHyphen() || hasAddedEllipsis())
length = textRun.length();
int sPos = 0;
......
......@@ -76,6 +76,7 @@ public:
using InlineBox::hasHyphen;
using InlineBox::setHasHyphen;
using InlineBox::setHasAddedEllipsis;
using InlineBox::canHaveLeadingExpansion;
using InlineBox::setCanHaveLeadingExpansion;
......
......@@ -515,6 +515,8 @@ RootInlineBox* RenderParagraph::constructLine(BidiRunList<BidiRun>& bidiRuns, co
text->setDirOverride(r->dirOverride());
if (r->m_hasHyphen)
text->setHasHyphen(true);
if (r->m_hasAddedEllipsis)
text->setHasAddedEllipsis(true);
}
}
......@@ -909,6 +911,8 @@ void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState,
if (bidiRuns.runCount() && lineBreaker.lineWasHyphenated())
bidiRuns.logicallyLastRun()->m_hasHyphen = true;
if (bidiRuns.runCount() && lineBreaker.lineWasEllipsized())
bidiRuns.logicallyLastRun()->m_hasAddedEllipsis = true;
// Now that the runs have been ordered, we create the line boxes.
// At the same time we figure out where border/padding/margin should be applied for
......@@ -928,6 +932,12 @@ void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState,
lineMidpointState.reset();
resolver.setPosition(endOfLine, numberOfIsolateAncestors(endOfLine));
// Limit ellipsized text to a single line.
if (lineBreaker.lineWasEllipsized()) {
resolver.setPosition(InlineIterator(resolver.position().root(), 0, 0), 0);
break;
}
}
}
......
......@@ -88,7 +88,7 @@ public:
void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects);
void handleEmptyInline();
void handleReplaced();
bool handleText(WordMeasurements&, bool& hyphenated);
bool handleText(WordMeasurements&, bool& hyphenated, bool& ellipsized);
void commitAndUpdateLineBreakIfNeeded();
InlineIterator handleEndOfLine();
......@@ -409,6 +409,13 @@ inline float measureHyphenWidth(RenderText* renderer, const Font& font, TextDire
style->hyphenString().string(), style, style->direction()));
}
inline float measureEllipsisWidth(RenderText* renderer, const Font& font)
{
RenderStyle* style = renderer->style();
return font.width(constructTextRun(renderer, font,
String(&WTF::Unicode::horizontalEllipsis, 1), style, style->direction()));
}
ALWAYS_INLINE TextDirection textDirectionFromUnicode(WTF::Unicode::Direction direction)
{
return direction == WTF::Unicode::RightToLeft
......@@ -428,7 +435,7 @@ ALWAYS_INLINE float textWidth(RenderText* text, unsigned from, unsigned len, con
return font.width(run, fallbackFonts, &glyphOverflow);
}
inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated)
inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool& hyphenated, bool& ellipsized)
{
if (!m_current.offset())
m_appliedStartWidth = false;
......@@ -458,8 +465,17 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
bool breakWords = m_currentStyle->breakWords() && ((m_autoWrap && !m_width.committedWidth()) || m_currWS == PRE);
bool midWordBreak = false;
bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap;
float hyphenWidth = 0;
bool ellipsizeMode = m_blockStyle->textOverflow() == TextOverflowEllipsis;
float ellipsisWidth = 0;
unsigned ellipsisBreakOffset = 0;
if (ellipsizeMode) {
ellipsisWidth = measureEllipsisWidth(renderText, font);
breakAll = true;
}
if (m_renderTextInfo.m_text != renderText) {
m_renderTextInfo.m_text = renderText;
m_renderTextInfo.m_font = &font;
......@@ -496,7 +512,14 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
wrapW += charWidth;
bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText->textLength() && U16_IS_TRAIL((*renderText)[m_current.offset() + 1]);
charWidth = textWidth(renderText, m_current.offset(), midWordBreakIsBeforeSurrogatePair ? 2 : 1, font, m_width.committedWidth() + wrapW, isFixedPitch, m_collapseWhiteSpace);
midWordBreak = m_width.committedWidth() + wrapW + charWidth > m_width.availableWidth();
float midWordWidth = m_width.committedWidth() + wrapW + charWidth;
midWordBreak = midWordWidth > m_width.availableWidth();
// Check whether there is enough space to fit this character plus an ellipsis.
if (ellipsizeMode && midWordWidth + ellipsisWidth <= m_width.availableWidth()) {
ellipsisBreakOffset = m_current.offset();
}
}
int nextBreakablePosition = m_current.nextBreakablePosition();
......@@ -584,6 +607,13 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
wordMeasurement.width = charWidth;
}
}
if (ellipsizeMode) {
// Break the line at the position where an ellipsis would fit.
m_lineBreak.moveTo(m_current.object(), ellipsisBreakOffset, m_current.nextBreakablePosition());
ellipsized = true;
}
// Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace.
if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) {
m_atEnd = true;
......
......@@ -45,6 +45,7 @@ void LineBreaker::reset()
{
m_positionedObjects.clear();
m_hyphenated = false;
m_ellipsized = false;
}
InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo& lineInfo,
......@@ -75,7 +76,7 @@ InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo
} else if (context.currentObject()->isReplaced()) {
context.handleReplaced();
} else if (context.currentObject()->isText()) {
if (context.handleText(wordMeasurements, m_hyphenated)) {
if (context.handleText(wordMeasurements, m_hyphenated, m_ellipsized)) {
// We've hit a hard text line break. Our line break iterator is updated, so go ahead and early return.
return context.lineBreak();
}
......
......@@ -46,6 +46,7 @@ public:
FloatingObject* lastFloatFromPreviousLine, WordMeasurements&);
bool lineWasHyphenated() { return m_hyphenated; }
bool lineWasEllipsized() { return m_ellipsized; }
const Vector<RenderBox*>& positionedObjects() { return m_positionedObjects; }
private:
void reset();
......@@ -54,6 +55,7 @@ private:
RenderParagraph* m_block;
bool m_hyphenated;
bool m_ellipsized;
Vector<RenderBox*> m_positionedObjects;
};
......
......@@ -69,6 +69,7 @@ struct BidiCharacterRun {
// Do not add anything apart from bitfields until after m_next. See https://bugs.webkit.org/show_bug.cgi?id=100173
bool m_override : 1;
bool m_hasHyphen : 1; // Used by BidiRun subclass which is a layering violation but enables us to save 8 bytes per object on 64-bit.
bool m_hasAddedEllipsis : 1;
unsigned char m_level;
BidiCharacterRun* m_next;
int m_start;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册