Bug 264412. Refactor nsIFrame::GetRenderedText API to be more sane. r=mats,marcoz

The test changes here are to adjust for the fact that
nsTextFrame::GetRenderedText can now trim whitespace from the end of lines
that end in a hard line break.
This commit is contained in:
Robert O'Callahan
2015-10-24 22:27:29 +13:00
parent f144f65b05
commit 5919848f93
17 changed files with 197 additions and 149 deletions

View File

@@ -228,12 +228,11 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
nsIContent* containerElm = containerNode->IsElement() ? nsIContent* containerElm = containerNode->IsElement() ?
containerNode->AsElement() : nullptr; containerNode->AsElement() : nullptr;
nsAutoString text; nsIFrame::RenderedText text = textFrame->GetRenderedText();
textFrame->GetRenderedText(&text);
// Remove text accessible if rendered text is empty. // Remove text accessible if rendered text is empty.
if (textAcc) { if (textAcc) {
if (text.IsEmpty()) { if (text.mString.IsEmpty()) {
#ifdef A11Y_LOG #ifdef A11Y_LOG
if (logging::IsEnabled(logging::eTree | logging::eText)) { if (logging::IsEnabled(logging::eTree | logging::eText)) {
logging::MsgBegin("TREE", "text node lost its content"); logging::MsgBegin("TREE", "text node lost its content");
@@ -256,17 +255,17 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
logging::MsgEntry("old text '%s'", logging::MsgEntry("old text '%s'",
NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get()); NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get());
logging::MsgEntry("new text: '%s'", logging::MsgEntry("new text: '%s'",
NS_ConvertUTF16toUTF8(text).get()); NS_ConvertUTF16toUTF8(text.mString).get());
logging::MsgEnd(); logging::MsgEnd();
} }
#endif #endif
TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text); TextUpdater::Run(mDocument, textAcc->AsTextLeaf(), text.mString);
continue; continue;
} }
// Append an accessible if rendered text is not empty. // Append an accessible if rendered text is not empty.
if (!text.IsEmpty()) { if (!text.mString.IsEmpty()) {
#ifdef A11Y_LOG #ifdef A11Y_LOG
if (logging::IsEnabled(logging::eTree | logging::eText)) { if (logging::IsEnabled(logging::eTree | logging::eText)) {
logging::MsgBegin("TREE", "text node gains new content"); logging::MsgBegin("TREE", "text node gains new content");

View File

@@ -1091,12 +1091,11 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
// Create accessible for visible text frames. // Create accessible for visible text frames.
if (content->IsNodeOfType(nsINode::eTEXT)) { if (content->IsNodeOfType(nsINode::eTEXT)) {
nsAutoString text; nsIFrame::RenderedText text = frame->GetRenderedText();
frame->GetRenderedText(&text, nullptr, nullptr, 0, UINT32_MAX);
// Ignore not rendered text nodes and whitespace text nodes between table // Ignore not rendered text nodes and whitespace text nodes between table
// cells. // cells.
if (text.IsEmpty() || if (text.mString.IsEmpty() ||
(aContext->IsTableRow() && nsCoreUtils::IsWhitespaceString(text))) { (aContext->IsTableRow() && nsCoreUtils::IsWhitespaceString(text.mString))) {
if (aIsSubtreeHidden) if (aIsSubtreeHidden)
*aIsSubtreeHidden = true; *aIsSubtreeHidden = true;
@@ -1108,7 +1107,7 @@ nsAccessibilityService::GetOrCreateAccessible(nsINode* aNode,
return nullptr; return nullptr;
document->BindToDocument(newAcc, nullptr); document->BindToDocument(newAcc, nullptr);
newAcc->AsTextLeaf()->SetText(text); newAcc->AsTextLeaf()->SetText(text.mString);
return newAcc; return newAcc;
} }

View File

@@ -139,8 +139,8 @@ nsTextEquivUtils::AppendTextEquivFromTextContent(nsIContent *aContent,
if (aContent->TextLength() > 0) { if (aContent->TextLength() > 0) {
nsIFrame *frame = aContent->GetPrimaryFrame(); nsIFrame *frame = aContent->GetPrimaryFrame();
if (frame) { if (frame) {
nsresult rv = frame->GetRenderedText(aString); nsIFrame::RenderedText text = frame->GetRenderedText();
NS_ENSURE_SUCCESS(rv, rv); aString->Append(text.mString);
} else { } else {
// If aContent is an object that is display: none, we have no a frame. // If aContent is an object that is display: none, we have no a frame.
aContent->AppendTextTo(*aString); aContent->AppendTextTo(*aString);

View File

@@ -394,11 +394,11 @@ Accessible::VisibilityState()
if (frame->GetType() == nsGkAtoms::textFrame && if (frame->GetType() == nsGkAtoms::textFrame &&
!(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) && !(frame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) &&
frame->GetRect().IsEmpty()) { frame->GetRect().IsEmpty()) {
nsAutoString renderedText; nsIFrame::RenderedText text = frame->GetRenderedText();
frame->GetRenderedText(&renderedText, nullptr, nullptr, 0, 1); if (text.mString.IsEmpty()) {
if (renderedText.IsEmpty())
return states::INVISIBLE; return states::INVISIBLE;
} }
}
return 0; return 0;
} }

View File

@@ -1984,17 +1984,9 @@ HyperTextAccessible::ContentToRenderedOffset(nsIFrame* aFrame, int32_t aContentO
NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
"Call on primary frame only"); "Call on primary frame only");
gfxSkipChars skipChars; nsIFrame::RenderedText text = aFrame->GetRenderedText(aContentOffset,
gfxSkipCharsIterator iter; aContentOffset + 1);
// Only get info up to original offset, we know that will be larger than skipped offset *aRenderedOffset = text.mOffsetWithinNodeRenderedText;
nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aContentOffset);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t ourRenderedStart = iter.GetSkippedOffset();
int32_t ourContentStart = iter.GetOriginalOffset();
*aRenderedOffset = iter.ConvertOriginalToSkipped(aContentOffset + ourContentStart) -
ourRenderedStart;
return NS_OK; return NS_OK;
} }
@@ -2016,16 +2008,9 @@ HyperTextAccessible::RenderedToContentOffset(nsIFrame* aFrame, uint32_t aRendere
NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr, NS_ASSERTION(aFrame->GetPrevContinuation() == nullptr,
"Call on primary frame only"); "Call on primary frame only");
gfxSkipChars skipChars; nsIFrame::RenderedText text = aFrame->GetRenderedText(aRenderedOffset,
gfxSkipCharsIterator iter; aRenderedOffset + 1, nsIFrame::TextOffsetType::OFFSETS_IN_RENDERED_TEXT);
// We only need info up to skipped offset -- that is what we're converting to original offset *aContentOffset = text.mOffsetWithinNodeText;
nsresult rv = aFrame->GetRenderedText(nullptr, &skipChars, &iter, 0, aRenderedOffset);
NS_ENSURE_SUCCESS(rv, rv);
uint32_t ourRenderedStart = iter.GetSkippedOffset();
int32_t ourContentStart = iter.GetOriginalOffset();
*aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart;
return NS_OK; return NS_OK;
} }

View File

@@ -113,7 +113,7 @@
'A esoteric weapon wielded by only the most ' + 'A esoteric weapon wielded by only the most ' +
'formidable warriors, for its unrelenting strict' + 'formidable warriors, for its unrelenting strict' +
' power is unfathomable.', ' power is unfathomable.',
'• Lists of Programming Languages', 'Lisp ', '• Lists of Programming Languages', 'Lisp',
'1. Scheme', '2. Racket', '3. Clojure', '1. Scheme', '2. Racket', '3. Clojure',
'4. Standard Lisp', 'link-0', ' Lisp', '4. Standard Lisp', 'link-0', ' Lisp',
'checkbox-1-5', ' LeLisp', '• JavaScript', 'checkbox-1-5', ' LeLisp', '• JavaScript',
@@ -124,7 +124,7 @@
'5 8', 'gridcell4', 'Just an innocuous separator', '5 8', 'gridcell4', 'Just an innocuous separator',
'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt', 'Dirty Words', 'Meaning', 'Mud', 'Wet Dirt',
'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2', 'Dirt', 'Messy Stuff', 'statusbar-1', 'statusbar-2',
'switch-1', 'This is a MathML formula ', 'math-1', 'switch-1', 'This is a MathML formula', 'math-1',
'with some text after.']); 'with some text after.']);
queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null, queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null,

View File

@@ -52,7 +52,7 @@
gQueue, docAcc, ObjectTraversalRule, null, gQueue, docAcc, ObjectTraversalRule, null,
['Main Title', 'Lorem ipsum ', ['Main Title', 'Lorem ipsum ',
'dolor', ' sit amet. Integer vitae urna leo, id ', 'dolor', ' sit amet. Integer vitae urna leo, id ',
'semper', ' nulla. ', 'Second Section Title', 'semper', ' nulla.', 'Second Section Title',
'Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.', 'Sed accumsan luctus lacus, vitae mollis arcu tristique vulputate.',
'An ', 'embedded', ' document.', 'Hide me', 'Link 1', 'Link 2', 'An ', 'embedded', ' document.', 'Hide me', 'Link 1', 'Link 2',
'Link 3', 'Hello', 'World']); 'Link 3', 'Hello', 'World']);
@@ -90,7 +90,7 @@
gQueue, docAcc, ObjectTraversalRule, gQueue, docAcc, ObjectTraversalRule,
getAccessible(doc.getElementById('paragraph-1')), getAccessible(doc.getElementById('paragraph-1')),
['Lorem ipsum ', 'dolor', ' sit amet. Integer vitae urna leo, id ', ['Lorem ipsum ', 'dolor', ' sit amet. Integer vitae urna leo, id ',
'semper', ' nulla. ']); 'semper', ' nulla.']);
gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent, gQueue.push(new setModalRootInvoker(docAcc, docAcc.parent,
NS_ERROR_INVALID_ARG)); NS_ERROR_INVALID_ARG));

