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

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

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