269 lines
12 KiB
JavaScript
269 lines
12 KiB
JavaScript
/*
|
|
* 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();
|
|
}
|
|
}
|