Bug 328168: Horizontal paddings, borders and margins on multi-frame bidi inlines appear in the wrong places. Patch by Haamed Gheibi <gheibi@gmail.com>. r=roc+uriber, sr=roc.

This commit is contained in:
uriber%gmail.com 2006-06-15 08:16:23 +00:00
Родитель d82d580d5d
Коммит ab53025882
5 изменённых файлов: 353 добавлений и 49 удалений

Просмотреть файл

@ -21,6 +21,7 @@
*
* Contributor(s):
* Uri Bernstein <uriber@gmail.com>
* Haamed Gheibi <gheibi@metanetworking.com>
*
* 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
}

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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