From 816ab1e15e3c61b4c30e2110b65701df71fd1a4b Mon Sep 17 00:00:00 2001 From: "uriber%gmail.com" Date: Thu, 15 Jun 2006 08:16:23 +0000 Subject: [PATCH] Bug 328168: Horizontal paddings, borders and margins on multi-frame bidi inlines appear in the wrong places. Patch by Haamed Gheibi . r=roc+uriber, sr=roc. git-svn-id: svn://10.0.0.236/trunk@199957 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/layout/base/nsBidiPresUtils.cpp | 231 ++++++++++++++++++++--- mozilla/layout/base/nsBidiPresUtils.h | 93 ++++++++- mozilla/layout/generic/nsInlineFrame.cpp | 23 ++- mozilla/layout/generic/nsInlineFrame.h | 33 ++++ mozilla/layout/generic/nsLineLayout.cpp | 22 ++- 5 files changed, 353 insertions(+), 49 deletions(-) diff --git a/mozilla/layout/base/nsBidiPresUtils.cpp b/mozilla/layout/base/nsBidiPresUtils.cpp index 538b76f5fc5..e9b35579826 100644 --- a/mozilla/layout/base/nsBidiPresUtils.cpp +++ b/mozilla/layout/base/nsBidiPresUtils.cpp @@ -21,6 +21,7 @@ * * Contributor(s): * Uri Bernstein + * Haamed Gheibi * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), @@ -51,6 +52,9 @@ #include "nsCSSFrameConstructor.h" #include "nsHTMLContainerFrame.h" #include "nsLayoutUtils.h" +#include "nsInlineFrame.h" + +static NS_DEFINE_IID(kInlineFrameCID, NS_INLINE_FRAME_CID); static const PRUnichar kSpace = 0x0020; static const PRUnichar kLineSeparator = 0x2028; @@ -714,24 +718,179 @@ nsBidiPresUtils::GetFrameBaseLevel(nsIFrame* aFrame) return NS_GET_BASE_LEVEL(firstLeaf); } -static void -ReverseChildFramesPositioning(nsIFrame* aFirstChild) +void +nsBidiPresUtils::IsLeftOrRightMost(nsIFrame* aFrame, + nsContinuationStates* aContinuationStates, + PRBool& aIsLeftMost /* out */, + PRBool& aIsRightMost /* out */) const { - if (!aFirstChild) + const nsStyleVisibility* vis = aFrame->GetStyleVisibility(); + PRBool isLTR = (NS_STYLE_DIRECTION_LTR == vis->mDirection); + + /* + * Since we lay out frames from left to right (in both LTR and RTL), visiting a + * frame with 'mFirstVisualFrame == nsnull', means it's the first appearance of + * one of its continuation chain frames on the line. + * To determine if it's the last visual frame of its continuation chain on the line + * or not, we count the number of frames of the chain on the line, and then reduce + * it when we lay out a frame of the chain. If this value becomes 1 it means + * that it's the last visual frame of its continuation chain on this line. + */ + + nsFrameContinuationState* frameState = aContinuationStates->GetEntry(aFrame); + nsFrameContinuationState* firstFrameState; + + if (!frameState->mFirstVisualFrame) { + // aFrame is the first visual frame of its continuation chain + nsFrameContinuationState* contState; + nsIFrame* frame; + + frameState->mFrameCount = 1; + frameState->mFirstVisualFrame = aFrame; + + /** + * Traverse continuation chain of aFrame in both backward and forward + * directions while the frames are on this line. Count the frames and + * set their mFirstVisualFrame to aFrame. + */ + // Traverse continuation chain backward + for (frame = aFrame->GetPrevContinuation(); + frame && (contState = aContinuationStates->GetEntry(frame)); + frame = frame->GetPrevContinuation()) { + frameState->mFrameCount++; + contState->mFirstVisualFrame = aFrame; + } + frameState->mHasContOnPrevLines = (frame != nsnull); + + // Traverse continuation chain forward + for (frame = aFrame->GetNextContinuation(); + frame && (contState = aContinuationStates->GetEntry(frame)); + frame = frame->GetNextContinuation()) { + frameState->mFrameCount++; + contState->mFirstVisualFrame = aFrame; + } + frameState->mHasContOnNextLines = (frame != nsnull); + + aIsLeftMost = isLTR ? !frameState->mHasContOnPrevLines + : !frameState->mHasContOnNextLines; + firstFrameState = frameState; + } else { + // aFrame is not the first visual frame of its continuation chain + aIsLeftMost = PR_FALSE; + firstFrameState = aContinuationStates->GetEntry(frameState->mFirstVisualFrame); + } + + aIsRightMost = (firstFrameState->mFrameCount == 1) && + (isLTR ? !firstFrameState->mHasContOnNextLines + : !firstFrameState->mHasContOnPrevLines); + + // Reduce number of remaining frames of the continuation chain on the line. + firstFrameState->mFrameCount--; +} + +void +nsBidiPresUtils::RepositionFrame(nsIFrame* aFrame, + PRBool aIsOddLevel, + nscoord& aLeft, + nsContinuationStates* aContinuationStates) const +{ + if (!aFrame) return; - - // Get the right edge of the last sibling - nsIFrame* lastSibling; - for (lastSibling = aFirstChild; lastSibling->GetNextSibling(); lastSibling = lastSibling->GetNextSibling()) - ; - nscoord right = lastSibling->GetRect().XMost(); - + + PRBool isLeftMost, isRightMost; + IsLeftOrRightMost(aFrame, + aContinuationStates, + isLeftMost /* out */, + isRightMost /* out */); + + nsIFrame* testFrame; + aFrame->QueryInterface(kInlineFrameCID, (void**)&testFrame); + + if (testFrame) { + aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET); + + if (isLeftMost) + aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST); + else + aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST); + + if (isRightMost) + aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST); + else + aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST); + } + nsMargin margin; + aFrame->GetMargin(margin); + if (isLeftMost) + aLeft += margin.left; + + nscoord start = aLeft; + + if (!IsBidiLeaf(aFrame)) + { + nscoord x = 0; + nsMargin borderPadding; + aFrame->GetBorderAndPadding(borderPadding); + if (isLeftMost) { + x += borderPadding.left; + } + + // If aIsOddLevel is true, so we need to traverse the child list + // in reverse order, to make it O(n) we store the list locally and + // iterate the list reversely + nsVoidArray childList; + nsIFrame *frame = aFrame->GetFirstChild(nsnull); + if (frame && aIsOddLevel) { + childList.AppendElement(nsnull); + while (frame) { + childList.AppendElement(frame); + frame = frame->GetNextSibling(); + } + frame = (nsIFrame*)childList[childList.Count() - 1]; + } + + // Reposition the child frames + PRInt32 index = 0; + while (frame) { + RepositionFrame(frame, + aIsOddLevel, + x, + aContinuationStates); + index++; + frame = aIsOddLevel ? + (nsIFrame*)childList[childList.Count() - index - 1] : + frame->GetNextSibling(); + } + + if (isRightMost) { + x += borderPadding.right; + } + aLeft += x; + } else { + aLeft += aFrame->GetSize().width; + } + nsRect rect = aFrame->GetRect(); + aFrame->SetRect(nsRect(start, rect.y, aLeft - start, rect.height)); + + if (isRightMost) + aLeft += margin.right; +} + +void +nsBidiPresUtils::InitContinuationStates(nsIFrame* aFrame, + nsContinuationStates* aContinuationStates) const +{ + nsFrameContinuationState* state = aContinuationStates->PutEntry(aFrame); + state->mFirstVisualFrame = nsnull; + state->mFrameCount = 0; + + // Continue for child frames nsIFrame* frame; - for (frame = aFirstChild; frame; frame = frame->GetNextSibling()) { - right -= frame->GetRect().width; - frame->SetPosition(nsPoint(right, frame->GetPosition().y)); - if (!IsBidiLeaf(frame)) - ReverseChildFramesPositioning(frame->GetFirstChild(nsnull)); + for (frame = aFrame->GetFirstChild(nsnull); + frame; + frame = frame->GetNextSibling()) { + InitContinuationStates(frame, + aContinuationStates); } } @@ -741,19 +900,37 @@ nsBidiPresUtils::RepositionInlineFrames(nsPresContext* aPresContext, nsIFrame* aFirstChild, PRBool aReordered) const { - PRInt32 count = mVisualFrames.Count(); - nscoord left = aFirstChild->GetPosition().x; - nsIFrame* frame; - - for (PRInt32 i = 0; i < count; i++) { - frame = (nsIFrame*) (mVisualFrames[i]); - if (aReordered) // Don't touch positioning if no reordering was done, to avoid killing margins. - frame->SetPosition(nsPoint(left, frame->GetPosition().y)); - left += frame->GetRect().width; + nsMargin margin; + const nsStyleVisibility* vis = aFirstChild->GetStyleVisibility(); + PRBool isLTR = (NS_STYLE_DIRECTION_LTR == vis->mDirection); + nscoord leftSpace = 0; - // If this is an odd-level frame, reverse the positioning of its childern - if ((mLevels[mIndexMap[i]] & 1) && !IsBidiLeaf(frame)) - ReverseChildFramesPositioning(frame->GetFirstChild(nsnull)); + aFirstChild->GetMargin(margin); + if (!aFirstChild->GetPrevContinuation()) + leftSpace = isLTR ? margin.left : margin.right; + + nscoord left = aFirstChild->GetPosition().x - leftSpace; + nsIFrame* frame; + PRInt32 count = mVisualFrames.Count(); + PRInt32 index; + nsContinuationStates continuationStates; + + continuationStates.Init(); + + // Initialize continuation states to (nsnull, 0) for + // each frame on the line. + for (index = 0; index < count; index++) { + InitContinuationStates((nsIFrame*)mVisualFrames[index], + &continuationStates); + } + + // Reposition frames in visual order + for (index = 0; index < count; index++) { + frame = (nsIFrame*) (mVisualFrames[index]); + RepositionFrame(frame, + (mLevels[mIndexMap[index]] & 1), + left, + &continuationStates); } // for } diff --git a/mozilla/layout/base/nsBidiPresUtils.h b/mozilla/layout/base/nsBidiPresUtils.h index 6a34f8044b2..1f6c985e8d8 100644 --- a/mozilla/layout/base/nsBidiPresUtils.h +++ b/mozilla/layout/base/nsBidiPresUtils.h @@ -49,6 +49,46 @@ #include "nsCOMPtr.h" #include "nsDataHashtable.h" #include "nsBlockFrame.h" +#include "nsTHashtable.h" + +/** + * A structure representing some continuation state for each frame on the line, + * used to determine the first and the last continuation frame for each + * continuation chain on the line. + */ +struct nsFrameContinuationState : public nsVoidPtrHashKey +{ + nsFrameContinuationState(const void *aFrame) : nsVoidPtrHashKey(aFrame) {} + + /** + * The first visual frame in the continuation chain containing this frame, or + * nsnull if this frame is the first visual frame in the chain. + */ + nsIFrame* mFirstVisualFrame; + + /** + * The number of frames in the continuation chain containing this frame, if + * this frame is the first visual frame of the chain, or 0 otherwise. + */ + PRUint32 mFrameCount; + + /** + * TRUE if this frame is the first visual frame of its continuation chain on + * this line and the chain has some frames on the previous lines. + */ + PRPackedBool mHasContOnPrevLines; + + /** + * TRUE if this frame is the first visual frame of its continuation chain on + * this line and the chain has some frames left for next lines. + */ + PRPackedBool mHasContOnNextLines; +}; + +/* + * Following type is used to pass needed hashtable to reordering methods + */ +typedef nsTHashtable nsContinuationStates; /** * A structure representing a logical position which should be resolved @@ -238,6 +278,55 @@ private: */ nsresult Reorder(PRBool& aReordered, PRBool& aHasRTLFrames); + /* + * Position aFrame and it's descendants to their visual places. Also if aFrame + * is not leaf, resize it to embrace it's children. + * + * @param aFrame The frame which itself and its children are going + * to be repositioned + * @param aIsOddLevel TRUE means the embedding level of this frame is odd + * @param[in,out] aLeft IN value is the starting position of aFrame(without + * considering its left margin) + * OUT value will be the ending position of aFrame(after + * adding its right margin) + * @param aContinuationStates A map from nsIFrame* to nsFrameContinuationState + */ + void RepositionFrame(nsIFrame* aFrame, + PRBool aIsOddLevel, + nscoord& aLeft, + nsContinuationStates* aContinuationStates) const; + + /* + * Initialize the continuation state(nsFrameContinuationState) to + * (nsnull, 0) for aFrame and its descendants. + * + * @param aFrame The frame which itself and its descendants will + * be initialized + * @param aContinuationStates A map from nsIFrame* to nsFrameContinuationState + */ + void InitContinuationStates(nsIFrame* aFrame, + nsContinuationStates* aContinuationStates) const; + + /* + * Determine if aFrame is leftmost or rightmost, and set aIsLeftMost and + * aIsRightMost values. Also set continuation states of aContinuationStates. + * + * A frame is leftmost if it's the first appearance of its continuation chain + * on the line and the chain is on its first line if it's LTR or the chain is + * on its last line if it's RTL. + * A frame is rightmost if it's the last appearance of its continuation chain + * on the line and the chain is on its first line if it's RTL or the chain is + * on its last line if it's LTR. + * + * @param aContinuationStates A map from nsIFrame* to nsFrameContinuationState + * @param[out] aIsLeftMost TRUE means aFrame is leftmost frame or continuation + * @param[out] aIsRightMost TRUE means aFrame is rightmost frame or continuation + */ + void IsLeftOrRightMost(nsIFrame* aFrame, + nsContinuationStates* aContinuationStates, + PRBool& aIsLeftMost /* out */, + PRBool& aIsRightMost /* out */) const; + /** * Adjust frame positions following their visual order * @@ -250,10 +339,6 @@ private: nsIFrame* aFirstChild, PRBool aReordered) const; - void RepositionContainerFrame(nsPresContext* aPresContext, - nsIFrame* aContainer, - PRInt32& aMinX, - PRInt32& aMaxX) const; /** * Helper method for Resolve() * Truncate a text frame and possibly create a continuation frame with the diff --git a/mozilla/layout/generic/nsInlineFrame.cpp b/mozilla/layout/generic/nsInlineFrame.cpp index 90e6614b3df..99c0aa97b84 100644 --- a/mozilla/layout/generic/nsInlineFrame.cpp +++ b/mozilla/layout/generic/nsInlineFrame.cpp @@ -438,15 +438,18 @@ nsInlineFrame::ReflowFrames(nsPresContext* aPresContext, aStatus = NS_FRAME_COMPLETE; nsLineLayout* lineLayout = aReflowState.mLineLayout; + PRBool ltr = (NS_STYLE_DIRECTION_LTR == aReflowState.mStyleVisibility->mDirection); nscoord leftEdge = 0; if (nsnull == GetPrevContinuation()) { - leftEdge = aReflowState.mComputedBorderPadding.left; + leftEdge = ltr ? aReflowState.mComputedBorderPadding.left + : aReflowState.mComputedBorderPadding.right; } nscoord availableWidth = aReflowState.availableWidth; if (NS_UNCONSTRAINEDSIZE != availableWidth) { // Subtract off left and right border+padding from availableWidth availableWidth -= leftEdge; - availableWidth -= aReflowState.mComputedBorderPadding.right; + availableWidth -= ltr ? aReflowState.mComputedBorderPadding.right + : aReflowState.mComputedBorderPadding.left; availableWidth = PR_MAX(0, availableWidth); } lineLayout->BeginSpan(this, &aReflowState, leftEdge, leftEdge + availableWidth); @@ -552,10 +555,12 @@ nsInlineFrame::ReflowFrames(nsPresContext* aPresContext, // Compute final width aMetrics.width = size.width; if (nsnull == GetPrevContinuation()) { - aMetrics.width += aReflowState.mComputedBorderPadding.left; + aMetrics.width += ltr ? aReflowState.mComputedBorderPadding.left + : aReflowState.mComputedBorderPadding.right; } if (NS_FRAME_IS_COMPLETE(aStatus) && (!GetNextContinuation() || GetNextInFlow())) { - aMetrics.width += aReflowState.mComputedBorderPadding.right; + aMetrics.width += ltr ? aReflowState.mComputedBorderPadding.right + : aReflowState.mComputedBorderPadding.left; } SetFontFromStyle(aReflowState.rendContext, mStyleContext); @@ -783,9 +788,10 @@ PRIntn nsInlineFrame::GetSkipSides() const { PRIntn skip = 0; - if (nsnull != GetPrevContinuation()) { + if (!IsLeftMost()) { nsInlineFrame* prev = (nsInlineFrame*) GetPrevContinuation(); - if (prev->mRect.height || prev->mRect.width) { + if ((GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) || + (prev && (prev->mRect.height || prev->mRect.width))) { // Prev continuation is not empty therefore we don't render our left // border edge. skip |= 1 << NS_SIDE_LEFT; @@ -795,9 +801,10 @@ nsInlineFrame::GetSkipSides() const // edge border render. } } - if (nsnull != GetNextContinuation()) { + if (!IsRightMost()) { nsInlineFrame* next = (nsInlineFrame*) GetNextContinuation(); - if (next->mRect.height || next->mRect.width) { + if ((GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) || + (next && (next->mRect.height || next->mRect.width))) { // Next continuation is not empty therefore we don't render our right // border edge. skip |= 1 << NS_SIDE_RIGHT; diff --git a/mozilla/layout/generic/nsInlineFrame.h b/mozilla/layout/generic/nsInlineFrame.h index 5c697d8f613..28403b6cf2f 100644 --- a/mozilla/layout/generic/nsInlineFrame.h +++ b/mozilla/layout/generic/nsInlineFrame.h @@ -62,6 +62,17 @@ class nsAnonymousBlockFrame; #define NS_INLINE_FRAME_HARD_TEXT_OFFSETS 0x00200000 +/** In Bidi left (or right) margin/padding/border should be applied to left + * (or right) most frame (or a continuation frame). + * This state value shows if this frame is left (or right) most continuation + * or not. + */ +#define NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET 0x00400000 + +#define NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST 0x00800000 + +#define NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST 0x01000000 + /** * Inline frame class. * @@ -115,6 +126,28 @@ public: mFrames.SetFrames(nsnull); } + /** + * Return true if the frame is leftmost frame or continuation. + */ + PRBool IsLeftMost() const { + // If the frame's bidi visual state is set, return is-leftmost state + // else return true if it's the first continuation. + return (GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) + ? (GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_IS_LEFT_MOST) + : (!GetPrevInFlow()); + } + + /** + * Return true if the frame is rightmost frame or continuation. + */ + PRBool IsRightMost() const { + // If the frame's bidi visual state is set, return is-rightmost state + // else return true if it's the last continuation. + return (GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET) + ? (GetStateBits() & NS_INLINE_FRAME_BIDI_VISUAL_IS_RIGHT_MOST) + : (!GetNextInFlow()); + } + protected: // Additional reflow state used during our reflow methods struct InlineReflowState { diff --git a/mozilla/layout/generic/nsLineLayout.cpp b/mozilla/layout/generic/nsLineLayout.cpp index 7f3dc041c3f..c6ff9561f67 100644 --- a/mozilla/layout/generic/nsLineLayout.cpp +++ b/mozilla/layout/generic/nsLineLayout.cpp @@ -1239,16 +1239,16 @@ nsLineLayout::ApplyStartMargin(PerFrameData* pfd, else pfd->mMargin.right = 0; } + else { + pfd->mBounds.x += ltr ? pfd->mMargin.left : pfd->mMargin.right; - if (NS_UNCONSTRAINEDSIZE != aReflowState.availableWidth){ - // Adjust available width to account for the left margin. The - // right margin will be accounted for when we finish flowing the - // frame. - aReflowState.availableWidth -= ltr ? pfd->mMargin.left : pfd->mMargin.right; + if (NS_UNCONSTRAINEDSIZE != aReflowState.availableWidth){ + // Adjust available width to account for the left margin. The + // right margin will be accounted for when we finish flowing the + // frame. + aReflowState.availableWidth -= ltr ? pfd->mMargin.left : pfd->mMargin.right; + } } - - if (ltr) - pfd->mBounds.x += pfd->mMargin.left; } /** @@ -1314,7 +1314,8 @@ nsLineLayout::CanPlaceFrame(PerFrameData* pfd, // Set outside to PR_TRUE if the result of the reflow leads to the // frame sticking outside of our available area. - PRBool outside = pfd->mBounds.XMost() + pfd->mMargin.right > psd->mRightEdge; + PRBool ltr = (NS_STYLE_DIRECTION_LTR == aReflowState.mStyleVisibility->mDirection); + PRBool outside = pfd->mBounds.XMost() + (ltr ? pfd->mMargin.right : pfd->mMargin.left) > psd->mRightEdge; if (!outside) { // If it fits, it fits #ifdef NOISY_CAN_PLACE_FRAME @@ -1474,8 +1475,9 @@ nsLineLayout::PlaceFrame(PerFrameData* pfd, nsHTMLReflowMetrics& aMetrics) SetFlag(LL_UPDATEDBAND, PR_FALSE); } + PRBool ltr = (NS_STYLE_DIRECTION_LTR == pfd->mFrame->GetStyleVisibility()->mDirection); // Advance to next X coordinate - psd->mX = pfd->mBounds.XMost() + pfd->mMargin.right; + psd->mX = pfd->mBounds.XMost() + (ltr ? pfd->mMargin.right : pfd->mMargin.left); // If the frame is a not aware of white-space and it takes up some // width, disable leading white-space compression for the next frame