/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1998 * Netscape Communications Corporation. All Rights Reserved. */ #include "nsFrameReflowState.h" #include "nsIStyleContext.h" #include "nsStyleConsts.h" #include "nsIFrame.h" #include "nsIHTMLReflow.h" #include "nsIContent.h" #include "nsHTMLAtoms.h" #include "nsIPresContext.h" #include "nsIPresShell.h" PRBool nsHTMLReflowState::HaveFixedContentWidth() const { const nsStylePosition* pos; frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)pos); // What about 'inherit'? return eStyleUnit_Auto != pos->mWidth.GetUnit(); } PRBool nsHTMLReflowState::HaveFixedContentHeight() const { const nsStylePosition* pos; frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)pos); // What about 'inherit'? return eStyleUnit_Auto != pos->mHeight.GetUnit(); } const nsHTMLReflowState* nsHTMLReflowState::GetContainingBlockReflowState(const nsReflowState* aParentRS) { while (nsnull != aParentRS) { if (nsnull != aParentRS->frame) { PRBool isContainingBlock; // XXX This needs to go and we need to start using the info in the // reflow state... nsresult rv = aParentRS->frame->IsPercentageBase(isContainingBlock); if (NS_SUCCEEDED(rv) && isContainingBlock) { return (const nsHTMLReflowState*) aParentRS; } } aParentRS = aParentRS->parentReflowState; } return nsnull; } const nsHTMLReflowState* nsHTMLReflowState::GetPageBoxReflowState(const nsReflowState* aParentRS) { // XXX write me as soon as we can ask a frame if it's a page frame... return nsnull; } nscoord nsHTMLReflowState::GetContainingBlockContentWidth(const nsReflowState* aParentRS) { nscoord width = 0; const nsHTMLReflowState* rs = GetContainingBlockReflowState(aParentRS); if (nsnull != rs) { return ((nsHTMLReflowState*)aParentRS)->computedWidth;/* XXX cast */ } return width; } static inline PRBool IsReplaced(nsIAtom* aTag) { return (nsHTMLAtoms::img == aTag) || (nsHTMLAtoms::applet == aTag) || (nsHTMLAtoms::object == aTag) || (nsHTMLAtoms::input == aTag) || (nsHTMLAtoms::select == aTag) || (nsHTMLAtoms::textarea == aTag) || (nsHTMLAtoms::iframe == aTag); } // XXX there is no CLEAN way to detect the "replaced" attribute (yet) void nsHTMLReflowState::DetermineFrameType(nsIPresContext& aPresContext) { nsIAtom* tag = nsnull; nsIContent* content; if (NS_SUCCEEDED(frame->GetContent(&content)) && (nsnull != content)) { content->GetTag(tag); NS_RELEASE(content); } // Section 9.7 indicates that absolute position takes precedence // over float which takes precedence over display. const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); const nsStylePosition* pos; frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)pos); if (pos->IsAbsolutelyPositioned()) { frameType = NS_CSS_FRAME_TYPE_ABSOLUTE; } else if (NS_STYLE_FLOAT_NONE != display->mFloats) { frameType = NS_CSS_FRAME_TYPE_FLOATING; } else { switch (display->mDisplay) { case NS_STYLE_DISPLAY_BLOCK: case NS_STYLE_DISPLAY_LIST_ITEM: case NS_STYLE_DISPLAY_TABLE: frameType = NS_CSS_FRAME_TYPE_BLOCK; break; case NS_STYLE_DISPLAY_INLINE: case NS_STYLE_DISPLAY_MARKER: case NS_STYLE_DISPLAY_INLINE_TABLE: frameType = NS_CSS_FRAME_TYPE_INLINE; break; case NS_STYLE_DISPLAY_RUN_IN: case NS_STYLE_DISPLAY_COMPACT: // XXX need to look ahead at the frame's sibling frameType = NS_CSS_FRAME_TYPE_BLOCK; break; case NS_STYLE_DISPLAY_TABLE_CELL: case NS_STYLE_DISPLAY_TABLE_CAPTION: case NS_STYLE_DISPLAY_TABLE_ROW_GROUP: case NS_STYLE_DISPLAY_TABLE_COLUMN: case NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP: case NS_STYLE_DISPLAY_TABLE_HEADER_GROUP: case NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP: case NS_STYLE_DISPLAY_TABLE_ROW: frameType = NS_CSS_FRAME_TYPE_INTERNAL_TABLE; break; case NS_STYLE_DISPLAY_NONE: default: frameType = NS_CSS_FRAME_TYPE_UNKNOWN; break; } } if (IsReplaced(tag)) { frameType = NS_FRAME_REPLACED(frameType); } NS_IF_RELEASE(tag); } // Helper function that re-calculates the left and right margin based on // the width of the containing block, the border/padding, and the computed // width. // // This function is called by InitConstraints() when the 'width' property // has a value other than 'auto' void nsHTMLReflowState::CalculateLeftRightMargin(const nsHTMLReflowState* aContainingBlockRS, const nsStyleSpacing* aSpacing, nscoord aComputedWidth, const nsMargin& aBorderPadding, nscoord& aComputedLeftMargin, nscoord& aComputedRightMargin) { PRBool isAutoLeftMargin = eStyleUnit_Auto == aSpacing->mMargin.GetLeftUnit(); PRBool isAutoRightMargin = eStyleUnit_Auto == aSpacing->mMargin.GetRightUnit(); // Calculate how much space is available for margins nscoord availMarginSpace = aContainingBlockRS->computedWidth - aComputedWidth - aBorderPadding.left - aBorderPadding.right; // See whether we're over constrained if (!isAutoLeftMargin && !isAutoRightMargin) { // Neither margin is 'auto' so we're over constrained. Use the // 'direction' property to tell which margin to ignore const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); if (NS_STYLE_DIRECTION_LTR == display->mDirection) { isAutoRightMargin = PR_TRUE; } else { isAutoLeftMargin = PR_TRUE; } } if (isAutoLeftMargin) { if (isAutoRightMargin) { // Both margins are 'auto' so their computed values are equal aComputedLeftMargin = availMarginSpace / 2; aComputedRightMargin = availMarginSpace - aComputedLeftMargin; } else { aComputedLeftMargin = availMarginSpace - aComputedRightMargin; } } else if (isAutoRightMargin) { aComputedRightMargin = availMarginSpace - aComputedLeftMargin; } } void nsHTMLReflowState::ComputeRelativeOffsets(const nsHTMLReflowState* cbrs, const nsStylePosition* aPosition) { nsStyleCoord coord; const nsHTMLReflowState* pcbrs = nsnull; // If any of the offsets are 'inherit' we need to find the positioned // containing block if ((eStyleUnit_Inherit == aPosition->mOffset.GetLeftUnit()) || (eStyleUnit_Inherit == aPosition->mOffset.GetTopUnit()) || (eStyleUnit_Inherit == aPosition->mOffset.GetRightUnit()) || (eStyleUnit_Inherit == aPosition->mOffset.GetBottomUnit())) { pcbrs = cbrs; while (nsnull != pcbrs) { const nsStylePosition* position; pcbrs->frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)position); if ((NS_STYLE_POSITION_ABSOLUTE == position->mPosition) || (NS_STYLE_POSITION_RELATIVE == position->mPosition)) { break; } pcbrs = (const nsHTMLReflowState*)pcbrs->parentReflowState; // XXX cast } } // Compute the 'left' and 'right' values. 'Left' moves the boxes to the right, // and 'right' moves the boxes to the left. The computed values are always: // left=-right PRBool leftIsAuto = eStyleUnit_Auto == aPosition->mOffset.GetLeftUnit(); PRBool rightIsAuto = eStyleUnit_Auto == aPosition->mOffset.GetRightUnit(); // Check for percentage based values and an unconstrained containing // block width. Treat them like 'auto' if (NS_UNCONSTRAINEDSIZE == cbrs->computedWidth) { if (eStyleUnit_Percent == aPosition->mOffset.GetLeftUnit()) { leftIsAuto = PR_TRUE; } if (eStyleUnit_Percent == aPosition->mOffset.GetRightUnit()) { rightIsAuto = PR_TRUE; } } // If neither 'left' not 'right' are auto, then we're over-constrained and // we ignore one of them if (!leftIsAuto && !rightIsAuto) { const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); if (NS_STYLE_DIRECTION_LTR == display->mDirection) { rightIsAuto = PR_TRUE; } else { leftIsAuto = PR_TRUE; } } if (leftIsAuto) { if (rightIsAuto) { // If both are 'auto' (their initial values), the computed values are 0 computedOffsets.left = computedOffsets.right = 0; } else { // 'Right' isn't 'auto' so compute its value if (eStyleUnit_Inherit == aPosition->mOffset.GetRightUnit()) { computedOffsets.right = pcbrs ? pcbrs->computedOffsets.right : 0; } else { ComputeHorizontalValue(cbrs->computedWidth, aPosition->mOffset.GetRightUnit(), aPosition->mOffset.GetRight(coord), computedOffsets.right); } // Computed value for 'left' is minus the value of 'right' computedOffsets.left = -computedOffsets.right; } } else { NS_ASSERTION(rightIsAuto, "unexpected specified constraint"); // 'Left' isn't 'auto' so compute its value if (eStyleUnit_Inherit == aPosition->mOffset.GetLeftUnit()) { computedOffsets.left = pcbrs ? pcbrs->computedOffsets.left : 0; } else { ComputeHorizontalValue(cbrs->computedWidth, aPosition->mOffset.GetLeftUnit(), aPosition->mOffset.GetLeft(coord), computedOffsets.left); } // Computed value for 'right' is minus the value of 'left' computedOffsets.right = -computedOffsets.left; } // Compute the 'top' and 'bottom' values. The 'top' and 'bottom' properties // move relatively positioned elements up and down. They also must be each // other's negative PRBool topIsAuto = eStyleUnit_Auto == aPosition->mOffset.GetTopUnit(); PRBool bottomIsAuto = eStyleUnit_Auto == aPosition->mOffset.GetBottomUnit(); // Check for percentage based values and a containing block height that // depends on the content height. Treat them like 'auto' if (NS_AUTOHEIGHT == cbrs->computedHeight) { if (eStyleUnit_Percent == aPosition->mOffset.GetTopUnit()) { topIsAuto = PR_TRUE; } if (eStyleUnit_Percent == aPosition->mOffset.GetBottomUnit()) { bottomIsAuto = PR_TRUE; } } // If neither is 'auto', 'bottom' is ignored if (!topIsAuto && !bottomIsAuto) { bottomIsAuto = PR_TRUE; } if (topIsAuto) { if (bottomIsAuto) { // If both are 'auto' (their initial values), the computed values are 0 computedOffsets.top = computedOffsets.bottom = 0; } else { // 'Bottom' isn't 'auto' so compute its value if (eStyleUnit_Inherit == aPosition->mOffset.GetBottomUnit()) { computedOffsets.bottom = pcbrs ? pcbrs->computedOffsets.bottom : 0; } else { ComputeVerticalValue(cbrs->computedHeight, aPosition->mOffset.GetBottomUnit(), aPosition->mOffset.GetBottom(coord), computedOffsets.bottom); } // Computed value for 'top' is minus the value of 'bottom' computedOffsets.top = -computedOffsets.bottom; } } else { NS_ASSERTION(bottomIsAuto, "unexpected specified constraint"); // 'Top' isn't 'auto' so compute its value if (eStyleUnit_Inherit == aPosition->mOffset.GetTopUnit()) { computedOffsets.top = pcbrs ? pcbrs->computedOffsets.top : 0; } else { ComputeVerticalValue(cbrs->computedHeight, aPosition->mOffset.GetTopUnit(), aPosition->mOffset.GetTop(coord), computedOffsets.top); } // Computed value for 'bottom' is minus the value of 'top' computedOffsets.bottom = -computedOffsets.top; } } void nsHTMLReflowState::InitAbsoluteConstraints(nsIPresContext& aPresContext, const nsHTMLReflowState* cbrs, const nsStylePosition* aPosition, nscoord containingBlockWidth, nscoord containingBlockHeight) { const nsStyleSpacing* spacing; frame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)spacing); const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); // If any of the offsets are 'auto', then get the placeholder frame and compute // its origin relative to the containing block nsPoint placeholderOffset(0, 0); if ((eStyleUnit_Auto == aPosition->mOffset.GetLeftUnit()) || (eStyleUnit_Auto == aPosition->mOffset.GetTopUnit()) || (eStyleUnit_Auto == aPosition->mOffset.GetRightUnit()) || (eStyleUnit_Auto == aPosition->mOffset.GetBottomUnit())) { // Get the placeholder frame nsIFrame* placeholderFrame; nsIPresShell* presShell = aPresContext.GetShell(); presShell->GetPlaceholderFrameFor(frame, placeholderFrame); NS_RELEASE(presShell); NS_ASSERTION(nsnull != placeholderFrame, "no placeholder frame"); if (nsnull != placeholderFrame) { placeholderFrame->GetOrigin(placeholderOffset); nsIFrame* parent; placeholderFrame->GetParent(&parent); while ((nsnull != parent) && (parent != cbrs->frame)) { nsPoint origin; parent->GetOrigin(origin); placeholderOffset += origin; parent->GetParent(&parent); } // Offsets are relative to the containing block's padding edge, so translate // from the frame's edge to the padding edge nsMargin blockBorder; const nsStyleSpacing* blockSpacing; cbrs->frame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)blockSpacing); blockSpacing->GetBorder(blockBorder); placeholderOffset.x -= blockBorder.top; placeholderOffset.y -= blockBorder.bottom; } } // Compute border and padding nsMargin borderPadding; ComputeBorderPaddingFor(frame, parentReflowState, borderPadding); nsStyleUnit widthUnit = aPosition->mWidth.GetUnit(); nsStyleUnit heightUnit = aPosition->mHeight.GetUnit(); // Initialize the 'left' and 'right' computed offsets PRBool leftIsAuto = PR_FALSE, rightIsAuto = PR_FALSE; nsStyleCoord coord; if (eStyleUnit_Inherit == aPosition->mOffset.GetLeftUnit()) { computedOffsets.left = cbrs->computedOffsets.left; } else if (eStyleUnit_Auto == aPosition->mOffset.GetLeftUnit()) { if (NS_STYLE_DIRECTION_LTR == display->mDirection) { computedOffsets.left = placeholderOffset.x; } else { computedOffsets.left = 0; leftIsAuto = PR_TRUE; } } else { ComputeHorizontalValue(containingBlockWidth, aPosition->mOffset.GetLeftUnit(), aPosition->mOffset.GetLeft(coord), computedOffsets.left); } if (eStyleUnit_Inherit == aPosition->mOffset.GetRightUnit()) { computedOffsets.right = cbrs->computedOffsets.right; } else if (eStyleUnit_Auto == aPosition->mOffset.GetRightUnit()) { if (NS_STYLE_DIRECTION_RTL == display->mDirection) { computedOffsets.right = placeholderOffset.x; } else { computedOffsets.right = 0; rightIsAuto = PR_TRUE; } } else { ComputeHorizontalValue(containingBlockWidth, aPosition->mOffset.GetRightUnit(), aPosition->mOffset.GetRight(coord), computedOffsets.right); } // Calculate the computed width if (eStyleUnit_Auto == widthUnit) { // Any remaining 'auto' values for 'left', 'right', 'margin-left', or // 'margin-right' are replaced with 0 (their default value) computedWidth = containingBlockWidth - computedOffsets.left - computedMargin.left - borderPadding.left - borderPadding.right - computedMargin.right - computedOffsets.right; } else { // Use the specified value for the computed width ComputeHorizontalValue(containingBlockWidth, widthUnit, aPosition->mWidth, computedWidth); if (leftIsAuto) { // Any 'auto' on 'margin-left' or 'margin-right' are replaced with 0 // (their default value) computedOffsets.left = containingBlockWidth - computedMargin.left - borderPadding.left - computedWidth - borderPadding.right - computedMargin.right - computedOffsets.right; } else if (rightIsAuto) { // Any 'auto' on 'margin-left' or 'margin-right' are replaced with 0 // (their default value) computedOffsets.right = containingBlockWidth - computedOffsets.left - computedMargin.left - borderPadding.left - computedWidth - borderPadding.right - computedMargin.right; } else { // All that's left to solve for are 'auto' values for 'margin-left' and // 'margin-right' if ((eStyleUnit_Auto == spacing->mMargin.GetLeftUnit()) || (eStyleUnit_Auto == spacing->mMargin.GetRightUnit())) { nscoord availMarginSpace = containingBlockWidth - computedOffsets.left - borderPadding.left - computedWidth - borderPadding.right - computedOffsets.right; if (eStyleUnit_Auto == spacing->mMargin.GetLeftUnit()) { if (eStyleUnit_Auto == spacing->mMargin.GetRightUnit()) { // Both 'margin-left' and 'margin-right' are 'auto', so they get // equal values computedMargin.left = availMarginSpace / 2; computedMargin.right = availMarginSpace - computedMargin.left; } else { computedMargin.left = availMarginSpace; } } else { computedMargin.right = availMarginSpace; } } } } // Initialize the 'top' and 'bottom' computed offsets PRBool bottomIsAuto = PR_FALSE; if (eStyleUnit_Inherit == aPosition->mOffset.GetTopUnit()) { computedOffsets.top = cbrs->computedOffsets.top; } else if ((eStyleUnit_Auto == aPosition->mOffset.GetTopUnit()) || ((NS_AUTOHEIGHT == containingBlockHeight) && (eStyleUnit_Percent == aPosition->mOffset.GetTopUnit()))) { // Use the placeholder position computedOffsets.top = placeholderOffset.y; } else { nsStyleCoord coord; ComputeVerticalValue(containingBlockHeight, aPosition->mOffset.GetTopUnit(), aPosition->mOffset.GetTop(coord), computedOffsets.top); } if (eStyleUnit_Inherit == aPosition->mOffset.GetBottomUnit()) { computedOffsets.bottom = cbrs->computedOffsets.bottom; } else if ((eStyleUnit_Auto == aPosition->mOffset.GetBottomUnit()) || ((NS_AUTOHEIGHT == containingBlockHeight) && (eStyleUnit_Percent == aPosition->mOffset.GetBottomUnit()))) { if (eStyleUnit_Auto == heightUnit) { computedOffsets.bottom = 0; } else { bottomIsAuto = PR_TRUE; } } else { nsStyleCoord coord; ComputeVerticalValue(containingBlockHeight, aPosition->mOffset.GetBottomUnit(), aPosition->mOffset.GetBottom(coord), computedOffsets.bottom); } // Check for a percentage based height and a containing block height // that depends on its content height, i.e., not explicitly specified if (eStyleUnit_Percent == heightUnit) { if (NS_AUTOHEIGHT == containingBlockHeight) { // Interpret the height like 'auto' heightUnit = eStyleUnit_Auto; } } // Calculate the computed height if (eStyleUnit_Auto == heightUnit) { // Any 'auto' on 'margin-top' or 'margin-bottom' are replaced with 0 // (their default value) if (NS_FRAME_IS_REPLACED(frameType)) { computedHeight = NS_INTRINSICSIZE; } else { // If the containing block's height was not explicitly specified (i.e., // it depends on its content height), then so does our height if (NS_AUTOHEIGHT == containingBlockHeight) { computedHeight = NS_AUTOHEIGHT; } else { computedHeight = containingBlockHeight - computedOffsets.top - computedMargin.top - borderPadding.top - borderPadding.bottom - computedMargin.bottom - computedOffsets.bottom; } } } else { // Use the specified value for the computed height ComputeVerticalValue(containingBlockHeight, heightUnit, aPosition->mHeight, computedHeight); if (NS_AUTOHEIGHT != containingBlockHeight) { if (bottomIsAuto) { // Any 'auto' on 'margin-top' or 'margin-bottom' are replaced with 0 computedOffsets.bottom = containingBlockHeight - computedOffsets.top - computedMargin.top - borderPadding.top - computedHeight - borderPadding.bottom - computedMargin.bottom; } else { // All that's left to solve for are 'auto' values for 'margin-top' and // 'margin-bottom' if ((eStyleUnit_Auto == spacing->mMargin.GetTopUnit()) || (eStyleUnit_Auto == spacing->mMargin.GetBottomUnit())) { nscoord availMarginSpace = containingBlockHeight - computedOffsets.top - borderPadding.top - computedHeight - borderPadding.bottom - computedOffsets.bottom; if (eStyleUnit_Auto == spacing->mMargin.GetTopUnit()) { if (eStyleUnit_Auto == spacing->mMargin.GetBottomUnit()) { // Both 'margin-top' and 'margin-bottom' are 'auto', so they get // equal values computedMargin.top = availMarginSpace / 2; computedMargin.bottom = availMarginSpace - computedMargin.top; } else { computedMargin.top = availMarginSpace; } } else { computedMargin.bottom = availMarginSpace; } } } } } } void nsHTMLReflowState::InitConstraints(nsIPresContext& aPresContext) { const nsStylePosition* pos; nsresult result = frame->GetStyleData(eStyleStruct_Position, (const nsStyleStruct*&)pos); // If this is the root frame then set the computed width and // height equal to the available space if (nsnull == parentReflowState) { computedWidth = availableWidth; computedHeight = availableHeight; computedMargin.SizeTo(0, 0, 0, 0); computedOffsets.SizeTo(0, 0, 0, 0); } else { // Get the containing block reflow state const nsHTMLReflowState* cbrs = GetContainingBlockReflowState(parentReflowState); NS_ASSERTION(nsnull != cbrs, "no containing block"); // See if the element is relatively positioned if (NS_STYLE_POSITION_RELATIVE == pos->mPosition) { ComputeRelativeOffsets(cbrs, pos); } else { // Initialize offsets to 0 computedOffsets.SizeTo(0, 0, 0, 0); } // Compute margins from the specified margin style information. These // become the default computed values, and may be adjusted below ComputeMarginFor(frame, parentReflowState, computedMargin); // Calculate the line height. // XXX Do we need to do this for all elements or just inline non-replaced // elements? mLineHeight = CalcLineHeight(aPresContext, frame); // See if it's an inline non-replaced element if (NS_CSS_FRAME_TYPE_INLINE == frameType) { // 'width' property doesn't apply to inline non-replaced elements. The // 'height' is given by the element's 'line-height' value if (mLineHeight >= 0) { computedHeight = mLineHeight; } return; // nothing else to compute } // Get the containing block width and height. We'll need them when // calculating the computed width and height. For all elements other // than absolutely positioned elements, the containing block is formed // by the content edge // XXX Correctly handle absolutely positioned elements... nscoord containingBlockWidth = cbrs->computedWidth; nscoord containingBlockHeight = cbrs->computedHeight; //NS_ASSERTION(0 != containingBlockWidth, "containing block width of 0"); nsStyleUnit widthUnit = pos->mWidth.GetUnit(); nsStyleUnit heightUnit = pos->mHeight.GetUnit(); // Check for a percentage based width and an unconstrained containing // block width if (eStyleUnit_Percent == widthUnit) { if (NS_UNCONSTRAINEDSIZE == containingBlockWidth) { // Interpret the width like 'auto' widthUnit = eStyleUnit_Auto; } } // Check for a percentage based height and a containing block height // that depends on the content height if (eStyleUnit_Percent == heightUnit) { if (NS_AUTOHEIGHT == containingBlockHeight) { // Interpret the height like 'auto' heightUnit = eStyleUnit_Auto; } } // Calculate the computed width and height. This varies by frame type if ((NS_FRAME_REPLACED(NS_CSS_FRAME_TYPE_INLINE) == frameType) || (NS_FRAME_REPLACED(NS_CSS_FRAME_TYPE_FLOATING) == frameType)) { // Inline replaced element and floating replaced element are basically // treated the same if (eStyleUnit_Auto == widthUnit) { // A specified value of 'auto' uses the element's intrinsic width computedWidth = NS_INTRINSICSIZE; } else { ComputeHorizontalValue(containingBlockWidth, widthUnit, pos->mWidth, computedWidth); } if (eStyleUnit_Auto == heightUnit) { // A specified value of 'auto' uses the element's intrinsic height computedHeight = NS_INTRINSICSIZE; } else { ComputeVerticalValue(containingBlockHeight, heightUnit, pos->mHeight, computedHeight); } } else if (NS_CSS_FRAME_TYPE_FLOATING == frameType) { // Floating non-replaced element if (eStyleUnit_Auto == widthUnit) { // A specified value of 'auto' becomes a computed width of 0 computedWidth = 0; } else { ComputeHorizontalValue(containingBlockWidth, widthUnit, pos->mWidth, computedWidth); } if (eStyleUnit_Auto == heightUnit) { computedHeight = NS_AUTOHEIGHT; // let it choose its height } else { ComputeVerticalValue(containingBlockHeight, heightUnit, pos->mHeight, computedHeight); } } else if (NS_CSS_FRAME_TYPE_INTERNAL_TABLE == frameType) { // Internal table elements. The rules vary depending on the type const nsStyleDisplay* display; frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&)display); // Calculate the computed width if ((NS_STYLE_DISPLAY_TABLE_ROW == display->mDisplay) || (NS_STYLE_DISPLAY_TABLE_ROW_GROUP == display->mDisplay)) { // 'width' property doesn't apply to table rows and row groups widthUnit = eStyleUnit_Auto; } if (eStyleUnit_Auto == widthUnit) { computedWidth = availableWidth; if (computedWidth != NS_UNCONSTRAINEDSIZE) { // Internal table elements don't have margins, but they have border // and padding nsMargin borderPadding; ComputeBorderPaddingFor(frame, parentReflowState, borderPadding); computedWidth -= borderPadding.left + borderPadding.right; } } else { ComputeHorizontalValue(containingBlockWidth, widthUnit, pos->mWidth, computedWidth); } // Calculate the computed height if ((NS_STYLE_DISPLAY_TABLE_COLUMN == display->mDisplay) || (NS_STYLE_DISPLAY_TABLE_COLUMN_GROUP == display->mDisplay)) { // 'height' property doesn't apply to table columns and column groups heightUnit = eStyleUnit_Auto; } if (eStyleUnit_Auto == heightUnit) { computedHeight = NS_AUTOHEIGHT; } else { ComputeVerticalValue(containingBlockHeight, heightUnit, pos->mHeight, computedHeight); } } else if (NS_FRAME_GET_TYPE(frameType) == NS_CSS_FRAME_TYPE_ABSOLUTE) { InitAbsoluteConstraints(aPresContext, cbrs, pos, containingBlockWidth, containingBlockHeight); } else { // Block-level elements const nsStyleSpacing* spacing; frame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&)spacing); // Compute border and padding nsMargin borderPadding; ComputeBorderPaddingFor(frame, parentReflowState, borderPadding); // Compute the content width if (eStyleUnit_Auto == widthUnit) { if (NS_FRAME_IS_REPLACED(frameType)) { // Block-level replaced element in the flow. A specified value of // 'auto' uses the element's intrinsic width computedWidth = NS_INTRINSICSIZE; } else { // Block-level non-replaced element in the flow if (NS_UNCONSTRAINEDSIZE == containingBlockWidth) { computedWidth = NS_UNCONSTRAINEDSIZE; } else { // Block-level non-replaced element in the flow. 'auto' values for // margin-left and margin-right become 0 and the sum of the areas must // equal the width of the containing block computedWidth = containingBlockWidth - computedMargin.left - computedMargin.right - borderPadding.left - borderPadding.right; } } } else { ComputeHorizontalValue(containingBlockWidth, widthUnit, pos->mWidth, computedWidth); // Calculate the computed left and right margin again taking into // account the computed width, border/padding, and width of the // containing block CalculateLeftRightMargin(cbrs, spacing, computedWidth, borderPadding, computedMargin.left, computedMargin.right); } // Compute the content height if (eStyleUnit_Auto == heightUnit) { if (NS_FRAME_IS_REPLACED(frameType)) { computedHeight = NS_INTRINSICSIZE; } else { computedHeight = NS_AUTOHEIGHT; } } else { ComputeVerticalValue(containingBlockHeight, heightUnit, pos->mHeight, computedHeight); } } } } nscoord nsHTMLReflowState::CalcLineHeight(nsIPresContext& aPresContext, nsIFrame* aFrame) { nscoord lineHeight = -1; nsIStyleContext* sc; aFrame->GetStyleContext(&sc); const nsStyleFont* elementFont = nsnull; if (nsnull != sc) { elementFont = (const nsStyleFont*)sc->GetStyleData(eStyleStruct_Font); for (;;) { const nsStyleText* text = (const nsStyleText*) sc->GetStyleData(eStyleStruct_Text); if (nsnull != text) { nsStyleUnit unit = text->mLineHeight.GetUnit(); #ifdef NOISY_VERTICAL_ALIGN printf(" styleUnit=%d\n", unit); #endif if (eStyleUnit_Enumerated == unit) { // Normal value; we use 1.0 for normal // XXX could come from somewhere else break; } else if (eStyleUnit_Factor == unit) { if (nsnull != elementFont) { // CSS2 spec says that the number is inherited, not the // computed value. Therefore use the font size of the // element times the inherited number. nscoord size = elementFont->mFont.size; lineHeight = nscoord(size * text->mLineHeight.GetFactorValue()); } break; } else if (eStyleUnit_Coord == unit) { lineHeight = text->mLineHeight.GetCoordValue(); break; } else if (eStyleUnit_Percent == unit) { // XXX This could arguably be the font-metrics actual height // instead since the spec says use the computed height. const nsStyleFont* font = (const nsStyleFont*) sc->GetStyleData(eStyleStruct_Font); nscoord size = font->mFont.size; lineHeight = nscoord(size * text->mLineHeight.GetPercentValue()); break; } else if (eStyleUnit_Inherit == unit) { nsIStyleContext* parentSC; parentSC = sc->GetParent(); if (nsnull == parentSC) { // Note: Break before releasing to avoid double-releasing sc break; } NS_RELEASE(sc); sc = parentSC; } else { // other units are not part of the spec so don't bother // looping break; } } } NS_RELEASE(sc); } return lineHeight; } void nsHTMLReflowState::ComputeHorizontalValue(nscoord aContainingBlockWidth, nsStyleUnit aUnit, const nsStyleCoord& aCoord, nscoord& aResult) { NS_PRECONDITION(eStyleUnit_Inherit != aUnit, "unexpected unit"); aResult = 0; if (eStyleUnit_Percent == aUnit) { float pct = aCoord.GetPercentValue(); aResult = NSToCoordFloor(aContainingBlockWidth * pct); } else if (eStyleUnit_Coord == aUnit) { aResult = aCoord.GetCoordValue(); } } void nsHTMLReflowState::ComputeVerticalValue(nscoord aContainingBlockHeight, nsStyleUnit aUnit, const nsStyleCoord& aCoord, nscoord& aResult) { NS_PRECONDITION(eStyleUnit_Inherit != aUnit, "unexpected unit"); aResult = 0; if (eStyleUnit_Percent == aUnit) { // Verify no one is trying to calculate a percentage based height against // a height that's shrink wrapping to its content. In that case they should // treat the specified value like 'auto' NS_ASSERTION(NS_AUTOHEIGHT != aContainingBlockHeight, "unexpected containing block height"); float pct = aCoord.GetPercentValue(); aResult = NSToCoordFloor(aContainingBlockHeight * pct); } else if (eStyleUnit_Coord == aUnit) { aResult = aCoord.GetCoordValue(); } } void nsHTMLReflowState::ComputeMarginFor(nsIFrame* aFrame, const nsReflowState* aParentRS, nsMargin& aResult) { const nsStyleSpacing* spacing; nsresult rv; rv = aFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); if (NS_SUCCEEDED(rv) && (nsnull != spacing)) { // If style style can provide us the margin directly, then use it. #if 0 if (!spacing->GetMargin(aResult) && (nsnull != aParentRS)) #else spacing->CalcMarginFor(aFrame, aResult); if ((eStyleUnit_Percent == spacing->mMargin.GetTopUnit()) || (eStyleUnit_Percent == spacing->mMargin.GetRightUnit()) || (eStyleUnit_Percent == spacing->mMargin.GetBottomUnit()) || (eStyleUnit_Percent == spacing->mMargin.GetLeftUnit())) #endif { // We have to compute the value (because it's uncomputable by // the style code). const nsHTMLReflowState* rs = GetContainingBlockReflowState(aParentRS); if (nsnull != rs) { nsStyleCoord left, right; nscoord containingBlockWidth = rs->computedWidth; ComputeHorizontalValue(containingBlockWidth, spacing->mMargin.GetLeftUnit(), spacing->mMargin.GetLeft(left), aResult.left); ComputeHorizontalValue(containingBlockWidth, spacing->mMargin.GetRightUnit(), spacing->mMargin.GetRight(right), aResult.right); } else { aResult.left = 0; aResult.right = 0; } const nsHTMLReflowState* rs2 = GetPageBoxReflowState(aParentRS); nsStyleCoord top, bottom; if (nsnull != rs2) { // According to the CSS2 spec, margin percentages are // calculated with respect to the *height* of the containing // block when in a paginated context. ComputeVerticalValue(rs2->computedHeight, spacing->mMargin.GetTopUnit(), spacing->mMargin.GetTop(top), aResult.top); ComputeVerticalValue(rs2->computedHeight, spacing->mMargin.GetBottomUnit(), spacing->mMargin.GetBottom(bottom), aResult.bottom); } else if (nsnull != rs) { // According to the CSS2 spec, margin percentages are // calculated with respect to the *width* of the containing // block, even for margin-top and margin-bottom. nscoord containingBlockWidth = rs->computedWidth; ComputeHorizontalValue(containingBlockWidth, spacing->mMargin.GetTopUnit(), spacing->mMargin.GetTop(top), aResult.top); ComputeHorizontalValue(containingBlockWidth, spacing->mMargin.GetBottomUnit(), spacing->mMargin.GetBottom(bottom), aResult.bottom); } else { aResult.top = 0; aResult.bottom = 0; } } } } void nsHTMLReflowState::ComputePaddingFor(nsIFrame* aFrame, const nsReflowState* aParentRS, nsMargin& aResult) { const nsStyleSpacing* spacing; nsresult rv; rv = aFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); if (NS_SUCCEEDED(rv) && (nsnull != spacing)) { // If style can provide us the padding directly, then use it. spacing->CalcPaddingFor(aFrame, aResult); #if 0 if (!spacing->GetPadding(aResult) && (nsnull != aParentRS)) #else spacing->CalcPaddingFor(aFrame, aResult); if ((eStyleUnit_Percent == spacing->mPadding.GetTopUnit()) || (eStyleUnit_Percent == spacing->mPadding.GetRightUnit()) || (eStyleUnit_Percent == spacing->mPadding.GetBottomUnit()) || (eStyleUnit_Percent == spacing->mPadding.GetLeftUnit())) #endif { // We have to compute the value (because it's uncomputable by // the style code). const nsHTMLReflowState* rs = GetContainingBlockReflowState(aParentRS); if (nsnull != rs) { nsStyleCoord left, right, top, bottom; nscoord containingBlockWidth = rs->computedWidth; ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetLeftUnit(), spacing->mPadding.GetLeft(left), aResult.left); ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetRightUnit(), spacing->mPadding.GetRight(right), aResult.right); // According to the CSS2 spec, padding percentages are // calculated with respect to the *width* of the containing // block, even for padding-top and padding-bottom. ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetTopUnit(), spacing->mPadding.GetTop(top), aResult.top); ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetBottomUnit(), spacing->mPadding.GetBottom(bottom), aResult.bottom); } else { aResult.SizeTo(0, 0, 0, 0); } } } else { aResult.SizeTo(0, 0, 0, 0); } } void nsHTMLReflowState::ComputeBorderFor(nsIFrame* aFrame, nsMargin& aResult) { const nsStyleSpacing* spacing; nsresult rv; rv = aFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); if (NS_SUCCEEDED(rv) && (nsnull != spacing)) { // If style can provide us the border directly, then use it. if (!spacing->GetBorder(aResult)) { // CSS2 has no percentage borders aResult.SizeTo(0, 0, 0, 0); } } else { aResult.SizeTo(0, 0, 0, 0); } } void nsHTMLReflowState::ComputeBorderPaddingFor(nsIFrame* aFrame, const nsReflowState* aParentRS, nsMargin& aResult) { const nsStyleSpacing* spacing; nsresult rv; rv = aFrame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) spacing); if (NS_SUCCEEDED(rv) && (nsnull != spacing)) { nsMargin b, p; // If style style can provide us the margin directly, then use it. if (!spacing->GetBorder(b)) { b.SizeTo(0, 0, 0, 0); } #if 0 if (!spacing->GetPadding(p) && (nsnull != aParentRS)) #else spacing->CalcPaddingFor(aFrame, p); if ((eStyleUnit_Percent == spacing->mPadding.GetTopUnit()) || (eStyleUnit_Percent == spacing->mPadding.GetRightUnit()) || (eStyleUnit_Percent == spacing->mPadding.GetBottomUnit()) || (eStyleUnit_Percent == spacing->mPadding.GetLeftUnit())) #endif { // We have to compute the value (because it's uncomputable by // the style code). const nsHTMLReflowState* rs = GetContainingBlockReflowState(aParentRS); if (nsnull != rs) { nsStyleCoord left, right, top, bottom; nscoord containingBlockWidth = rs->computedWidth; ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetLeftUnit(), spacing->mPadding.GetLeft(left), p.left); ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetRightUnit(), spacing->mPadding.GetRight(right), p.right); // According to the CSS2 spec, padding percentages are // calculated with respect to the *width* of the containing // block, even for padding-top and padding-bottom. ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetTopUnit(), spacing->mPadding.GetTop(top), p.top); ComputeHorizontalValue(containingBlockWidth, spacing->mPadding.GetBottomUnit(), spacing->mPadding.GetBottom(bottom), p.bottom); } else { p.SizeTo(0, 0, 0, 0); } } aResult.top = b.top + p.top; aResult.right = b.right + p.right; aResult.bottom = b.bottom + p.bottom; aResult.left = b.left + p.left; } else { aResult.SizeTo(0, 0, 0, 0); } } //---------------------------------------------------------------------- nsFrameReflowState::nsFrameReflowState(nsIPresContext& aPresContext, const nsHTMLReflowState& aReflowState, const nsHTMLReflowMetrics& aMetrics) : nsHTMLReflowState(aReflowState), mPresContext(aPresContext) { // While we skip around the reflow state that our parent gave us so // that the parentReflowState is linked properly, we don't want to // skip over it's reason. reason = aReflowState.reason; mNextRCFrame = nsnull; // Initialize max-element-size mComputeMaxElementSize = nsnull != aMetrics.maxElementSize; mMaxElementSize.width = 0; mMaxElementSize.height = 0; // Get style data that we need frame->GetStyleData(eStyleStruct_Text, (const nsStyleStruct*&) mStyleText); frame->GetStyleData(eStyleStruct_Display, (const nsStyleStruct*&) mStyleDisplay); frame->GetStyleData(eStyleStruct_Spacing, (const nsStyleStruct*&) mStyleSpacing); // Calculate our border and padding value ComputeBorderPaddingFor(frame, parentReflowState, mBorderPadding); // Set mNoWrap flag switch (mStyleText->mWhiteSpace) { case NS_STYLE_WHITESPACE_PRE: case NS_STYLE_WHITESPACE_NOWRAP: mNoWrap = PR_TRUE; break; default: mNoWrap = PR_FALSE; break; } // Set mDirection value mDirection = mStyleDisplay->mDirection; // See if this container frame will act as a root for margin // collapsing behavior. mIsMarginRoot = PR_FALSE; if ((0 != mBorderPadding.top) || (0 != mBorderPadding.bottom)) { mIsMarginRoot = PR_TRUE; } mCarriedOutTopMargin = 0; mPrevBottomMargin = 0; mCarriedOutMarginFlags = 0; } nsFrameReflowState::~nsFrameReflowState() { } void nsFrameReflowState::SetupChildReflowState(nsHTMLReflowState& aChildRS) { // Get reflow reason set correctly. It's possible that a child was // created and then it was decided that it could not be reflowed // (for example, a block frame that isn't at the start of a // line). In this case the reason will be wrong so we need to check // the frame state. nsReflowReason reason = eReflowReason_Resize; nsIFrame* frame = aChildRS.frame; nsFrameState state; frame->GetFrameState(&state); if (NS_FRAME_FIRST_REFLOW & state) { reason = eReflowReason_Initial; } else if (mNextRCFrame == frame) { reason = eReflowReason_Incremental; // Make sure we only incrementally reflow once mNextRCFrame = nsnull;/* XXX bad coupling */ } aChildRS.reason = reason; }