View File

@@ -16,8 +16,8 @@
function doTest() function doTest()
{ {
var iframeDoc = [ getNode("iframe").contentDocument ]; var iframeDoc = [ getNode("iframe").contentDocument ];
testCharacterCount(iframeDoc, 15); testCharacterCount(iframeDoc, 13);
testText(iframeDoc, 0, 15, "outbody inbody "); testText(iframeDoc, 0, 13, "outbodyinbody");
SimpleTest.finish(); SimpleTest.finish();
} }

View File

@@ -58,7 +58,7 @@
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// getTextAtOffset line boundary // getTextAtOffset line boundary
testTextAtOffset(0, BOUNDARY_LINE_START, "line ", 0, 5, testTextAtOffset(0, BOUNDARY_LINE_START, "line", 0, 4,
"hypertext3", kOk, kOk, kOk); "hypertext3", kOk, kOk, kOk);
// XXX: see bug 634202. // XXX: see bug 634202.
@@ -122,7 +122,7 @@
<div id="nulltext"></div> <div id="nulltext"></div>
<div id="hypertext">hello <a>friend</a> see <img></div> <div id="hypertext">hello <a>friend</a> see <img src="about:blank"></div>
<div id="hypertext2">hello <a>friend</a> see <input></div> <div id="hypertext2">hello <a>friend</a> see <input></div>
<ol id="list"> <ol id="list">
<li id="listitem">foo</li> <li id="listitem">foo</li>

