Commit 8403c150 authored by mstensho's avatar mstensho Committed by Commit bot

Handle exclusive end offsets when translating from flow thread coordinates.

If we're in flipped blocks writing mode (i.e. vertical-rl), the flow thread
block offset we're dealing with may be a logical end point, and end points are
exclusive. This means that we need to pick the previous column, not the next,
if the offset is exactly at a column boundary.

Let flowThreadTranslationAtOffset() and columnIndexAtOffset() take a
PageBoundaryRule argument to handle this.

This makes offsetLeft and offsetTop work properly in vertical-rl writing mode
for elements that end at column boundaries. Added a test for that, and threw in
a vertical-lr test too, for good measure.

Remove ColumnIndexCalculationMode from columnIndexAtOffset(). It was partially
and inaccurately used to make sure we didn't escape the valid column range in
case an exclusive end offset was passed. Have the call sites that really need
to clamp the column index do it themselves. It's up to the callers to decide
how to treat offsets outside the range of columns anyway.

Review-Url: https://codereview.chromium.org/2339973002
Cr-Commit-Position: refs/heads/master@{#418800}
parent 43271670
<!DOCTYPE html>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<div style="position:relative; writing-mode:vertical-lr; columns:3; column-gap:10px; column-fill:auto; width:100px; height:320px;">
<div id="first" style="width:100px;"></div>
<div id="second" style="width:100px;"></div>
<div id="third" style="width:100px;"></div>
</div>
<script>
test(function() {
assert_equals(document.getElementById('first').offsetLeft, 0);
assert_equals(document.getElementById('first').offsetTop, 0);
}, "offsetLeft and offsetTop in vertical-lr at column boundaries, first element");
test(function() {
assert_equals(document.getElementById('second').offsetLeft, 0);
assert_equals(document.getElementById('second').offsetTop, 110);
}, "offsetLeft and offsetTop in vertical-lr at column boundaries, second element");
test(function() {
assert_equals(document.getElementById('third').offsetLeft, 0);
assert_equals(document.getElementById('third').offsetTop, 220);
}, "offsetLeft and offsetTop in vertical-lr at column boundaries, third element");
</script>
<!DOCTYPE html>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<div style="position:relative; writing-mode:vertical-rl; columns:3; column-gap:10px; column-fill:auto; width:100px; height:320px;">
<div id="first" style="width:100px;"></div>
<div id="second" style="width:100px;"></div>
<div id="third" style="width:100px;"></div>
</div>
<script>
test(function() {
assert_equals(document.getElementById('first').offsetLeft, 0);
assert_equals(document.getElementById('first').offsetTop, 0);
}, "offsetLeft and offsetTop in vertical-rl at column boundaries, first element");
test(function() {
assert_equals(document.getElementById('second').offsetLeft, 0);
assert_equals(document.getElementById('second').offsetTop, 110);
}, "offsetLeft and offsetTop in vertical-rl at column boundaries, second element");
test(function() {
assert_equals(document.getElementById('third').offsetLeft, 0);
assert_equals(document.getElementById('third').offsetTop, 220);
}, "offsetLeft and offsetTop in vertical-rl at column boundaries, third element");
</script>
......@@ -165,7 +165,7 @@ void InitialColumnHeightFinder::recordStrutBeforeOffset(LayoutUnit offsetInFlowT
ASSERT(columnSet().usedColumnCount() >= 1);
unsigned columnCount = columnSet().usedColumnCount();
ASSERT(m_shortestStruts.size() == columnCount);
unsigned index = groupAtOffset(offsetInFlowThread).columnIndexAtOffset(offsetInFlowThread - strut, MultiColumnFragmentainerGroup::AssumeNewColumns);
unsigned index = groupAtOffset(offsetInFlowThread).columnIndexAtOffset(offsetInFlowThread - strut, LayoutBox::AssociateWithLatterPage);
if (index >= columnCount)
return;
m_shortestStruts[index] = std::min(m_shortestStruts[index], strut);
......@@ -173,7 +173,7 @@ void InitialColumnHeightFinder::recordStrutBeforeOffset(LayoutUnit offsetInFlowT
LayoutUnit InitialColumnHeightFinder::spaceUsedByStrutsAt(LayoutUnit offsetInFlowThread) const
{
unsigned stopBeforeColumn = groupAtOffset(offsetInFlowThread).columnIndexAtOffset(offsetInFlowThread, MultiColumnFragmentainerGroup::AssumeNewColumns) + 1;
unsigned stopBeforeColumn = groupAtOffset(offsetInFlowThread).columnIndexAtOffset(offsetInFlowThread, LayoutBox::AssociateWithLatterPage) + 1;
stopBeforeColumn = std::min(stopBeforeColumn, columnSet().usedColumnCount());
ASSERT(stopBeforeColumn <= m_shortestStruts.size());
LayoutUnit totalStrutSpace;
......
......@@ -103,7 +103,7 @@ bool FragmentainerIterator::setFragmentainersOfInterest()
// narrow it down even further. The clip rect needs to be relative to the current fragmentainer
// group.
LayoutRect clipRect = m_clipRectInMulticolContainer;
LayoutSize offset = group.flowThreadTranslationAtOffset(group.logicalTopInFlowThread(), CoordinateSpaceConversion::Visual);
LayoutSize offset = group.flowThreadTranslationAtOffset(group.logicalTopInFlowThread(), LayoutBox::AssociateWithFormerPage, CoordinateSpaceConversion::Visual);
clipRect.move(-offset);
unsigned firstFragmentainerInClipRect, lastFragmentainerInClipRect;
group.columnIntervalForVisualRect(clipRect, firstFragmentainerInClipRect, lastFragmentainerInClipRect);
......@@ -125,7 +125,7 @@ void FragmentainerIterator::updateOutput()
// Set the physical translation offset.
LayoutUnit fragmentainerLogicalTopInFlowThread = group.logicalTopInFlowThread() + m_currentFragmentainerIndex * group.logicalHeight();
m_paginationOffset = group.flowThreadTranslationAtOffset(fragmentainerLogicalTopInFlowThread, CoordinateSpaceConversion::Visual);
m_paginationOffset = group.flowThreadTranslationAtOffset(fragmentainerLogicalTopInFlowThread, LayoutBox::AssociateWithLatterPage, CoordinateSpaceConversion::Visual);
// Set the overflow clip rect that corresponds to the fragmentainer.
m_clipRectInFlowThread = group.flowThreadPortionOverflowRectAt(m_currentFragmentainerIndex);
......
......@@ -332,21 +332,26 @@ bool LayoutMultiColumnFlowThread::isPageLogicalHeightKnown() const
return false;
}
LayoutSize LayoutMultiColumnFlowThread::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread, CoordinateSpaceConversion mode) const
LayoutSize LayoutMultiColumnFlowThread::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread, PageBoundaryRule rule, CoordinateSpaceConversion mode) const
{
if (!hasValidColumnSetInfo())
return LayoutSize(0, 0);
LayoutMultiColumnSet* columnSet = columnSetAtBlockOffset(offsetInFlowThread);
if (!columnSet)
return LayoutSize(0, 0);
return columnSet->flowThreadTranslationAtOffset(offsetInFlowThread, mode);
return columnSet->flowThreadTranslationAtOffset(offsetInFlowThread, rule, mode);
}
LayoutSize LayoutMultiColumnFlowThread::flowThreadTranslationAtPoint(const LayoutPoint& flowThreadPoint, CoordinateSpaceConversion mode) const
{
LayoutPoint flippedPoint = flipForWritingMode(flowThreadPoint);
LayoutUnit blockOffset = isHorizontalWritingMode() ? flippedPoint.y() : flippedPoint.x();
return flowThreadTranslationAtOffset(blockOffset, mode);
// If block direction is flipped, points at a column boundary belong in the former column, not
// the latter.
PageBoundaryRule rule = hasFlippedBlocksWritingMode() ? AssociateWithFormerPage : AssociateWithLatterPage;
return flowThreadTranslationAtOffset(blockOffset, rule, mode);
}
LayoutPoint LayoutMultiColumnFlowThread::flowThreadPointToVisualPoint(const LayoutPoint& flowThreadPoint) const
......
......@@ -191,7 +191,7 @@ public:
bool isPageLogicalHeightKnown() const final;
LayoutSize flowThreadTranslationAtOffset(LayoutUnit, CoordinateSpaceConversion) const;
LayoutSize flowThreadTranslationAtOffset(LayoutUnit, PageBoundaryRule, CoordinateSpaceConversion) const;
LayoutSize flowThreadTranslationAtPoint(const LayoutPoint& flowThreadPoint, CoordinateSpaceConversion) const;
LayoutPoint flowThreadPointToVisualPoint(const LayoutPoint& flowThreadPoint) const override;
......
......@@ -289,9 +289,9 @@ bool LayoutMultiColumnSet::heightIsAuto() const
return !flowThread->columnHeightAvailable();
}
LayoutSize LayoutMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockOffset, CoordinateSpaceConversion mode) const
LayoutSize LayoutMultiColumnSet::flowThreadTranslationAtOffset(LayoutUnit blockOffset, PageBoundaryRule rule, CoordinateSpaceConversion mode) const
{
return fragmentainerGroupAtFlowThreadOffset(blockOffset).flowThreadTranslationAtOffset(blockOffset, mode);
return fragmentainerGroupAtFlowThreadOffset(blockOffset).flowThreadTranslationAtOffset(blockOffset, rule, mode);
}
LayoutPoint LayoutMultiColumnSet::visualPointToFlowThreadPoint(const LayoutPoint& visualPoint) const
......
......@@ -124,7 +124,7 @@ public:
// Find the column that contains the given block offset, and return the translation needed to
// get from flow thread coordinates to visual coordinates.
LayoutSize flowThreadTranslationAtOffset(LayoutUnit, CoordinateSpaceConversion) const;
LayoutSize flowThreadTranslationAtOffset(LayoutUnit, PageBoundaryRule, CoordinateSpaceConversion) const;
LayoutPoint visualPointToFlowThreadPoint(const LayoutPoint& visualPoint) const;
......
......@@ -98,10 +98,16 @@ bool MultiColumnFragmentainerGroup::recalculateColumnHeight(LayoutMultiColumnSet
return true; // Need another pass.
}
LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread, CoordinateSpaceConversion mode) const
LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUnit offsetInFlowThread, LayoutBox::PageBoundaryRule rule, CoordinateSpaceConversion mode) const
{
LayoutMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread();
unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread);
// A column out of range doesn't have a flow thread portion, so we need to clamp to make sure
// that we stay within the actual columns. This means that content in the overflow area will be
// mapped to the last actual column, instead of being mapped to an imaginary column further
// ahead.
unsigned columnIndex = offsetInFlowThread >= logicalBottomInFlowThread() ? actualColumnCount() - 1 : columnIndexAtOffset(offsetInFlowThread, rule);
LayoutRect portionRect(flowThreadPortionRectAt(columnIndex));
flowThread->flipForWritingMode(portionRect);
LayoutRect columnRect(columnRectAt(columnIndex));
......@@ -117,11 +123,11 @@ LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUn
// Translation that would map points in the coordinate space of the outermost flow thread to
// visual points in the first column in the first fragmentainer group (row) in our multicol
// container.
LayoutSize enclosingTranslationOrigin = enclosingFlowThread->flowThreadTranslationAtOffset(firstRow.blockOffsetInEnclosingFragmentationContext(), mode);
LayoutSize enclosingTranslationOrigin = enclosingFlowThread->flowThreadTranslationAtOffset(firstRow.blockOffsetInEnclosingFragmentationContext(), LayoutBox::AssociateWithLatterPage, mode);
// Translation that would map points in the coordinate space of the outermost flow thread to
// visual points in the first column in this fragmentainer group.
enclosingTranslation = enclosingFlowThread->flowThreadTranslationAtOffset(blockOffsetInEnclosingFragmentationContext(), mode);
enclosingTranslation = enclosingFlowThread->flowThreadTranslationAtOffset(blockOffsetInEnclosingFragmentationContext(), LayoutBox::AssociateWithLatterPage, mode);
// What we ultimately return from this method is a translation that maps points in the
// coordinate space of our flow thread to a visual point in a certain column in this
......@@ -137,7 +143,7 @@ LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(LayoutUn
LayoutUnit MultiColumnFragmentainerGroup::columnLogicalTopForOffset(LayoutUnit offsetInFlowThread) const
{
unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread, AssumeNewColumns);
unsigned columnIndex = columnIndexAtOffset(offsetInFlowThread, LayoutBox::AssociateWithLatterPage);
return logicalTopInFlowThreadAt(columnIndex);
}
......@@ -187,7 +193,7 @@ LayoutRect MultiColumnFragmentainerGroup::fragmentsBoundingBox(const LayoutRect&
flowThread->flipForWritingMode(startColumnFlowThreadOverflowPortion);
LayoutRect startColumnRect(boundingBoxInFlowThread);
startColumnRect.intersect(startColumnFlowThreadOverflowPortion);
startColumnRect.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(startColumn), CoordinateSpaceConversion::Containing));
startColumnRect.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(startColumn), LayoutBox::AssociateWithLatterPage, CoordinateSpaceConversion::Containing));
if (startColumn == endColumn)
return startColumnRect; // It all takes place in one column. We're done.
......@@ -195,7 +201,7 @@ LayoutRect MultiColumnFragmentainerGroup::fragmentsBoundingBox(const LayoutRect&
flowThread->flipForWritingMode(endColumnFlowThreadOverflowPortion);
LayoutRect endColumnRect(boundingBoxInFlowThread);
endColumnRect.intersect(endColumnFlowThreadOverflowPortion);
endColumnRect.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(endColumn), CoordinateSpaceConversion::Containing));
endColumnRect.move(flowThreadTranslationAtOffset(logicalTopInFlowThreadAt(endColumn), LayoutBox::AssociateWithLatterPage, CoordinateSpaceConversion::Containing));
return unionRect(startColumnRect, endColumnRect);
}
......@@ -385,21 +391,22 @@ LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionOverflowRectAt(unsign
return overflowRect;
}
unsigned MultiColumnFragmentainerGroup::columnIndexAtOffset(LayoutUnit offsetInFlowThread, ColumnIndexCalculationMode mode) const
unsigned MultiColumnFragmentainerGroup::columnIndexAtOffset(LayoutUnit offsetInFlowThread, LayoutBox::PageBoundaryRule pageBoundaryRule) const
{
// Handle the offset being out of range.
if (offsetInFlowThread < m_logicalTopInFlowThread)
return 0;
// If we're laying out right now, we cannot constrain against some logical bottom, since it
// isn't known yet. Otherwise, just return the last column if we're past the logical bottom.
if (mode == ClampToExistingColumns) {
if (offsetInFlowThread >= m_logicalBottomInFlowThread)
return actualColumnCount() - 1;
}
if (m_columnHeight)
return ((offsetInFlowThread - m_logicalTopInFlowThread) / m_columnHeight).floor();
if (!m_columnHeight)
return 0;
unsigned columnIndex = ((offsetInFlowThread - m_logicalTopInFlowThread) / m_columnHeight).floor();
if (pageBoundaryRule == LayoutBox::AssociateWithFormerPage
&& columnIndex > 0 && logicalTopInFlowThreadAt(columnIndex) == offsetInFlowThread) {
// We are exactly at a column boundary, and we've been told to associate offsets at column
// boundaries with the former column, not the latter.
columnIndex--;
}
return columnIndex;
}
unsigned MultiColumnFragmentainerGroup::columnIndexAtVisualPoint(const LayoutPoint& visualPoint) const
......@@ -422,12 +429,11 @@ unsigned MultiColumnFragmentainerGroup::columnIndexAtVisualPoint(const LayoutPoi
void MultiColumnFragmentainerGroup::columnIntervalForBlockRangeInFlowThread(LayoutUnit logicalTopInFlowThread, LayoutUnit logicalBottomInFlowThread, unsigned& firstColumn, unsigned& lastColumn) const
{
logicalTopInFlowThread = std::max(logicalTopInFlowThread, this->logicalTopInFlowThread());
logicalBottomInFlowThread = std::min(logicalBottomInFlowThread, this->logicalBottomInFlowThread());
ASSERT(logicalTopInFlowThread <= logicalBottomInFlowThread);
firstColumn = columnIndexAtOffset(logicalTopInFlowThread);
lastColumn = columnIndexAtOffset(logicalBottomInFlowThread);
// logicalBottomInFlowThread is an exclusive endpoint, so some additional adjustments may be necessary.
if (lastColumn > firstColumn && logicalTopInFlowThreadAt(lastColumn) == logicalBottomInFlowThread)
lastColumn--;
firstColumn = columnIndexAtOffset(logicalTopInFlowThread, LayoutBox::AssociateWithLatterPage);
lastColumn = columnIndexAtOffset(logicalBottomInFlowThread, LayoutBox::AssociateWithFormerPage);
}
void MultiColumnFragmentainerGroup::columnIntervalForVisualRect(const LayoutRect& rect, unsigned& firstColumn, unsigned& lastColumn) const
......
......@@ -62,7 +62,7 @@ public:
void resetColumnHeight();
bool recalculateColumnHeight(LayoutMultiColumnSet&);
LayoutSize flowThreadTranslationAtOffset(LayoutUnit, CoordinateSpaceConversion) const;
LayoutSize flowThreadTranslationAtOffset(LayoutUnit, LayoutBox::PageBoundaryRule, CoordinateSpaceConversion) const;
LayoutUnit columnLogicalTopForOffset(LayoutUnit offsetInFlowThread) const;
LayoutPoint visualPointToFlowThreadPoint(const LayoutPoint& visualPoint) const;
LayoutRect fragmentsBoundingBox(const LayoutRect& boundingBoxInFlowThread) const;
......@@ -78,13 +78,10 @@ public:
LayoutRect calculateOverflow() const;
enum ColumnIndexCalculationMode {
ClampToExistingColumns, // Stay within the range of already existing columns.
AssumeNewColumns // Allow column indices outside the range of already existing columns.
};
unsigned columnIndexAtOffset(LayoutUnit offsetInFlowThread, ColumnIndexCalculationMode = ClampToExistingColumns) const;
unsigned columnIndexAtOffset(LayoutUnit offsetInFlowThread, LayoutBox::PageBoundaryRule) const;
// The "CSS actual" value of column-count. This includes overflowing columns, if any.
// Returns 1 or greater, never 0.
unsigned actualColumnCount() const;
private:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment