diff --git a/gfx/tests/gtest/TestSkipChars.cpp b/gfx/tests/gtest/TestSkipChars.cpp index de9f5ead46..65c96f4172 100644 --- a/gfx/tests/gtest/TestSkipChars.cpp +++ b/gfx/tests/gtest/TestSkipChars.cpp @@ -82,7 +82,7 @@ TestIterator() "[4] Check mapping of original to skipped for " << i; } - uint32_t expectOriginal1[] = + int32_t expectOriginal1[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28 }; @@ -136,7 +136,7 @@ TestIterator() "[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++) { EXPECT_TRUE(iter2.ConvertSkippedToOriginal(i) == expectOriginal2[i]) << diff --git a/gfx/thebes/gfxSkipChars.h b/gfx/thebes/gfxSkipChars.h index 5663dd3285..352a16090d 100644 --- a/gfx/thebes/gfxSkipChars.h +++ b/gfx/thebes/gfxSkipChars.h @@ -235,7 +235,7 @@ public: return GetSkippedOffset(); } - uint32_t ConvertSkippedToOriginal(int32_t aSkippedStringOffset) + int32_t ConvertSkippedToOriginal(uint32_t aSkippedStringOffset) { SetSkippedOffset(aSkippedStringOffset); return GetOriginalOffset(); diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index babab3821b..dc754afcea 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -1864,26 +1864,36 @@ public: 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, - * depending on whether the CSS rule "white-space: pre" is active for this frame. - * if aStartOffset + aLength goes past end, or if aLength is not specified - * then use the text up to the string's end. + * depending on CSS white-space processing. + * If aEndOffset goes past end, use the text up to the string's end. * 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 - * @param aSkipChars if aSkipIter is non-null, this must also be non-null. - * This gets used as backing data for the iterator so it should outlive the iterator. - * @param aSkipIter Where to fill in the gfxSkipCharsIterator info, or null if not needed by caller - * @param aStartOffset Skipped (rendered text) start offset - * @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. + * aStartOffset and aEndOffset can be content offsets or offsets in the + * rendered text, depending on aOffsetType. + * Returns a string, as well as offsets identifying the start of the text + * within the rendered text for the whole node, and within the text content + * of the node. */ - virtual nsresult GetRenderedText(nsAString* aAppendToString = nullptr, - gfxSkipChars* aSkipChars = nullptr, - gfxSkipCharsIterator* aSkipIter = nullptr, - uint32_t aSkippedStartOffset = 0, - uint32_t aSkippedMaxLength = UINT32_MAX) - { return NS_ERROR_NOT_IMPLEMENTED; } + struct RenderedText { + nsAutoString mString; + uint32_t mOffsetWithinNodeRenderedText; + int32_t mOffsetWithinNodeText; + RenderedText() : mOffsetWithinNodeRenderedText(0), + 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. diff --git a/layout/generic/nsLineLayout.cpp b/layout/generic/nsLineLayout.cpp index 1314b35ff5..af374f9851 100644 --- a/layout/generic/nsLineLayout.cpp +++ b/layout/generic/nsLineLayout.cpp @@ -1170,6 +1170,7 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, } else { PushFrame(aFrame); + aPushedFrame = true; } #ifdef REALLY_NOISY_REFLOW diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 4faca6579c..fd7dc9bd36 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -3936,9 +3936,8 @@ a11y::AccType nsTextFrame::AccessibleType() { if (IsEmpty()) { - nsAutoString renderedWhitespace; - GetRenderedText(&renderedWhitespace, nullptr, nullptr, 0, 1); - if (renderedWhitespace.IsEmpty()) { + RenderedText text = GetRenderedText(); + if (text.mString.IsEmpty()) { return a11y::eNoType; } } @@ -4051,13 +4050,6 @@ public: virtual void AddInlinePrefISize(nsRenderingContext *aRenderingContext, 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: explicit nsContinuingTextFrame(nsStyleContext* aContext) : nsTextFrame(aContext) {} nsIFrame* mPrevContinuation; @@ -8750,79 +8742,151 @@ static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun, return aChar; } -nsresult nsTextFrame::GetRenderedText(nsAString* aAppendToString, - gfxSkipChars* aSkipChars, - gfxSkipCharsIterator* aSkipIter, - uint32_t aSkippedStartOffset, - uint32_t aSkippedMaxLength) +static bool +LineEndsInHardLineBreak(nsTextFrame* aFrame) { - // The handling of aSkippedStartOffset and aSkippedMaxLength could be more efficient... - gfxSkipChars skipChars; + nsIFrame* lineContainer = FindLineContainer(aFrame); + 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; const nsTextFragment* textFrag = mContent->GetText(); - uint32_t keptCharsLength = 0; - uint32_t validCharsLength = 0; + uint32_t offsetInRenderedString = 0; + bool haveOffsets = false; - // Build skipChars and copy text, for each text frame in this continuation block for (textFrame = this; textFrame; textFrame = static_cast(textFrame->GetNextContinuation())) { - // For each text frame continuation in this block ... - 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; } // Ensure the text run and grab the gfxSkipCharsIterator for it gfxSkipCharsIterator iter = textFrame->EnsureTextRun(nsTextFrame::eInflated); - if (!textFrame->mTextRun) - return NS_ERROR_FAILURE; + if (!textFrame->mTextRun) { + break; + } + gfxSkipCharsIterator tmpIter = iter; // 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 - // break, in which case we need to accurately detect those sitations and - // call GetTrimmedOffsets() with true to trim whitespace at the line's end - TrimmedOffsets trimmedContentOffsets = textFrame->GetTrimmedOffsets(textFrag, false); - int32_t startOfLineSkipChars = trimmedContentOffsets.mStart - textFrame->mContentOffset; - if (startOfLineSkipChars > 0) { - skipChars.SkipChars(startOfLineSkipChars); - iter.SetOriginalOffset(trimmedContentOffsets.mStart); + TrimmedOffsets trimmedOffsets = textFrame->GetTrimmedOffsets(textFrag, + textFrame->IsAtEndOfLine() && LineEndsInHardLineBreak(textFrame)); + bool trimmedSignificantNewline = + trimmedOffsets.GetEnd() < GetContentEnd() && + HasSignificantTerminalNewline(); + uint32_t skippedToRenderedStringOffset = offsetInRenderedString - + tmpIter.ConvertOriginalToSkipped(trimmedOffsets.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(aStartOffset, trimmedOffsets.mStart); + result.mOffsetWithinNodeRenderedText = + tmpIter.ConvertOriginalToSkipped(clamped) + skippedToRenderedStringOffset; + MOZ_ASSERT(result.mOffsetWithinNodeRenderedText >= offsetInRenderedString && + result.mOffsetWithinNodeRenderedText <= INT32_MAX, + "Bad offset within rendered text"); + haveOffsets = true; + } + if (uint32_t(textFrame->mContentOffset) >= aEndOffset) { + break; + } + } + + int32_t startOffset; + int32_t endOffset; + if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) { + startOffset = + tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset); + endOffset = + tmpIter.ConvertSkippedToOriginal(aEndOffset - skippedToRenderedStringOffset); + } else { + startOffset = aStartOffset; + endOffset = std::min(INT32_MAX, aEndOffset); + } + trimmedOffsets.mStart = std::max(trimmedOffsets.mStart, + startOffset); + trimmedOffsets.mLength = std::min(trimmedOffsets.GetEnd(), + endOffset) - trimmedOffsets.mStart; + if (trimmedOffsets.mLength <= 0) { + offsetInRenderedString = nextOffsetInRenderedString; + continue; } - // Keep and copy the appropriate chars withing the caller's requested range const nsStyleText* textStyle = textFrame->StyleText(); - while (iter.GetOriginalOffset() < trimmedContentOffsets.GetEnd() && - keptCharsLength < aSkippedMaxLength) { - // For each original char from content text - if (iter.IsOriginalCharSkipped() || ++validCharsLength <= aSkippedStartOffset) { - skipChars.SkipChar(); - } else { - ++keptCharsLength; - skipChars.KeepChar(); - if (aAppendToString) { - aAppendToString->Append( - TransformChar(textStyle, textFrame->mTextRun, iter.GetSkippedOffset(), - textFrag->CharAt(iter.GetOriginalOffset()))); + 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(textStyle, textFrame->mTextRun, + iter.GetSkippedOffset(), ch)); } iter.AdvanceOriginal(1); } - if (keptCharsLength >= aSkippedMaxLength) { - break; // Already past the end, don't build string or gfxSkipCharsIter anymore - } - } - - if (aSkipChars) { - aSkipChars->TakeFrom(&skipChars); // Copy skipChars into aSkipChars - if (aSkipIter) { - // 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()); + + 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; } - return NS_OK; + if (!haveOffsets) { + result.mOffsetWithinNodeText = textFrag->GetLength(); + result.mOffsetWithinNodeRenderedText = offsetInRenderedString; + } + return result; } nsIAtom* diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 99976a082c..aeec4a13fa 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -248,11 +248,10 @@ public: nscoord mDeltaWidth; }; TrimOutput TrimTrailingWhiteSpace(nsRenderingContext* aRC); - virtual nsresult GetRenderedText(nsAString* aString = nullptr, - gfxSkipChars* aSkipChars = nullptr, - gfxSkipCharsIterator* aSkipIter = nullptr, - uint32_t aSkippedStartOffset = 0, - uint32_t aSkippedMaxLength = UINT32_MAX) override; + virtual RenderedText GetRenderedText(uint32_t aStartOffset = 0, + uint32_t aEndOffset = UINT32_MAX, + TextOffsetType aOffsetType = + TextOffsetType::OFFSETS_IN_CONTENT_TEXT) override; nsOverflowAreas RecomputeOverflow(const nsHTMLReflowState& aBlockReflowState);