View File

@@ -14,9 +14,9 @@
function doTest() function doTest()
{ {
testTextAtOffset("line_test_1", BOUNDARY_LINE_START, testTextAtOffset("line_test_1", BOUNDARY_LINE_START,
[[0, 6, "Line 1 ", 0, 7], [[0, 5, "Line 1", 0, 6],
[7, 7, "", 7, 7], [6, 6, "", 6, 6],
[8, 15, "Line 3 ", 8, 15]]); [7, 13, "Line 3", 7, 13]]);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__ // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
@@ -114,10 +114,10 @@
[ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]); [ [ 0, 3, "foo\n", 0, 4 ], [ 4, 4, "", 4, 4 ] ]);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// 'Hello world ' (\n is rendered as space) // 'Hello world'
testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START, testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
[ [ 0, 12, "Hello world ", 0, 12 ] ]); [ [ 0, 11, "Hello world", 0, 11 ] ]);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// list items // list items

View File

@@ -42,27 +42,20 @@
[ [ 0, 6, "", 0, 0 ] ]); [ [ 0, 6, "", 0, 0 ] ]);
testTextBeforeOffset(ids, BOUNDARY_WORD_END, testTextBeforeOffset(ids, BOUNDARY_WORD_END,
[ [ 0, 5, "", 0, 0 ], [ [ 0, 5, "", 0, 0 ],
[ 6, 6, "hello", 0, 5, [ 6, 6, "hello", 0, 5 ]
[ [6, "e2", kTodo, kOk, kTodo ] ]
]
]); ]);
testTextAtOffset(ids, BOUNDARY_WORD_START, testTextAtOffset(ids, BOUNDARY_WORD_START,
[ [ 0, 6, "hello ", 0, 6 ] ]); [ [ 0, 6, "hello ", 0, 6 ] ]);
testTextAtOffset(ids, BOUNDARY_WORD_END, testTextAtOffset(ids, BOUNDARY_WORD_END,
[ [ 0, 4, "hello", 0, 5 ], [ [ 0, 4, "hello", 0, 5 ],
[ 5, 6, " ", 5, 6, [ 5, 6, " ", 5, 6 ]
[ [ 5, "e2", kTodo, kTodo, kOk ],
[ 6, "e2", kTodo, kTodo, kOk ] ]
]
]); ]);
testTextAfterOffset(ids, BOUNDARY_WORD_START, testTextAfterOffset(ids, BOUNDARY_WORD_START,
[ [ 0, 6, "", 6, 6 ] ]); [ [ 0, 6, "", 6, 6 ] ]);
testTextAfterOffset(ids, BOUNDARY_WORD_END, testTextAfterOffset(ids, BOUNDARY_WORD_END,
[ [ 0, 5, " ", 5, 6, [ [ 0, 5, " ", 5, 6 ],
[ [ 5, "e2", kTodo, kTodo, kOk ] ]
],
[ 6, 6, "", 6, 6 ] [ 6, 6, "", 6, 6 ]
]); ]);
@@ -256,7 +249,7 @@
<input id="i2" value="hello "/> <input id="i2" value="hello "/>
<pre><div id="d2">hello </div></pre> <pre><div id="d2">hello </div></pre>
<div id="e2" contenteditable="true">hello </div> <div id="e2" contenteditable="true" style='white-space:pre'>hello </div>
<textarea id="t2">hello </textarea> <textarea id="t2">hello </textarea>
<input id="i6" value="hello all"/> <input id="i6" value="hello all"/>

