LayoutRect MultiColumnFragmentainerGroup::flowThreadPortionRectAt(unsigned columnIndex) const
{
    LayoutUnit logicalTop = logicalTopInFlowThreadAt(columnIndex);
    LayoutUnit logicalBottom = logicalTop + m_columnHeight;
    if (logicalBottom > logicalBottomInFlowThread()) {
        // The last column may not be using all available space.
        ASSERT(columnIndex + 1 == actualColumnCount());
        logicalBottom = logicalBottomInFlowThread();
        ASSERT(logicalBottom >= logicalTop);
    }
    LayoutUnit portionLogicalHeight = logicalBottom - logicalTop;
    if (m_columnSet.isHorizontalWritingMode())
        return LayoutRect(LayoutUnit(), logicalTop, m_columnSet.pageLogicalWidth(), portionLogicalHeight);
    return LayoutRect(logicalTop, LayoutUnit(), portionLogicalHeight, m_columnSet.pageLogicalWidth());
}
LayoutRect MultiColumnFragmentainerGroup::columnRectAt(
    unsigned columnIndex) const {
  LayoutUnit columnLogicalWidth = m_columnSet.pageLogicalWidth();
  LayoutUnit columnLogicalHeight = m_columnHeight;
  LayoutUnit columnLogicalTop;
  LayoutUnit columnLogicalLeft;
  LayoutUnit columnGap = m_columnSet.columnGap();
  LayoutUnit portionOutsideFlowThread =
      logicalTopInFlowThread() + (columnIndex + 1) * columnLogicalHeight -
      logicalBottomInFlowThread();
  if (portionOutsideFlowThread > 0) {
    // The last column may not be using all available space.
    ASSERT(columnIndex + 1 == actualColumnCount());
    columnLogicalHeight -= portionOutsideFlowThread;
    ASSERT(columnLogicalHeight >= 0);
  }

  if (m_columnSet.multiColumnFlowThread()->progressionIsInline()) {
    if (m_columnSet.style()->isLeftToRightDirection())
      columnLogicalLeft += columnIndex * (columnLogicalWidth + columnGap);
    else
      columnLogicalLeft += m_columnSet.contentLogicalWidth() -
                           columnLogicalWidth -
                           columnIndex * (columnLogicalWidth + columnGap);
  } else {
    columnLogicalTop += columnIndex * (m_columnHeight + columnGap);
  }

  LayoutRect columnRect(columnLogicalLeft, columnLogicalTop, columnLogicalWidth,
                        columnLogicalHeight);
  if (!m_columnSet.isHorizontalWritingMode())
    return columnRect.transposedRect();
  return columnRect;
}
Example #3
0
void InitialColumnHeightFinder::distributeImplicitBreaks() {
  // Insert a final content run to encompass all content. This will include
  // overflow if we're at the end of the multicol container.
  addContentRun(logicalBottomInFlowThread());
  unsigned columnCount = m_contentRuns.size();

  // If there is room for more breaks (to reach the used value of column-count),
  // imagine that we insert implicit breaks at suitable locations. At any given
  // time, the content run with the currently tallest columns will get another
  // implicit break "inserted", which will increase its column count by one and
  // shrink its columns' height. Repeat until we have the desired total number
  // of breaks. The largest column height among the runs will then be the
  // initial column height for the balancer to use.
  if (columnCount > columnSet().usedColumnCount()) {
    // If we exceed used column-count (which we are allowed to do if we're at
    // the initial balancing pass for a multicol that lives inside another
    // to-be-balanced outer multicol container), we only care about content that
    // could end up in the last row. We need to pad up the number of columns, so
    // that all rows will contain as many columns as used column-count dictates.
    columnCount %= columnSet().usedColumnCount();
    // If there are just enough explicit breaks to fill all rows with the right
    // amount of columns, we won't be needing any implicit breaks.
    if (!columnCount)
      return;
  }
  while (columnCount < columnSet().usedColumnCount()) {
    unsigned index = contentRunIndexWithTallestColumns();
    m_contentRuns[index].assumeAnotherImplicitBreak();
    columnCount++;
  }
}
void RenderMultiColumnSet::distributeImplicitBreaks()
{
#ifndef NDEBUG
    // There should be no implicit breaks assumed at this point.
    for (unsigned i = 0; i < m_contentRuns.size(); i++)
        ASSERT(!m_contentRuns[i].assumedImplicitBreaks());
#endif // NDEBUG

    // Insert a final content run to encompass all content. This will include overflow if this is
    // the last set.
    addContentRun(logicalBottomInFlowThread());
    unsigned columnCount = m_contentRuns.size();

    // If there is room for more breaks (to reach the used value of column-count), imagine that we
    // insert implicit breaks at suitable locations. At any given time, the content run with the
    // currently tallest columns will get another implicit break "inserted", which will increase its
    // column count by one and shrink its columns' height. Repeat until we have the desired total
    // number of breaks. The largest column height among the runs will then be the initial column
    // height for the balancer to use.
    while (columnCount < usedColumnCount()) {
        unsigned index = findRunWithTallestColumns();
        m_contentRuns[index].assumeAnotherImplicitBreak();
        columnCount++;
    }
}
Example #5
0
void ColumnBalancer::traverseLines(const LayoutBlockFlow& blockFlow) {
  for (const RootInlineBox* line = blockFlow.firstRootBox(); line;
       line = line->nextRootBox()) {
    LayoutUnit lineTopInFlowThread =
        m_flowThreadOffset + line->lineTopWithLeading();
    if (lineTopInFlowThread < logicalTopInFlowThread())
      continue;
    if (lineTopInFlowThread >= logicalBottomInFlowThread())
      break;
    examineLine(*line);
  }
}
bool MultiColumnFragmentainerGroup::recalculateColumnHeight(
    LayoutMultiColumnSet& columnSet) {
  LayoutUnit oldColumnHeight = m_columnHeight;

  m_maxColumnHeight = calculateMaxColumnHeight();

  // Only the last row may have auto height, and thus be balanced. There are no
  // good reasons to balance the preceding rows, and that could potentially lead
  // to an insane number of layout passes as well.
  if (isLastGroup() && columnSet.heightIsAuto()) {
    LayoutUnit newColumnHeight;
    if (!columnSet.isInitialHeightCalculated()) {
      // Initial balancing: Start with the lowest imaginable column height. Also
      // calculate the height of the tallest piece of unbreakable content.
      // Columns should never get any shorter than that (unless constrained by
      // max-height). Propagate this to our containing column set, in case there
      // is an outer multicol container that also needs to balance. After having
      // calculated the initial column height, the multicol container needs
      // another layout pass with the column height that we just calculated.
      InitialColumnHeightFinder initialHeightFinder(
          columnSet, logicalTopInFlowThread(), logicalBottomInFlowThread());
      LayoutUnit tallestUnbreakableLogicalHeight =
          initialHeightFinder.tallestUnbreakableLogicalHeight();
      columnSet.propagateTallestUnbreakableLogicalHeight(
          tallestUnbreakableLogicalHeight);
      newColumnHeight =
          std::max(initialHeightFinder.initialMinimalBalancedHeight(),
                   tallestUnbreakableLogicalHeight);
    } else {
      // Rebalancing: After having laid out again, we'll need to rebalance if
      // the height wasn't enough and we're allowed to stretch it, and then
      // re-lay out.  There are further details on the column balancing
      // machinery in ColumnBalancer and its derivates.
      newColumnHeight = rebalanceColumnHeightIfNeeded();
    }
    setAndConstrainColumnHeight(newColumnHeight);
  } else {
    // The position of the column set may have changed, in which case height
    // available for columns may have changed as well.
    setAndConstrainColumnHeight(m_columnHeight);
  }

  if (m_columnHeight == oldColumnHeight)
    return false;  // No change. We're done.

  return true;  // Need another pass.
}
LayoutRect MultiColumnFragmentainerGroup::fragmentsBoundingBox(
    const LayoutRect& boundingBoxInFlowThread) const {
  // Find the start and end column intersected by the bounding box.
  LayoutRect flippedBoundingBoxInFlowThread(boundingBoxInFlowThread);
  LayoutFlowThread* flowThread = m_columnSet.flowThread();
  flowThread->flipForWritingMode(flippedBoundingBoxInFlowThread);
  bool isHorizontalWritingMode = m_columnSet.isHorizontalWritingMode();
  LayoutUnit boundingBoxLogicalTop = isHorizontalWritingMode
                                         ? flippedBoundingBoxInFlowThread.y()
                                         : flippedBoundingBoxInFlowThread.x();
  LayoutUnit boundingBoxLogicalBottom =
      isHorizontalWritingMode ? flippedBoundingBoxInFlowThread.maxY()
                              : flippedBoundingBoxInFlowThread.maxX();
  if (boundingBoxLogicalBottom <= logicalTopInFlowThread() ||
      boundingBoxLogicalTop >= logicalBottomInFlowThread()) {
    // The bounding box doesn't intersect this fragmentainer group.
    return LayoutRect();
  }
  unsigned startColumn;
  unsigned endColumn;
  columnIntervalForBlockRangeInFlowThread(
      boundingBoxLogicalTop, boundingBoxLogicalBottom, startColumn, endColumn);

  LayoutRect startColumnFlowThreadOverflowPortion =
      flowThreadPortionOverflowRectAt(startColumn);
  flowThread->flipForWritingMode(startColumnFlowThreadOverflowPortion);
  LayoutRect startColumnRect(boundingBoxInFlowThread);
  startColumnRect.intersect(startColumnFlowThreadOverflowPortion);
  startColumnRect.move(flowThreadTranslationAtOffset(
      logicalTopInFlowThreadAt(startColumn), LayoutBox::AssociateWithLatterPage,
      CoordinateSpaceConversion::Containing));
  if (startColumn == endColumn)
    return startColumnRect;  // It all takes place in one column. We're done.

  LayoutRect endColumnFlowThreadOverflowPortion =
      flowThreadPortionOverflowRectAt(endColumn);
  flowThread->flipForWritingMode(endColumnFlowThreadOverflowPortion);
  LayoutRect endColumnRect(boundingBoxInFlowThread);
  endColumnRect.intersect(endColumnFlowThreadOverflowPortion);
  endColumnRect.move(flowThreadTranslationAtOffset(
      logicalTopInFlowThreadAt(endColumn), LayoutBox::AssociateWithLatterPage,
      CoordinateSpaceConversion::Containing));
  return unionRect(startColumnRect, endColumnRect);
}
Example #8
0
void InitialColumnHeightFinder::distributeImplicitBreaks() {
  // Insert a final content run to encompass all content. This will include
  // overflow if we're at the end of the multicol container.
  addContentRun(logicalBottomInFlowThread());
  unsigned columnCount = m_contentRuns.size();

  // If there is room for more breaks (to reach the used value of column-count),
  // imagine that we insert implicit breaks at suitable locations. At any given
  // time, the content run with the currently tallest columns will get another
  // implicit break "inserted", which will increase its column count by one and
  // shrink its columns' height. Repeat until we have the desired total number
  // of breaks. The largest column height among the runs will then be the
  // initial column height for the balancer to use.
  while (columnCount < columnSet().usedColumnCount()) {
    unsigned index = contentRunIndexWithTallestColumns();
    m_contentRuns[index].assumeAnotherImplicitBreak();
    columnCount++;
  }
}
LayoutUnit MultiColumnFragmentainerGroup::rebalanceColumnHeightIfNeeded()
    const {
  if (actualColumnCount() <= m_columnSet.usedColumnCount()) {
    // With the current column height, the content fits without creating
    // overflowing columns. We're done.
    return m_columnHeight;
  }

  if (m_columnHeight >= m_maxColumnHeight) {
    // We cannot stretch any further. We'll just have to live with the
    // overflowing columns. This typically happens if the max column height is
    // less than the height of the tallest piece of unbreakable content (e.g.
    // lines).
    return m_columnHeight;
  }

  MinimumSpaceShortageFinder shortageFinder(
      columnSet(), logicalTopInFlowThread(), logicalBottomInFlowThread());

  if (shortageFinder.forcedBreaksCount() + 1 >= m_columnSet.usedColumnCount()) {
    // Too many forced breaks to allow any implicit breaks. Initial balancing
    // should already have set a good height. There's nothing more we should do.
    return m_columnHeight;
  }

  // If the initial guessed column height wasn't enough, stretch it now. Stretch
  // by the lowest amount of space.
  LayoutUnit minSpaceShortage = shortageFinder.minimumSpaceShortage();

  ASSERT(minSpaceShortage > 0);  // We should never _shrink_ the height!
  ASSERT(minSpaceShortage !=
         LayoutUnit::max());  // If this happens, we probably have a bug.
  if (minSpaceShortage == LayoutUnit::max())
    return m_columnHeight;  // So bail out rather than looping infinitely.

  return m_columnHeight + minSpaceShortage;
}
Example #10
0
void ColumnBalancer::traverseChildren(const LayoutObject& object) {
  // The break-after value from the previous in-flow block-level object to be
  // joined with the break-before value of the next in-flow block-level sibling.
  EBreak previousBreakAfterValue = BreakAuto;

  for (const LayoutObject* child = object.slowFirstChild(); child;
       child = child->nextSibling()) {
    if (!child->isBox()) {
      // Keep traversing inside inlines. There may be floats there.
      if (child->isLayoutInline())
        traverseChildren(*child);
      continue;
    }

    const LayoutBox& childBox = toLayoutBox(*child);

    LayoutUnit borderEdgeOffset;
    LayoutUnit logicalTop = childBox.logicalTop();
    LayoutUnit logicalHeight = childBox.logicalHeightWithVisibleOverflow();
    // Floats' margins don't collapse with column boundaries, and we don't want
    // to break inside them, or separate them from the float's border box. Set
    // the offset to the margin-before edge (rather than border-before edge),
    // and include the block direction margins in the child height.
    if (childBox.isFloating()) {
      LayoutUnit marginBefore = childBox.marginBefore(object.style());
      LayoutUnit marginAfter = childBox.marginAfter(object.style());
      logicalHeight =
          std::max(logicalHeight, childBox.logicalHeight() + marginAfter);
      logicalTop -= marginBefore;
      logicalHeight += marginBefore;

      // As soon as we want to process content inside this child, though, we
      // need to get to its border-before edge.
      borderEdgeOffset = marginBefore;
    }

    if (m_flowThreadOffset + logicalTop + logicalHeight <=
        logicalTopInFlowThread()) {
      // This child is fully above the flow thread portion we're examining.
      continue;
    }
    if (m_flowThreadOffset + logicalTop >= logicalBottomInFlowThread()) {
      // This child is fully below the flow thread portion we're examining. We
      // cannot just stop here, though, thanks to negative margins.
      // So keep looking.
      continue;
    }
    if (childBox.isOutOfFlowPositioned() || childBox.isColumnSpanAll())
      continue;

    // Tables are wicked. Both table rows and table cells are relative to their
    // table section.
    LayoutUnit offsetForThisChild =
        childBox.isTableRow() ? LayoutUnit() : logicalTop;

    m_flowThreadOffset += offsetForThisChild;

    examineBoxAfterEntering(childBox, logicalHeight, previousBreakAfterValue);
    // Unless the child is unsplittable, or if the child establishes an inner
    // multicol container, we descend into its subtree for further examination.
    if (childBox.getPaginationBreakability() != LayoutBox::ForbidBreaks &&
        (!childBox.isLayoutBlockFlow() ||
         !toLayoutBlockFlow(childBox).multiColumnFlowThread())) {
      // We need to get to the border edge before processing content inside
      // this child. If the child is floated, we're currently at the margin
      // edge.
      m_flowThreadOffset += borderEdgeOffset;
      traverseSubtree(childBox);
      m_flowThreadOffset -= borderEdgeOffset;
    }
    previousBreakAfterValue = childBox.breakAfter();
    examineBoxBeforeLeaving(childBox, logicalHeight);

    m_flowThreadOffset -= offsetForThisChild;
  }
}
LayoutSize MultiColumnFragmentainerGroup::flowThreadTranslationAtOffset(
    LayoutUnit offsetInFlowThread,
    LayoutBox::PageBoundaryRule rule,
    CoordinateSpaceConversion mode) const {
  LayoutMultiColumnFlowThread* flowThread = m_columnSet.multiColumnFlowThread();

  // 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);
  portionRect.moveBy(flowThread->topLeftLocation());

  LayoutRect columnRect(columnRectAt(columnIndex));
  columnRect.move(offsetFromColumnSet());
  m_columnSet.flipForWritingMode(columnRect);
  columnRect.moveBy(m_columnSet.topLeftLocation());

  LayoutSize translationRelativeToFlowThread =
      columnRect.location() - portionRect.location();
  if (mode == CoordinateSpaceConversion::Containing)
    return translationRelativeToFlowThread;

  LayoutSize enclosingTranslation;
  if (LayoutMultiColumnFlowThread* enclosingFlowThread =
          flowThread->enclosingFlowThread()) {
    const MultiColumnFragmentainerGroup& firstRow =
        flowThread->firstMultiColumnSet()->firstFragmentainerGroup();
    // 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(),
            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(),
        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 fragmentainer group. We had to go all the way up
    // to the outermost flow thread, since this fragmentainer group may be in a
    // different outer column than the first outer column that this multicol
    // container lives in. It's the visual distance between the first
    // fragmentainer group and this fragmentainer group that we need to add to
    // the translation.
    enclosingTranslation -= enclosingTranslationOrigin;
  }

  return enclosingTranslation + translationRelativeToFlowThread;
}
Example #12
0
void ColumnBalancer::traverseSubtree(const LayoutBox& box) {
  if (box.childrenInline() && box.isLayoutBlockFlow()) {
    // Look for breaks between lines.
    for (const RootInlineBox* line = toLayoutBlockFlow(box).firstRootBox();
         line; line = line->nextRootBox()) {
      LayoutUnit lineTopInFlowThread =
          m_flowThreadOffset + line->lineTopWithLeading();
      if (lineTopInFlowThread < logicalTopInFlowThread())
        continue;
      if (lineTopInFlowThread >= logicalBottomInFlowThread())
        break;
      examineLine(*line);
    }
  }

  const LayoutFlowThread* flowThread = columnSet().flowThread();
  bool isHorizontalWritingMode = flowThread->isHorizontalWritingMode();

  // The break-after value from the previous in-flow block-level object to be
  // joined with the break-before value of the next in-flow block-level sibling.
  EBreak previousBreakAfterValue = BreakAuto;

  // Look for breaks between and inside block-level children. Even if this is a
  // block flow with inline children, there may be interesting floats to examine
  // here.
  for (const LayoutObject* child = box.slowFirstChild(); child;
       child = child->nextSibling()) {
    if (!child->isBox() || child->isInline())
      continue;
    const LayoutBox& childBox = toLayoutBox(*child);
    LayoutRect overflowRect = childBox.layoutOverflowRect();
    LayoutUnit childLogicalBottomWithOverflow =
        childBox.logicalTop() +
        (isHorizontalWritingMode ? overflowRect.maxY() : overflowRect.maxX());
    if (m_flowThreadOffset + childLogicalBottomWithOverflow <=
        logicalTopInFlowThread()) {
      // This child is fully above the flow thread portion we're examining.
      continue;
    }
    LayoutUnit childLogicalTopWithOverflow =
        childBox.logicalTop() +
        (isHorizontalWritingMode ? overflowRect.y() : overflowRect.x());
    if (m_flowThreadOffset + childLogicalTopWithOverflow >=
        logicalBottomInFlowThread()) {
      // This child is fully below the flow thread portion we're examining. We
      // cannot just stop here, though, thanks to negative margins.
      // So keep looking.
      continue;
    }
    if (childBox.isOutOfFlowPositioned() || childBox.isColumnSpanAll())
      continue;

    // Tables are wicked. Both table rows and table cells are relative to their
    // table section.
    LayoutUnit offsetForThisChild =
        childBox.isTableRow() ? LayoutUnit() : childBox.logicalTop();
    m_flowThreadOffset += offsetForThisChild;

    examineBoxAfterEntering(childBox, previousBreakAfterValue);
    // Unless the child is unsplittable, or if the child establishes an inner
    // multicol container, we descend into its subtree for further examination.
    if (childBox.getPaginationBreakability() != LayoutBox::ForbidBreaks &&
        (!childBox.isLayoutBlockFlow() ||
         !toLayoutBlockFlow(childBox).multiColumnFlowThread()))
      traverseSubtree(childBox);
    previousBreakAfterValue = childBox.breakAfter();
    examineBoxBeforeLeaving(childBox);

    m_flowThreadOffset -= offsetForThisChild;
  }
}