提交 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 { ...@@ -93,6 +93,15 @@ enum TextAlign {
center 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 /// A horizontal line used for aligning text
enum TextBaseline { enum TextBaseline {
// The horizontal line used to align the bottom of glyphs for alphabetic characters // The horizontal line used to align the bottom of glyphs for alphabetic characters
...@@ -375,13 +384,16 @@ class TextStyle { ...@@ -375,13 +384,16 @@ class TextStyle {
// //
// - Element 3: The enum index of the |fontStyle|. // - Element 3: The enum index of the |fontStyle|.
// //
// - Element 4: The enum index of the |textOverflow|.
//
Int32List _encodeParagraphStyle(TextAlign textAlign, Int32List _encodeParagraphStyle(TextAlign textAlign,
FontWeight fontWeight, FontWeight fontWeight,
FontStyle fontStyle, FontStyle fontStyle,
TextOverflow textOverflow,
String fontFamily, String fontFamily,
double fontSize, double fontSize,
double lineHeight) { double lineHeight) {
Int32List result = new Int32List(4); Int32List result = new Int32List(5);
if (textAlign != null) { if (textAlign != null) {
result[0] |= 1 << 1; result[0] |= 1 << 1;
result[1] = textAlign.index; result[1] = textAlign.index;
...@@ -394,16 +406,20 @@ Int32List _encodeParagraphStyle(TextAlign textAlign, ...@@ -394,16 +406,20 @@ Int32List _encodeParagraphStyle(TextAlign textAlign,
result[0] |= 1 << 3; result[0] |= 1 << 3;
result[3] = fontStyle.index; result[3] = fontStyle.index;
} }
if (fontFamily != null) { if (textOverflow != null) {
result[0] |= 1 << 4; result[0] |= 1 << 4;
result[4] = textOverflow.index;
}
if (fontFamily != null) {
result[0] |= 1 << 5;
// Passed separately to native. // Passed separately to native.
} }
if (fontSize != null) { if (fontSize != null) {
result[0] |= 1 << 5; result[0] |= 1 << 6;
// Passed separately to native. // Passed separately to native.
} }
if (lineHeight != null) { if (lineHeight != null) {
result[0] |= 1 << 6; result[0] |= 1 << 7;
// Passed separately to native. // Passed separately to native.
} }
return result; return result;
...@@ -423,12 +439,14 @@ class ParagraphStyle { ...@@ -423,12 +439,14 @@ class ParagraphStyle {
TextAlign textAlign, TextAlign textAlign,
FontWeight fontWeight, FontWeight fontWeight,
FontStyle fontStyle, FontStyle fontStyle,
TextOverflow textOverflow,
String fontFamily, String fontFamily,
double fontSize, double fontSize,
double lineHeight double lineHeight
}) : _encoded = _encodeParagraphStyle(textAlign, }) : _encoded = _encodeParagraphStyle(textAlign,
fontWeight, fontWeight,
fontStyle, fontStyle,
textOverflow,
fontFamily, fontFamily,
fontSize, fontSize,
lineHeight), lineHeight),
...@@ -465,9 +483,10 @@ class ParagraphStyle { ...@@ -465,9 +483,10 @@ class ParagraphStyle {
'textAlign: ${ _encoded[0] & 0x02 == 0x02 ? TextAlign.values[_encoded[1]] : "unspecified"}, ' 'textAlign: ${ _encoded[0] & 0x02 == 0x02 ? TextAlign.values[_encoded[1]] : "unspecified"}, '
'fontWeight: ${ _encoded[0] & 0x04 == 0x04 ? FontWeight.values[_encoded[2]] : "unspecified"}, ' 'fontWeight: ${ _encoded[0] & 0x04 == 0x04 ? FontWeight.values[_encoded[2]] : "unspecified"}, '
'fontStyle: ${ _encoded[0] & 0x08 == 0x08 ? FontStyle.values[_encoded[3]] : "unspecified"}, ' 'fontStyle: ${ _encoded[0] & 0x08 == 0x08 ? FontStyle.values[_encoded[3]] : "unspecified"}, '
'fontFamily: ${ _encoded[0] & 0x10 == 0x10 ? _fontFamily : "unspecified"}, ' 'textOverflow: ${ _encoded[0] & 0x10 == 0x10 ? TextOverflow.values[_encoded[4]] : "unspecified"}, '
'fontSize: ${ _encoded[0] & 0x20 == 0x20 ? _fontSize : "unspecified"}, ' 'fontFamily: ${ _encoded[0] & 0x20 == 0x20 ? _fontFamily : "unspecified"}, '
'lineHeight: ${ _encoded[0] & 0x40 == 0x40 ? "${_lineHeight}x" : "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; ...@@ -95,13 +95,15 @@ const int tsHeightMask = 1 << tsHeightIndex;
const int psTextAlignIndex = 1; const int psTextAlignIndex = 1;
const int psFontWeightIndex = 2; const int psFontWeightIndex = 2;
const int psFontStyleIndex = 3; const int psFontStyleIndex = 3;
const int psFontFamilyIndex = 4; const int psTextOverflowIndex = 4;
const int psFontSizeIndex = 5; const int psFontFamilyIndex = 5;
const int psLineHeightIndex = 6; const int psFontSizeIndex = 6;
const int psLineHeightIndex = 7;
const int psTextAlignMask = 1 << psTextAlignIndex; const int psTextAlignMask = 1 << psTextAlignIndex;
const int psFontWeightMask = 1 << psFontWeightIndex; const int psFontWeightMask = 1 << psFontWeightIndex;
const int psFontStyleMask = 1 << psFontStyleIndex; const int psFontStyleMask = 1 << psFontStyleIndex;
const int psTextOverflowMask = 1 << psTextOverflowIndex;
const int psFontFamilyMask = 1 << psFontFamilyIndex; const int psFontFamilyMask = 1 << psFontFamilyIndex;
const int psFontSizeMask = 1 << psFontSizeIndex; const int psFontSizeMask = 1 << psFontSizeIndex;
const int psLineHeightMask = 1 << psLineHeightIndex; const int psLineHeightMask = 1 << psLineHeightIndex;
...@@ -241,7 +243,7 @@ ftl::RefPtr<Paragraph> ParagraphBuilder::build(tonic::Int32List& encoded, ...@@ -241,7 +243,7 @@ ftl::RefPtr<Paragraph> ParagraphBuilder::build(tonic::Int32List& encoded,
const std::string& fontFamily, const std::string& fontFamily,
double fontSize, double fontSize,
double lineHeight) { double lineHeight) {
FTL_DCHECK(encoded.num_elements() == 4); FTL_DCHECK(encoded.num_elements() == 5);
int32_t mask = encoded[0]; int32_t mask = encoded[0];
if (mask) { if (mask) {
...@@ -282,6 +284,11 @@ ftl::RefPtr<Paragraph> ParagraphBuilder::build(tonic::Int32List& encoded, ...@@ -282,6 +284,11 @@ ftl::RefPtr<Paragraph> ParagraphBuilder::build(tonic::Int32List& encoded,
if (mask & psLineHeightMask) if (mask & psLineHeightMask)
style->setLineHeight(Length(lineHeight * 100.0, Percent)); style->setLineHeight(Length(lineHeight * 100.0, Percent));
if (mask & psTextOverflowMask) {
style->setTextOverflow(
static_cast<TextOverflow>(encoded[psTextOverflowIndex]));
}
m_renderParagraph->setStyle(style.release()); m_renderParagraph->setStyle(style.release());
} }
......
...@@ -41,6 +41,7 @@ struct BidiRun : BidiCharacterRun { ...@@ -41,6 +41,7 @@ struct BidiRun : BidiCharacterRun {
{ {
// Stored in base class to save space. // Stored in base class to save space.
m_hasHyphen = false; m_hasHyphen = false;
m_hasAddedEllipsis = false;
} }
BidiRun* next() { return static_cast<BidiRun*>(m_next); } BidiRun* next() { return static_cast<BidiRun*>(m_next); }
......
...@@ -296,6 +296,7 @@ public: ...@@ -296,6 +296,7 @@ public:
, m_hasEllipsisBoxOrHyphen(false) , m_hasEllipsisBoxOrHyphen(false)
, m_dirOverride(false) , m_dirOverride(false)
, m_isText(false) , m_isText(false)
, m_hasAddedEllipsis(false)
, m_determinedIfNextOnLineExists(false) , m_determinedIfNextOnLineExists(false)
, m_nextOnLineExists(false) , m_nextOnLineExists(false)
, m_expansion(0) , m_expansion(0)
...@@ -326,6 +327,7 @@ public: ...@@ -326,6 +327,7 @@ public:
// for InlineTextBox // for InlineTextBox
ADD_BOOLEAN_BITFIELD(dirOverride, DirOverride); 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(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: private:
mutable unsigned m_determinedIfNextOnLineExists : 1; mutable unsigned m_determinedIfNextOnLineExists : 1;
...@@ -373,6 +375,8 @@ protected: ...@@ -373,6 +375,8 @@ protected:
void setCanHaveLeadingExpansion(bool canHaveLeadingExpansion) { m_bitfields.setHasSelectedChildrenOrCanHaveLeadingExpansion(canHaveLeadingExpansion); } void setCanHaveLeadingExpansion(bool canHaveLeadingExpansion) { m_bitfields.setHasSelectedChildrenOrCanHaveLeadingExpansion(canHaveLeadingExpansion); }
signed expansion() { return m_bitfields.expansion(); } signed expansion() { return m_bitfields.expansion(); }
void setExpansion(signed expansion) { m_bitfields.setExpansion(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 // For InlineFlowBox and InlineTextBox
bool extracted() const { return m_bitfields.extracted(); } bool extracted() const { return m_bitfields.extracted(); }
......
...@@ -450,9 +450,18 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset, ...@@ -450,9 +450,18 @@ void InlineTextBox::paint(PaintInfo& paintInfo, const LayoutPoint& paintOffset,
string.narrow(m_start, length); string.narrow(m_start, length);
maximumLength = renderer().textLength() - m_start; 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; StringBuilder charactersWithHyphen;
TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0); TextRun textRun = constructTextRun(styleToUse, font, string, maximumLength, hasHyphen() ? &charactersWithHyphen : 0);
if (hasHyphen()) if (hasHyphen() || hasAddedEllipsis())
length = textRun.length(); length = textRun.length();
int sPos = 0; int sPos = 0;
......
...@@ -76,6 +76,7 @@ public: ...@@ -76,6 +76,7 @@ public:
using InlineBox::hasHyphen; using InlineBox::hasHyphen;
using InlineBox::setHasHyphen; using InlineBox::setHasHyphen;
using InlineBox::setHasAddedEllipsis;
using InlineBox::canHaveLeadingExpansion; using InlineBox::canHaveLeadingExpansion;
using InlineBox::setCanHaveLeadingExpansion; using InlineBox::setCanHaveLeadingExpansion;
......
...@@ -515,6 +515,8 @@ RootInlineBox* RenderParagraph::constructLine(BidiRunList<BidiRun>& bidiRuns, co ...@@ -515,6 +515,8 @@ RootInlineBox* RenderParagraph::constructLine(BidiRunList<BidiRun>& bidiRuns, co
text->setDirOverride(r->dirOverride()); text->setDirOverride(r->dirOverride());
if (r->m_hasHyphen) if (r->m_hasHyphen)
text->setHasHyphen(true); text->setHasHyphen(true);
if (r->m_hasAddedEllipsis)
text->setHasAddedEllipsis(true);
} }
} }
...@@ -909,6 +911,8 @@ void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, ...@@ -909,6 +911,8 @@ void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState,
if (bidiRuns.runCount() && lineBreaker.lineWasHyphenated()) if (bidiRuns.runCount() && lineBreaker.lineWasHyphenated())
bidiRuns.logicallyLastRun()->m_hasHyphen = true; 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. // 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 // At the same time we figure out where border/padding/margin should be applied for
...@@ -928,6 +932,12 @@ void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState, ...@@ -928,6 +932,12 @@ void RenderParagraph::layoutRunsAndFloatsInRange(LineLayoutState& layoutState,
lineMidpointState.reset(); lineMidpointState.reset();
resolver.setPosition(endOfLine, numberOfIsolateAncestors(endOfLine)); 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: ...@@ -88,7 +88,7 @@ public:
void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects); void handleOutOfFlowPositioned(Vector<RenderBox*>& positionedObjects);
void handleEmptyInline(); void handleEmptyInline();
void handleReplaced(); void handleReplaced();
bool handleText(WordMeasurements&, bool& hyphenated); bool handleText(WordMeasurements&, bool& hyphenated, bool& ellipsized);
void commitAndUpdateLineBreakIfNeeded(); void commitAndUpdateLineBreakIfNeeded();
InlineIterator handleEndOfLine(); InlineIterator handleEndOfLine();
...@@ -409,6 +409,13 @@ inline float measureHyphenWidth(RenderText* renderer, const Font& font, TextDire ...@@ -409,6 +409,13 @@ inline float measureHyphenWidth(RenderText* renderer, const Font& font, TextDire
style->hyphenString().string(), style, style->direction())); 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) ALWAYS_INLINE TextDirection textDirectionFromUnicode(WTF::Unicode::Direction direction)
{ {
return direction == WTF::Unicode::RightToLeft return direction == WTF::Unicode::RightToLeft
...@@ -428,7 +435,7 @@ ALWAYS_INLINE float textWidth(RenderText* text, unsigned from, unsigned len, con ...@@ -428,7 +435,7 @@ ALWAYS_INLINE float textWidth(RenderText* text, unsigned from, unsigned len, con
return font.width(run, fallbackFonts, &glyphOverflow); 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()) if (!m_current.offset())
m_appliedStartWidth = false; m_appliedStartWidth = false;
...@@ -458,8 +465,17 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool ...@@ -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 breakWords = m_currentStyle->breakWords() && ((m_autoWrap && !m_width.committedWidth()) || m_currWS == PRE);
bool midWordBreak = false; bool midWordBreak = false;
bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap; bool breakAll = m_currentStyle->wordBreak() == BreakAllWordBreak && m_autoWrap;
float hyphenWidth = 0; 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) { if (m_renderTextInfo.m_text != renderText) {
m_renderTextInfo.m_text = renderText; m_renderTextInfo.m_text = renderText;
m_renderTextInfo.m_font = &font; m_renderTextInfo.m_font = &font;
...@@ -496,7 +512,14 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool ...@@ -496,7 +512,14 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
wrapW += charWidth; wrapW += charWidth;
bool midWordBreakIsBeforeSurrogatePair = U16_IS_LEAD(c) && m_current.offset() + 1 < renderText->textLength() && U16_IS_TRAIL((*renderText)[m_current.offset() + 1]); 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); 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(); int nextBreakablePosition = m_current.nextBreakablePosition();
...@@ -584,6 +607,13 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool ...@@ -584,6 +607,13 @@ inline bool BreakingContext::handleText(WordMeasurements& wordMeasurements, bool
wordMeasurement.width = charWidth; 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. // Didn't fit. Jump to the end unless there's still an opportunity to collapse whitespace.
if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) { if (m_ignoringSpaces || !m_collapseWhiteSpace || !m_currentCharacterIsSpace || !previousCharacterIsSpace) {
m_atEnd = true; m_atEnd = true;
......
...@@ -45,6 +45,7 @@ void LineBreaker::reset() ...@@ -45,6 +45,7 @@ void LineBreaker::reset()
{ {
m_positionedObjects.clear(); m_positionedObjects.clear();
m_hyphenated = false; m_hyphenated = false;
m_ellipsized = false;
} }
InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo& lineInfo, InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo& lineInfo,
...@@ -75,7 +76,7 @@ InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo ...@@ -75,7 +76,7 @@ InlineIterator LineBreaker::nextLineBreak(InlineBidiResolver& resolver, LineInfo
} else if (context.currentObject()->isReplaced()) { } else if (context.currentObject()->isReplaced()) {
context.handleReplaced(); context.handleReplaced();
} else if (context.currentObject()->isText()) { } 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. // We've hit a hard text line break. Our line break iterator is updated, so go ahead and early return.
return context.lineBreak(); return context.lineBreak();
} }
......
...@@ -46,6 +46,7 @@ public: ...@@ -46,6 +46,7 @@ public:
FloatingObject* lastFloatFromPreviousLine, WordMeasurements&); FloatingObject* lastFloatFromPreviousLine, WordMeasurements&);
bool lineWasHyphenated() { return m_hyphenated; } bool lineWasHyphenated() { return m_hyphenated; }
bool lineWasEllipsized() { return m_ellipsized; }
const Vector<RenderBox*>& positionedObjects() { return m_positionedObjects; } const Vector<RenderBox*>& positionedObjects() { return m_positionedObjects; }
private: private:
void reset(); void reset();
...@@ -54,6 +55,7 @@ private: ...@@ -54,6 +55,7 @@ private:
RenderParagraph* m_block; RenderParagraph* m_block;
bool m_hyphenated; bool m_hyphenated;
bool m_ellipsized;
Vector<RenderBox*> m_positionedObjects; Vector<RenderBox*> m_positionedObjects;
}; };
......
...@@ -69,6 +69,7 @@ struct BidiCharacterRun { ...@@ -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 // 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_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_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; unsigned char m_level;
BidiCharacterRun* m_next; BidiCharacterRun* m_next;
int m_start; int m_start;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册