View File

@@ -49,14 +49,14 @@
}, },
{ {
role: ROLE_TEXT_LEAF, role: ROLE_TEXT_LEAF,
name: "Hello3 " name: "Hello3"
}, },
{ {
role: ROLE_PARAGRAPH, role: ROLE_PARAGRAPH,
children: [ children: [
{ {
role: ROLE_TEXT_LEAF, role: ROLE_TEXT_LEAF,
name: "Hello4 " name: "Hello4"
} }
] ]
} }
@@ -71,7 +71,7 @@
children: [ children: [
{ {
role: ROLE_TEXT_LEAF, role: ROLE_TEXT_LEAF,
name: "helllo " name: "helllo"
}, },
{ {
role: ROLE_PARAGRAPH, role: ROLE_PARAGRAPH,
@@ -84,7 +84,7 @@
}, },
{ {
role: ROLE_TEXT_LEAF, role: ROLE_TEXT_LEAF,
name: "hello " name: "hello"
} }
] ]
}; };

View File

@@ -82,7 +82,7 @@ TestIterator()
"[4] Check mapping of original to skipped for " << i; "[4] Check mapping of original to skipped for " << i;
} }
uint32_t expectOriginal1[] = int32_t expectOriginal1[] =
{ 0, 1, 2, 3, 4, 5, 6, 7, 8, { 0, 1, 2, 3, 4, 5, 6, 7, 8,
10, 11, 12, 13, 14, 15, 16, 17, 18, 10, 11, 12, 13, 14, 15, 16, 17, 18,
20, 21, 22, 23, 24, 25, 26, 27, 28 }; 20, 21, 22, 23, 24, 25, 26, 27, 28 };
@@ -136,7 +136,7 @@ TestIterator()
"[9] Check mapping of original to skipped for " << i; "[9] Check mapping of original to skipped for " << i;
} }
uint32_t expectOriginal2[] = { 9, 19, 29 }; int32_t expectOriginal2[] = { 9, 19, 29 };
for (uint32_t i = 0; i < mozilla::ArrayLength(expectOriginal2); i++) { for (uint32_t i = 0; i < mozilla::ArrayLength(expectOriginal2); i++) {
EXPECT_TRUE(iter2.ConvertSkippedToOriginal(i) == expectOriginal2[i]) << EXPECT_TRUE(iter2.ConvertSkippedToOriginal(i) == expectOriginal2[i]) <<

View File

@@ -235,7 +235,7 @@ public:
return GetSkippedOffset(); return GetSkippedOffset();
} }
uint32_t ConvertSkippedToOriginal(int32_t aSkippedStringOffset) int32_t ConvertSkippedToOriginal(uint32_t aSkippedStringOffset)
{ {
SetSkippedOffset(aSkippedStringOffset); SetSkippedOffset(aSkippedStringOffset);
return GetOriginalOffset(); return GetOriginalOffset();

View File

@@ -1893,26 +1893,36 @@ public:
virtual bool CanContinueTextRun() const = 0; virtual bool CanContinueTextRun() const = 0;
/** /**
* Append the rendered text to the passed-in string. * Computes an approximation of the rendered text of the frame and its
* continuations. Returns nothing for non-text frames.
* The appended text will often not contain all the whitespace from source, * The appended text will often not contain all the whitespace from source,
* depending on whether the CSS rule "white-space: pre" is active for this frame. * depending on CSS white-space processing.
* if aStartOffset + aLength goes past end, or if aLength is not specified * if aEndOffset goes past end, use the text up to the string's end.
* then use the text up to the string's end.
* Call this on the primary frame for a text node. * Call this on the primary frame for a text node.
* @param aAppendToString String to append text to, or null if text should not be returned * aStartOffset and aEndOffset can be content offsets or offsets in the
* @param aSkipChars if aSkipIter is non-null, this must also be non-null. * rendered text, depending on aOffsetType.
* This gets used as backing data for the iterator so it should outlive the iterator. * Returns a string, as well as offsets identifying the start of the text
* @param aSkipIter Where to fill in the gfxSkipCharsIterator info, or null if not needed by caller * within the rendered text for the whole node, and within the text content
* @param aStartOffset Skipped (rendered text) start offset * of the node.
* @param aSkippedMaxLength Maximum number of characters to return
* The iterator can be used to map content offsets to offsets in the returned string, or vice versa.
*/ */
virtual nsresult GetRenderedText(nsAString* aAppendToString = nullptr, struct RenderedText {
gfxSkipChars* aSkipChars = nullptr, nsString mString;
gfxSkipCharsIterator* aSkipIter = nullptr, uint32_t mOffsetWithinNodeRenderedText;
uint32_t aSkippedStartOffset = 0, int32_t mOffsetWithinNodeText;
uint32_t aSkippedMaxLength = UINT32_MAX) RenderedText() : mOffsetWithinNodeRenderedText(0),
{ return NS_ERROR_NOT_IMPLEMENTED; } mOffsetWithinNodeText(0) {}
};
enum class TextOffsetType {
// Passed-in start and end offsets are within the content text.
OFFSETS_IN_CONTENT_TEXT,
// Passed-in start and end offsets are within the rendered text.
OFFSETS_IN_RENDERED_TEXT
};
virtual RenderedText GetRenderedText(uint32_t aStartOffset = 0,
uint32_t aEndOffset = UINT32_MAX,
TextOffsetType aOffsetType =
TextOffsetType::OFFSETS_IN_CONTENT_TEXT)
{ return RenderedText(); }
/** /**
* Returns true if the frame contains any non-collapsed characters. * Returns true if the frame contains any non-collapsed characters.

View File

@@ -3975,9 +3975,8 @@ a11y::AccType
nsTextFrame::AccessibleType() nsTextFrame::AccessibleType()
{ {
if (IsEmpty()) { if (IsEmpty()) {
nsAutoString renderedWhitespace; RenderedText text = GetRenderedText();
GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1); if (text.mString.IsEmpty()) {
if (renderedWhitespace.IsEmpty()) {
return a11y::eNoType; return a11y::eNoType;
} }
} }
@@ -4090,13 +4089,6 @@ public:
virtual void AddInlinePrefISize(nsRenderingContext *aRenderingContext, virtual void AddInlinePrefISize(nsRenderingContext *aRenderingContext,
InlinePrefISizeData *aData) override; InlinePrefISizeData *aData) override;
virtual nsresult GetRenderedText(nsAString* aString = nullptr,
gfxSkipChars* aSkipChars = nullptr,
gfxSkipCharsIterator* aSkipIter = nullptr,
uint32_t aSkippedStartOffset = 0,
uint32_t aSkippedMaxLength = UINT32_MAX) override
{ return NS_ERROR_NOT_IMPLEMENTED; } // Call on a primary text frame only
protected: protected:
explicit nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {} explicit nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {}
nsIFrame* mPrevContinuation; nsIFrame* mPrevContinuation;
@@ -8882,80 +8874,151 @@ static char16_t TransformChar(nsTextFrame* aFrame, const nsStyleText* aStyle,
return aChar; return aChar;
} }
nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString, static bool
gfxSkipChars* aSkipChars, LineEndsInHardLineBreak(nsTextFrame* aFrame)
gfxSkipCharsIterator* aSkipIter,
uint32_t aSkippedStartOffset,
uint32_t aSkippedMaxLength)
{ {
// The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient... nsIFrame* lineContainer = FindLineContainer(aFrame);
gfxSkipChars skipChars; nsBlockFrame* block = do_QueryFrame(lineContainer);
if (!block) {
// Weird situation where we have a line layout without a block.
// No soft breaks occur in this situation.
return true;
}
bool foundValidLine;
nsBlockInFlowLineIterator iter(block, aFrame, &foundValidLine);
if (!foundValidLine) {
NS_ERROR("Invalid line!");
return true;
}
return !iter.GetLine()->IsLineWrapped();
}
nsIFrame::RenderedText
nsTextFrame::GetRenderedText(uint32_t aStartOffset,
uint32_t aEndOffset,
TextOffsetType aOffsetType)
{
NS_ASSERTION(!GetPrevContinuation(), "Must be called on first-in-flow");
// The handling of offsets could be more efficient...
RenderedText result;
nsTextFrame* textFrame; nsTextFrame* textFrame;
const nsTextFragment* textFrag = mContent->GetText(); const nsTextFragment* textFrag = mContent->GetText();
uint32_t keptCharsLength = 0; uint32_t offsetInRenderedString = 0;
uint32_t validCharsLength = 0; bool haveOffsets = false;
// Build skipChars and copy text, for each text frame in this continuation block
for (textFrame = this; textFrame; for (textFrame = this; textFrame;
textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) { textFrame = static_cast<nsTextFrame*>(textFrame->GetNextContinuation())) {
// For each text frame continuation in this block ...
if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) { if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
// We don't trust dirty frames, expecially when computing rendered text. // We don't trust dirty frames, especially when computing rendered text.
break; break;
} }
// Ensure the text run and grab the gfxSkipCharsIterator for it // Ensure the text run and grab the gfxSkipCharsIterator for it
gfxSkipCharsIterator iter = gfxSkipCharsIterator iter =
textFrame->EnsureTextRun(nsTextFrame::eInflated); textFrame->EnsureTextRun(nsTextFrame::eInflated);
if (!textFrame->mTextRun) if (!textFrame->mTextRun) {
return NS_ERROR_FAILURE; break;
}
gfxSkipCharsIterator tmpIter = iter;
// Skip to the start of the text run, past ignored chars at start of line // Skip to the start of the text run, past ignored chars at start of line
// XXX In the future we may decide to trim extra spaces before a hard line TrimmedOffsets trimmedOffsets = textFrame->GetTrimmedOffsets(textFrag,
// break, in which case we need to accurately detect those sitations and textFrame->IsAtEndOfLine() && LineEndsInHardLineBreak(textFrame));
// call GetTrimmedOffsets() with true to trim whitespace at the line's end bool trimmedSignificantNewline =
TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false); trimmedOffsets.GetEnd() < GetContentEnd() &&
int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset; HasSignificantTerminalNewline();
if (startOfLineSkipChars > 0) { uint32_t skippedToRenderedStringOffset = offsetInRenderedString -
skipChars.SkipChars(startOfLineSkipChars); tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
iter.SetOriginalOffset(trimmedContentOffsets.mStart); uint32_t nextOffsetInRenderedString =
tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
(trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
if (nextOffsetInRenderedString <= aStartOffset) {
offsetInRenderedString = nextOffsetInRenderedString;
continue;
}
if (!haveOffsets) {
result.mOffsetWithinNodeText =
tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
result.mOffsetWithinNodeRenderedText = aStartOffset;
haveOffsets = true;
}
if (offsetInRenderedString >= aEndOffset) {
break;
}
} else {
if (uint32_t(textFrame->GetContentEnd()) <= aStartOffset) {
offsetInRenderedString = nextOffsetInRenderedString;
continue;
}
if (!haveOffsets) {
result.mOffsetWithinNodeText = aStartOffset;
// Skip trimmed space when computed the rendered text offset.
int32_t clamped = std::max<int32_t>(aStartOffset, trimmedOffsets.mStart);
result.mOffsetWithinNodeRenderedText =
tmpIter.ConvertOriginalToSkipped(clamped) + skippedToRenderedStringOffset;
NS_ASSERTION(result.mOffsetWithinNodeRenderedText >= offsetInRenderedString &&
result.mOffsetWithinNodeRenderedText <= INT32_MAX,
"Bad offset within rendered text");
haveOffsets = true;
}
if (uint32_t(textFrame->mContentOffset) >= aEndOffset) {
break;
}
} }
// Keep and copy the appropriate chars withing the caller's requested range int32_t startOffset;
const nsStyleText* textStyle = textFrame->StyleText(); int32_t endOffset;
while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() && if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
keptCharsLength < aSkippedMaxLength) { startOffset =
// For each original char from content text tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) { endOffset =
skipChars.SkipChar(); tmpIter.ConvertSkippedToOriginal(aEndOffset - skippedToRenderedStringOffset);
} else { } else {
++keptCharsLength; startOffset = aStartOffset;
skipChars.KeepChar(); endOffset = std::min<uint32_t>(INT32_MAX, aEndOffset);
if (aAppendToString) {
aAppendToString->Append(
TransformChar(textFrame, textStyle, textFrame->mTextRun,
iter.GetSkippedOffset(),
textFrag->CharAt(iter.GetOriginalOffset())));
} }
trimmedOffsets.mStart = std::max<uint32_t>(trimmedOffsets.mStart,
startOffset);
trimmedOffsets.mLength = std::min<uint32_t>(trimmedOffsets.GetEnd(),
endOffset) - trimmedOffsets.mStart;
if (trimmedOffsets.mLength <= 0) {
offsetInRenderedString = nextOffsetInRenderedString;
continue;
}
const nsStyleText* textStyle = textFrame->StyleText();
iter.SetOriginalOffset(trimmedOffsets.mStart);
while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
char16_t ch = textFrag->CharAt(iter.GetOriginalOffset());
if (iter.IsOriginalCharSkipped()) {
if (ch == CH_SHY) {
// We should preserve soft hyphens. They can't be transformed.
result.mString.Append(ch);
}
} else {
result.mString.Append(
TransformChar(textFrame, textStyle, textFrame->mTextRun,
iter.GetSkippedOffset(), ch));
} }
iter.AdvanceOriginal(1); iter.AdvanceOriginal(1);
} }
if (keptCharsLength >= aSkippedMaxLength) {
break; // Already past the end, don't build string or gfxSkipCharsIter anymore if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
// A significant newline was trimmed off (we must be
// white-space:pre-line). Put it back.
result.mString.Append('\n');
} }
offsetInRenderedString = nextOffsetInRenderedString;
} }
if (aSkipChars) { if (!haveOffsets) {
aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars result.mOffsetWithinNodeText = textFrag->GetLength();
if (aSkipIter) { result.mOffsetWithinNodeRenderedText = offsetInRenderedString;
// Caller must provide both pointers in order to retrieve a gfxSkipCharsIterator,
// because the gfxSkipCharsIterator holds a weak pointer to the gfxSkipChars.
*aSkipIter = gfxSkipCharsIterator(*aSkipChars, GetContentLength());
} }
} return result;
return NS_OK;
} }
nsIAtom* nsIAtom*

View File

@@ -261,11 +261,10 @@ public:
nscoord mDeltaWidth; nscoord mDeltaWidth;
}; };
TrimOutput TrimTrailingWhiteSpace(nsRenderingContext* aRC); TrimOutput TrimTrailingWhiteSpace(nsRenderingContext* aRC);
virtual nsresult GetRenderedText(nsAString* aString = nullptr, virtual RenderedText GetRenderedText(uint32_t aStartOffset = 0,
gfxSkipChars* aSkipChars = nullptr, uint32_t aEndOffset = UINT32_MAX,
gfxSkipCharsIterator* aSkipIter = nullptr, TextOffsetType aOffsetType =
uint32_t aSkippedStartOffset = 0, TextOffsetType::OFFSETS_IN_CONTENT_TEXT) override;
uint32_t aSkippedMaxLength = UINT32_MAX) override;
nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame); nsOverflowAreas RecomputeOverflow(nsIFrame* aBlockFrame);