blob: 0b1317c95ce17a148bd203bb98fbffd85be12d0a [file] [log] [blame]
/*
* Copyright (C) 2018 Apple Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
class FloatingContext {
public:
void computePosition(Layout::Box&);
LayoutUnit left(LayoutUnit verticalPosition);
LayoutUnit right(LayoutUnit verticalPosition);
LayoutUnit bottom();
private:
LayoutPoint positionForFloating(const Layout::Box&);
LayoutPoint positionForClear(const Layout::Box&);
LayoutPoint computePositionToAvoidIntrudingFloats(const Layout::Box&);
};
*/
// All geometry here is absolute to the formatting context's root.
class FloatingContext {
constructor(floatingState) {
this.m_floatingState = floatingState;
}
computePosition(layoutBox) {
if (layoutBox.isOutOfFlowPositioned())
return;
let displayBox = this._formattingState().displayBox(layoutBox);
if (layoutBox.isFloatingPositioned()) {
displayBox.setTopLeft(this._positionForFloating(layoutBox));
this._addFloatingBox(layoutBox);
return;
}
if (Utils.hasClear(layoutBox))
return displayBox.setTopLeft(this._positionForClear(layoutBox));
// Intruding floats might force this box move.
displayBox.setTopLeft(this._computePositionToAvoidIntrudingFloats(layoutBox));
}
left(verticalPosition) {
// Relative to the formatting context's root.
let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._leftFloatings());
if (!leftFloating)
return Number.NaN;
return this._mapDisplayMarginBoxToFormattingRoot(leftFloating).right();
}
right(verticalPosition) {
// Relative to the formatting context's root.
let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._rightFloatings());
if (!rightFloating)
return Number.NaN;
return this._mapDisplayMarginBoxToFormattingRoot(rightFloating).left();
}
bottom() {
let leftBottom = this._bottom(this._leftFloatings());
let rightBottom = this._bottom(this._rightFloatings());
if (Number.isNaN(leftBottom) && Number.isNaN(rightBottom))
return Number.NaN;
if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
return Math.max(leftBottom, rightBottom);
if (!Number.isNaN(leftBottom))
return leftBottom;
return rightBottom;
}
_positionForFloating(floatingBox) {
let absoluteFloatingBox = this._mapMarginBoxToFormattingRoot(floatingBox);
if (this._isEmpty())
return this._adjustedFloatingPosition(floatingBox, absoluteFloatingBox.top());
let verticalPosition = Math.max(absoluteFloatingBox.top(), this._mapDisplayMarginBoxToFormattingRoot(this._lastFloating()).top());
let spaceNeeded = absoluteFloatingBox.width();
while (true) {
let floatingPair = this._findInnerMostLeftAndRight(verticalPosition);
if (this._availableSpace(floatingBox.containingBlock(), floatingPair) >= spaceNeeded)
return this._adjustedFloatingPosition(floatingBox, verticalPosition, floatingPair);
verticalPosition = this._moveToNextVerticalPosition(floatingPair);
}
return Math.Nan;
}
_positionForClear(layoutBox) {
ASSERT(Utils.hasClear(layoutBox));
let displayBox = this._formattingState().displayBox(layoutBox);
if (this._isEmpty())
return displayBox.topLeft();
let leftBottom = Number.NaN;
let rightBottom = Number.NaN;
if (Utils.hasClearLeft(layoutBox) || Utils.hasClearBoth(layoutBox))
leftBottom = this._bottom(this._leftFloatings());
if (Utils.hasClearRight(layoutBox) || Utils.hasClearBoth(layoutBox))
rightBottom = this._bottom(this._rightFloatings());
if (!Number.isNaN(leftBottom) && !Number.isNaN(rightBottom))
return new LayoutPoint(Math.max(leftBottom, rightBottom), displayBox.left());
if (!Number.isNaN(leftBottom))
return new LayoutPoint(leftBottom, displayBox.left());
if (!Number.isNaN(rightBottom))
return new LayoutPoint(rightBottom, displayBox.left());
return displayBox.topLeft();
}
_computePositionToAvoidIntrudingFloats(layoutBox) {
if (!layoutBox.establishesBlockFormattingContext() || this._isEmpty())
return this._formattingState().displayBox(layoutBox).topLeft();
// The border box of a table, a block-level replaced element, or an element in the normal flow that establishes
// a new block formatting context (such as an element with 'overflow' other than 'visible') must not overlap the
// margin box of any floats in the same block formatting context as the element itself.
// For some reason, we position this as if it was floating left.
return this._positionForFloating(layoutBox);
}
_findInnerMostLeftAndRight(verticalPosition) {
let leftFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._leftFloatings());
let rightFloating = this._findFloatingAtVerticalPosition(verticalPosition, this._rightFloatings());
return { left: leftFloating, right: rightFloating };
}
_moveToNextVerticalPosition(floatingPair) {
if (!floatingPair.left && !floatingPair.right)
return Math.NaN;
let leftBottom = Number.POSITIVE_INFINITY;
let rightBottom = Number.POSITIVE_INFINITY;
if (floatingPair.left)
leftBottom = this._mapDisplayMarginBoxToFormattingRoot(floatingPair.left).bottom();
if (floatingPair.right)
rightBottom = this._mapDisplayMarginBoxToFormattingRoot(floatingPair.right).bottom();
return Math.min(leftBottom, rightBottom);
}
_availableSpace(containingBlock, floatingPair) {
let containingBlockContentBox = this._formattingState().displayBox(containingBlock);
if (floatingPair.left && floatingPair.right)
return floatingPair.right.left() - floatingPair.left.right();
if (floatingPair.left) {
return containingBlockContentBox.width() - (this._mapDisplayMarginBoxToFormattingRoot(floatingPair.left).right() - this._mapBorderBoxToFormattingRoot(containingBlock).left());
}
if (floatingPair.right)
return this._mapDisplayMarginBoxToFormattingRoot(floatingPair.right).left();
return containingBlockContentBox.width();
}
_findFloatingAtVerticalPosition(verticalPosition, floatingStack) {
let index = floatingStack.length;
while (--index >= 0 && this._mapDisplayMarginBoxToFormattingRoot(floatingStack[index]).bottom() <= verticalPosition);
return index >= 0 ? floatingStack[index] : null;
}
_isEmpty() {
return !this._leftFloatings().length && !this._rightFloatings().length;
}
_adjustedFloatingPosition(floatingBox, verticalPosition, leftRightFloatings) {
let containingBlock = floatingBox.containingBlock();
// Convert all coordinates relative to formatting context's root.
let left = this._mapContentBoxToFormattingRoot(containingBlock).left();
let right = this._mapContentBoxToFormattingRoot(containingBlock).right();
if (leftRightFloatings) {
if (leftRightFloatings.left) {
let floatingBoxRight = this._mapDisplayMarginBoxToFormattingRoot(leftRightFloatings.left).right();
if (floatingBoxRight > left)
left = floatingBoxRight;
}
if (leftRightFloatings.right) {
let floatingBoxLeft = this._mapDisplayMarginBoxToFormattingRoot(leftRightFloatings.right).left();
if (floatingBoxLeft < right)
right = floatingBoxLeft;
}
}
let floatingDisplayBox = this._formattingState().displayBox(floatingBox);
left += floatingDisplayBox.marginLeft();
right -= floatingDisplayBox.marginRight();
verticalPosition += floatingDisplayBox.marginTop();
// No convert them back relative to the floatingBox's containing block.
let containingBlockLeft = this._mapBorderBoxToFormattingRoot(containingBlock).left();
let containingBlockTop = this._mapBorderBoxToFormattingRoot(containingBlock).top();
left -= containingBlockLeft;
right -= containingBlockLeft;
verticalPosition -= containingBlockTop;
if (Utils.isFloatingLeft(floatingBox) || !Utils.isFloatingPositioned(floatingBox))
return new LayoutPoint(verticalPosition, left);
return new LayoutPoint(verticalPosition, right - floatingDisplayBox.rect().width());
}
_bottom(floatingStack) {
if (!floatingStack || !floatingStack.length)
return Number.NaN;
let max = Number.NEGATIVE_INFINITY;
for (let i = 0; i < floatingStack.length; ++i)
max = Math.max(this._mapDisplayMarginBoxToFormattingRoot(floatingStack[i]).bottom(), max);
return max;
}
_addFloatingBox(layoutBox) {
this._floatingState().addFloating(this._formattingState().displayBox(layoutBox), Utils.isFloatingLeft(layoutBox));
}
_mapMarginBoxToFormattingRoot(layoutBox) {
ASSERT(layoutBox instanceof Layout.Box);
return this._mapDisplayMarginBoxToFormattingRoot(this._formattingState().displayBox(layoutBox));
}
_mapDisplayMarginBoxToFormattingRoot(displayBox) {
ASSERT(displayBox instanceof Display.Box);
return Utils.marginBox(displayBox, this._formattingState().displayBox(this.formattingRoot()));
}
_mapBorderBoxToFormattingRoot(layoutBox) {
let displayBox = this._formattingState().displayBox(layoutBox);
let rootDisplayBox = this._formattingState().displayBox(this.formattingRoot());
return Utils.borderBox(displayBox, rootDisplayBox);
}
_mapContentBoxToFormattingRoot(layoutBox) {
let displayBox = this._formattingState().displayBox(layoutBox);
let rootDisplayBox = this._formattingState().displayBox(this.formattingRoot());
return Utils.contentBox(displayBox, rootDisplayBox);
}
formattingRoot() {
return this._formattingState().formattingRoot();
}
_floatingState() {
return this.m_floatingState;
}
_formattingState() {
return this._floatingState().formattingState();
}
_lastFloating() {
return this._floatingState().lastFloating();
}
_leftFloatings() {
return this._floatingState().leftFloatingStack();
}
_rightFloatings() {
return this._floatingState().rightFloatingStack();
}
}