| /* |
| * Copyright 2011 Adobe Systems Incorporated. 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 THE COPYRIGHT HOLDER “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 THE COPYRIGHT HOLDER 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. |
| */ |
| |
| #include "config.h" |
| |
| #include "RenderFlowThread.h" |
| |
| #include "HitTestRequest.h" |
| #include "HitTestResult.h" |
| #include "Node.h" |
| #include "PaintInfo.h" |
| #include "RenderBoxRegionInfo.h" |
| #include "RenderLayer.h" |
| #include "RenderRegion.h" |
| #include "RenderView.h" |
| #include "TransformState.h" |
| #include "WebKitNamedFlow.h" |
| |
| namespace WebCore { |
| |
| RenderFlowThread::RenderFlowThread(Node* node, const AtomicString& flowThread) |
| : RenderBlock(node) |
| , m_flowThread(flowThread) |
| , m_hasValidRegions(false) |
| , m_regionsInvalidated(false) |
| , m_regionsHaveUniformLogicalWidth(true) |
| , m_regionsHaveUniformLogicalHeight(true) |
| { |
| setIsAnonymous(false); |
| setInRenderFlowThread(); |
| } |
| |
| void RenderFlowThread::clearRenderRegionRangeMap() |
| { |
| deleteAllValues(m_regionRangeMap); |
| m_regionRangeMap.clear(); |
| } |
| |
| RenderFlowThread::~RenderFlowThread() |
| { |
| clearRenderRegionRangeMap(); |
| } |
| |
| PassRefPtr<RenderStyle> RenderFlowThread::createFlowThreadStyle(RenderStyle* parentStyle) |
| { |
| RefPtr<RenderStyle> newStyle(RenderStyle::create()); |
| newStyle->inheritFrom(parentStyle); |
| newStyle->setDisplay(BLOCK); |
| newStyle->setPosition(AbsolutePosition); |
| newStyle->setZIndex(0); |
| newStyle->setLeft(Length(0, Fixed)); |
| newStyle->setTop(Length(0, Fixed)); |
| newStyle->setWidth(Length(100, Percent)); |
| newStyle->setHeight(Length(100, Percent)); |
| newStyle->font().update(0); |
| |
| return newStyle.release(); |
| } |
| |
| void RenderFlowThread::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle) |
| { |
| RenderBlock::styleDidChange(diff, oldStyle); |
| |
| if (oldStyle && oldStyle->writingMode() != style()->writingMode()) |
| m_regionsInvalidated = true; |
| } |
| |
| RenderObject* RenderFlowThread::nextRendererForNode(Node* node) const |
| { |
| FlowThreadChildList::const_iterator it = m_flowThreadChildList.begin(); |
| FlowThreadChildList::const_iterator end = m_flowThreadChildList.end(); |
| |
| for (; it != end; ++it) { |
| RenderObject* child = *it; |
| ASSERT(child->node()); |
| unsigned short position = node->compareDocumentPosition(child->node()); |
| if (position & Node::DOCUMENT_POSITION_FOLLOWING) |
| return child; |
| } |
| |
| return 0; |
| } |
| |
| RenderObject* RenderFlowThread::previousRendererForNode(Node* node) const |
| { |
| if (m_flowThreadChildList.isEmpty()) |
| return 0; |
| |
| FlowThreadChildList::const_iterator begin = m_flowThreadChildList.begin(); |
| FlowThreadChildList::const_iterator end = m_flowThreadChildList.end(); |
| FlowThreadChildList::const_iterator it = end; |
| |
| do { |
| --it; |
| RenderObject* child = *it; |
| ASSERT(child->node()); |
| unsigned short position = node->compareDocumentPosition(child->node()); |
| if (position & Node::DOCUMENT_POSITION_PRECEDING) |
| return child; |
| } while (it != begin); |
| |
| return 0; |
| } |
| |
| void RenderFlowThread::addFlowChild(RenderObject* newChild, RenderObject* beforeChild) |
| { |
| // The child list is used to sort the flow thread's children render objects |
| // based on their corresponding nodes DOM order. The list is needed to avoid searching the whole DOM. |
| |
| // Do not add anonymous objects. |
| if (!newChild->node()) |
| return; |
| |
| if (beforeChild) |
| m_flowThreadChildList.insertBefore(beforeChild, newChild); |
| else |
| m_flowThreadChildList.add(newChild); |
| } |
| |
| void RenderFlowThread::removeFlowChild(RenderObject* child) |
| { |
| m_flowThreadChildList.remove(child); |
| } |
| |
| // Compare two regions to determine in which one the content should flow first. |
| // The function returns true if the first passed region is "less" than the second passed region. |
| // If the first region appears before second region in DOM, |
| // the first region is "less" than the second region. |
| // If the first region is "less" than the second region, the first region receives content before second region. |
| static bool compareRenderRegions(const RenderRegion* firstRegion, const RenderRegion* secondRegion) |
| { |
| ASSERT(firstRegion); |
| ASSERT(secondRegion); |
| |
| // If the regions have the same region-index, compare their position in dom. |
| ASSERT(firstRegion->node()); |
| ASSERT(secondRegion->node()); |
| |
| unsigned short position = firstRegion->node()->compareDocumentPosition(secondRegion->node()); |
| return (position & Node::DOCUMENT_POSITION_FOLLOWING); |
| } |
| |
| bool RenderFlowThread::dependsOn(RenderFlowThread* otherRenderFlowThread) const |
| { |
| if (m_layoutBeforeThreadsSet.contains(otherRenderFlowThread)) |
| return true; |
| |
| // Recursively traverse the m_layoutBeforeThreadsSet. |
| RenderFlowThreadCountedSet::const_iterator iterator = m_layoutBeforeThreadsSet.begin(); |
| RenderFlowThreadCountedSet::const_iterator end = m_layoutBeforeThreadsSet.end(); |
| for (; iterator != end; ++iterator) { |
| const RenderFlowThread* beforeFlowThread = (*iterator).first; |
| if (beforeFlowThread->dependsOn(otherRenderFlowThread)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void RenderFlowThread::addRegionToThread(RenderRegion* renderRegion) |
| { |
| ASSERT(renderRegion); |
| if (m_regionList.isEmpty()) |
| m_regionList.add(renderRegion); |
| else { |
| // Find the first region "greater" than renderRegion. |
| RenderRegionList::iterator it = m_regionList.begin(); |
| while (it != m_regionList.end() && !compareRenderRegions(renderRegion, *it)) |
| ++it; |
| m_regionList.insertBefore(it, renderRegion); |
| } |
| |
| ASSERT(!renderRegion->isValid()); |
| if (renderRegion->parentFlowThread()) { |
| if (renderRegion->parentFlowThread()->dependsOn(this)) { |
| // Register ourself to get a notification when the state changes. |
| renderRegion->parentFlowThread()->m_observerThreadsSet.add(this); |
| return; |
| } |
| |
| addDependencyOnFlowThread(renderRegion->parentFlowThread()); |
| } |
| |
| renderRegion->setIsValid(true); |
| |
| invalidateRegions(); |
| } |
| |
| void RenderFlowThread::removeRegionFromThread(RenderRegion* renderRegion) |
| { |
| ASSERT(renderRegion); |
| m_regionList.remove(renderRegion); |
| if (renderRegion->parentFlowThread()) { |
| if (!renderRegion->isValid()) { |
| renderRegion->parentFlowThread()->m_observerThreadsSet.remove(this); |
| // No need to invalidate the regions rectangles. The removed region |
| // was not taken into account. Just return here. |
| return; |
| } |
| removeDependencyOnFlowThread(renderRegion->parentFlowThread()); |
| } |
| |
| invalidateRegions(); |
| } |
| |
| void RenderFlowThread::checkInvalidRegions() |
| { |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| // The only reason a region would be invalid is because it has a parent flow thread. |
| ASSERT(region->isValid() || region->parentFlowThread()); |
| if (region->isValid() || region->parentFlowThread()->dependsOn(this)) |
| continue; |
| |
| region->parentFlowThread()->m_observerThreadsSet.remove(this); |
| addDependencyOnFlowThread(region->parentFlowThread()); |
| region->setIsValid(true); |
| invalidateRegions(); |
| } |
| |
| if (m_observerThreadsSet.isEmpty()) |
| return; |
| |
| // Notify all the flow threads that were dependent on this flow. |
| |
| // Create a copy of the list first. That's because observers might change the list when calling checkInvalidRegions. |
| Vector<RenderFlowThread*> observers; |
| copyToVector(m_observerThreadsSet, observers); |
| |
| for (size_t i = 0; i < observers.size(); ++i) { |
| RenderFlowThread* flowThread = observers.at(i); |
| flowThread->checkInvalidRegions(); |
| } |
| } |
| |
| void RenderFlowThread::addDependencyOnFlowThread(RenderFlowThread* otherFlowThread) |
| { |
| std::pair<RenderFlowThreadCountedSet::iterator, bool> result = m_layoutBeforeThreadsSet.add(otherFlowThread); |
| if (result.second) { |
| // This is the first time we see this dependency. Make sure we recalculate all the dependencies. |
| view()->setIsRenderFlowThreadOrderDirty(true); |
| } |
| } |
| |
| void RenderFlowThread::removeDependencyOnFlowThread(RenderFlowThread* otherFlowThread) |
| { |
| bool removed = m_layoutBeforeThreadsSet.remove(otherFlowThread); |
| if (removed) { |
| checkInvalidRegions(); |
| view()->setIsRenderFlowThreadOrderDirty(true); |
| } |
| } |
| |
| void RenderFlowThread::pushDependencies(RenderFlowThreadList& list) |
| { |
| for (RenderFlowThreadCountedSet::iterator iter = m_layoutBeforeThreadsSet.begin(); iter != m_layoutBeforeThreadsSet.end(); ++iter) { |
| RenderFlowThread* flowThread = (*iter).first; |
| if (list.contains(flowThread)) |
| continue; |
| flowThread->pushDependencies(list); |
| list.add(flowThread); |
| } |
| } |
| |
| class CurrentRenderFlowThreadMaintainer { |
| WTF_MAKE_NONCOPYABLE(CurrentRenderFlowThreadMaintainer); |
| public: |
| CurrentRenderFlowThreadMaintainer(RenderFlowThread* renderFlowThread) |
| : m_renderFlowThread(renderFlowThread) |
| { |
| RenderView* view = m_renderFlowThread->view(); |
| ASSERT(!view->currentRenderFlowThread()); |
| view->setCurrentRenderFlowThread(m_renderFlowThread); |
| } |
| ~CurrentRenderFlowThreadMaintainer() |
| { |
| RenderView* view = m_renderFlowThread->view(); |
| ASSERT(view->currentRenderFlowThread() == m_renderFlowThread); |
| view->setCurrentRenderFlowThread(0); |
| } |
| private: |
| RenderFlowThread* m_renderFlowThread; |
| }; |
| |
| void RenderFlowThread::layout() |
| { |
| bool regionsChanged = m_regionsInvalidated && m_everHadLayout; |
| if (m_regionsInvalidated) { |
| m_regionsInvalidated = false; |
| m_hasValidRegions = false; |
| m_regionsHaveUniformLogicalWidth = true; |
| m_regionsHaveUniformLogicalHeight = true; |
| clearRenderRegionRangeMap(); |
| LayoutUnit previousRegionLogicalWidth = 0; |
| LayoutUnit previousRegionLogicalHeight = 0; |
| if (hasRegions()) { |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| ASSERT(!region->needsLayout()); |
| |
| region->deleteAllRenderBoxRegionInfo(); |
| |
| LayoutUnit regionLogicalWidth; |
| LayoutUnit regionLogicalHeight; |
| |
| if (isHorizontalWritingMode()) { |
| regionLogicalWidth = region->contentWidth(); |
| regionLogicalHeight = region->contentHeight(); |
| } else { |
| regionLogicalWidth = region->contentHeight(); |
| regionLogicalHeight = region->contentWidth(); |
| } |
| |
| if (!m_hasValidRegions) |
| m_hasValidRegions = true; |
| else { |
| if (m_regionsHaveUniformLogicalWidth && previousRegionLogicalWidth != regionLogicalWidth) |
| m_regionsHaveUniformLogicalWidth = false; |
| if (m_regionsHaveUniformLogicalHeight && previousRegionLogicalHeight != regionLogicalHeight) |
| m_regionsHaveUniformLogicalHeight = false; |
| } |
| |
| previousRegionLogicalWidth = regionLogicalWidth; |
| } |
| |
| computeLogicalWidth(); // Called to get the maximum logical width for the region. |
| |
| LayoutUnit logicalHeight = 0; |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| LayoutRect regionRect; |
| if (isHorizontalWritingMode()) { |
| regionRect = LayoutRect(style()->direction() == LTR ? 0 : logicalWidth() - region->contentWidth(), logicalHeight, region->contentWidth(), region->contentHeight()); |
| logicalHeight += regionRect.height(); |
| } else { |
| regionRect = LayoutRect(logicalHeight, style()->direction() == LTR ? 0 : logicalWidth() - region->contentHeight(), region->contentWidth(), region->contentHeight()); |
| logicalHeight += regionRect.width(); |
| } |
| region->setRegionRect(regionRect); |
| } |
| } |
| } |
| |
| CurrentRenderFlowThreadMaintainer currentFlowThreadSetter(this); |
| LayoutStateMaintainer statePusher(view(), this, regionsChanged); |
| RenderBlock::layout(); |
| statePusher.pop(); |
| } |
| |
| void RenderFlowThread::computeLogicalWidth() |
| { |
| LayoutUnit logicalWidth = 0; |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| ASSERT(!region->needsLayout()); |
| logicalWidth = max(isHorizontalWritingMode() ? region->contentWidth() : region->contentHeight(), logicalWidth); |
| } |
| setLogicalWidth(logicalWidth); |
| |
| // If the regions have non-uniform logical widths, then insert inset information for the RenderFlowThread. |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| |
| LayoutUnit regionLogicalWidth = isHorizontalWritingMode() ? region->contentWidth() : region->contentHeight(); |
| if (regionLogicalWidth != logicalWidth) { |
| LayoutUnit logicalLeft = style()->direction() == LTR ? 0 : logicalWidth - regionLogicalWidth; |
| region->setRenderBoxRegionInfo(this, logicalLeft, regionLogicalWidth, false); |
| } |
| } |
| } |
| |
| void RenderFlowThread::computeLogicalHeight() |
| { |
| int logicalHeight = 0; |
| |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| ASSERT(!region->needsLayout()); |
| logicalHeight += isHorizontalWritingMode() ? region->contentHeight() : region->contentWidth(); |
| } |
| |
| setLogicalHeight(logicalHeight); |
| } |
| |
| void RenderFlowThread::paintIntoRegion(PaintInfo& paintInfo, RenderRegion* region, const LayoutPoint& paintOffset) |
| { |
| GraphicsContext* context = paintInfo.context; |
| if (!context) |
| return; |
| |
| // Adjust the clipping rect for the region. |
| // paintOffset contains the offset where the painting should occur |
| // adjusted with the region padding and border. |
| LayoutRect regionRect(region->regionRect()); |
| LayoutRect regionOverflowRect(region->regionOverflowRect()); |
| LayoutRect regionClippingRect(paintOffset + (regionOverflowRect.location() - regionRect.location()), regionOverflowRect.size()); |
| |
| PaintInfo info(paintInfo); |
| info.rect.intersect(regionClippingRect); |
| |
| if (!info.rect.isEmpty()) { |
| context->save(); |
| |
| context->clip(regionClippingRect); |
| |
| // RenderFlowThread should start painting its content in a position that is offset |
| // from the region rect's current position. The amount of offset is equal to the location of |
| // region in flow coordinates. |
| LayoutPoint renderFlowThreadOffset; |
| if (style()->isFlippedBlocksWritingMode()) { |
| LayoutRect flippedRegionRect(regionRect); |
| flipForWritingMode(flippedRegionRect); |
| renderFlowThreadOffset = LayoutPoint(paintOffset - flippedRegionRect.location()); |
| } else |
| renderFlowThreadOffset = LayoutPoint(paintOffset - regionRect.location()); |
| |
| context->translate(renderFlowThreadOffset.x(), renderFlowThreadOffset.y()); |
| info.rect.moveBy(-renderFlowThreadOffset); |
| |
| layer()->paint(context, info.rect, 0, 0, region, RenderLayer::PaintLayerTemporaryClipRects); |
| |
| context->restore(); |
| } |
| } |
| |
| bool RenderFlowThread::hitTestRegion(RenderRegion* region, const HitTestRequest& request, HitTestResult& result, const LayoutPoint& pointInContainer, const LayoutPoint& accumulatedOffset) |
| { |
| LayoutRect regionRect(region->regionRect()); |
| LayoutRect regionOverflowRect = region->regionOverflowRect(); |
| LayoutRect regionClippingRect(accumulatedOffset + (regionOverflowRect.location() - regionRect.location()), regionOverflowRect.size()); |
| if (!regionClippingRect.contains(pointInContainer)) |
| return false; |
| |
| LayoutPoint renderFlowThreadOffset; |
| if (style()->isFlippedBlocksWritingMode()) { |
| LayoutRect flippedRegionRect(regionRect); |
| flipForWritingMode(flippedRegionRect); |
| renderFlowThreadOffset = LayoutPoint(accumulatedOffset - flippedRegionRect.location()); |
| } else |
| renderFlowThreadOffset = LayoutPoint(accumulatedOffset - regionRect.location()); |
| |
| LayoutPoint transformedPoint(pointInContainer.x() - renderFlowThreadOffset.x(), pointInContainer.y() - renderFlowThreadOffset.y()); |
| |
| // Always ignore clipping, since the RenderFlowThread has nothing to do with the bounds of the FrameView. |
| HitTestRequest newRequest(request.type() & HitTestRequest::IgnoreClipping); |
| |
| RenderRegion* oldRegion = result.region(); |
| result.setRegion(region); |
| LayoutPoint oldPoint = result.point(); |
| result.setPoint(transformedPoint); |
| bool isPointInsideFlowThread = layer()->hitTest(newRequest, result); |
| result.setPoint(oldPoint); |
| result.setRegion(oldRegion); |
| |
| // FIXME: Should we set result.m_localPoint back to the RenderRegion's coordinate space or leave it in the RenderFlowThread's coordinate |
| // space? Right now it's staying in the RenderFlowThread's coordinate space, which may end up being ok. We will know more when we get around to |
| // patching positionForPoint. |
| return isPointInsideFlowThread; |
| } |
| |
| bool RenderFlowThread::shouldRepaint(const LayoutRect& r) const |
| { |
| if (view()->printing() || r.isEmpty()) |
| return false; |
| |
| return true; |
| } |
| |
| void RenderFlowThread::repaintRectangleInRegions(const LayoutRect& repaintRect, bool immediate) |
| { |
| if (!shouldRepaint(repaintRect)) |
| return; |
| |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| |
| // We only have to issue a repaint in this region if the region rect intersects the repaint rect. |
| LayoutRect flippedRegionRect(region->regionRect()); |
| LayoutRect flippedRegionOverflowRect(region->regionOverflowRect()); |
| flipForWritingMode(flippedRegionRect); // Put the region rects into physical coordinates. |
| flipForWritingMode(flippedRegionOverflowRect); |
| |
| LayoutRect clippedRect(flippedRegionOverflowRect); |
| clippedRect.intersect(repaintRect); |
| if (clippedRect.isEmpty()) |
| continue; |
| |
| // Put the region rect into the region's physical coordinate space. |
| clippedRect.setLocation(region->contentBoxRect().location() + (repaintRect.location() - flippedRegionRect.location())); |
| |
| // Now switch to the region's writing mode coordinate space and let it repaint itself. |
| region->flipForWritingMode(clippedRect); |
| LayoutStateDisabler layoutStateDisabler(view()); // We can't use layout state to repaint, since the region is somewhere else. |
| region->repaintRectangle(clippedRect, immediate); |
| } |
| } |
| |
| RenderRegion* RenderFlowThread::renderRegionForLine(LayoutUnit position, bool extendLastRegion) const |
| { |
| ASSERT(!m_regionsInvalidated); |
| |
| // If no region matches the position and extendLastRegion is true, it will return |
| // the last valid region. It is similar to auto extending the size of the last region. |
| RenderRegion* lastValidRegion = 0; |
| |
| // FIXME: The regions are always in order, optimize this search. |
| bool useHorizontalWritingMode = isHorizontalWritingMode(); |
| for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| |
| if (position <= 0) |
| return region; |
| |
| LayoutRect regionRect = region->regionRect(); |
| |
| if ((useHorizontalWritingMode && position < regionRect.maxY()) || (!useHorizontalWritingMode && position < regionRect.maxX())) |
| return region; |
| |
| if (extendLastRegion) |
| lastValidRegion = region; |
| } |
| |
| return lastValidRegion; |
| } |
| |
| LayoutUnit RenderFlowThread::regionLogicalWidthForLine(LayoutUnit position) const |
| { |
| RenderRegion* region = renderRegionForLine(position, true); |
| if (!region) |
| return contentLogicalWidth(); |
| return isHorizontalWritingMode() ? region->regionRect().width() : region->regionRect().height(); |
| } |
| |
| LayoutUnit RenderFlowThread::regionLogicalHeightForLine(LayoutUnit position) const |
| { |
| RenderRegion* region = renderRegionForLine(position); |
| if (!region) |
| return 0; |
| return isHorizontalWritingMode() ? region->regionRect().height() : region->regionRect().width(); |
| } |
| |
| LayoutUnit RenderFlowThread::regionRemainingLogicalHeightForLine(LayoutUnit position, PageBoundaryRule pageBoundaryRule) const |
| { |
| RenderRegion* region = renderRegionForLine(position); |
| if (!region) |
| return 0; |
| |
| LayoutUnit regionLogicalBottom = isHorizontalWritingMode() ? region->regionRect().maxY() : region->regionRect().maxX(); |
| LayoutUnit remainingHeight = regionLogicalBottom - position; |
| if (pageBoundaryRule == IncludePageBoundary) { |
| // If IncludePageBoundary is set, the line exactly on the top edge of a |
| // region will act as being part of the previous region. |
| LayoutUnit regionHeight = isHorizontalWritingMode() ? region->regionRect().height() : region->regionRect().width(); |
| remainingHeight = layoutMod(remainingHeight, regionHeight); |
| } |
| return remainingHeight; |
| } |
| |
| RenderRegion* RenderFlowThread::mapFromFlowToRegion(TransformState& transformState) const |
| { |
| if (!hasValidRegions()) |
| return 0; |
| |
| LayoutRect boxRect = transformState.mappedQuad().enclosingBoundingBox(); |
| flipForWritingMode(boxRect); |
| |
| // FIXME: We need to refactor RenderObject::absoluteQuads to be able to split the quads across regions, |
| // for now we just take the center of the mapped enclosing box and map it to a region. |
| // Note: Using the center in order to avoid rounding errors. |
| |
| LayoutPoint center = boxRect.center(); |
| RenderRegion* renderRegion = renderRegionForLine(isHorizontalWritingMode() ? center.y() : center.x(), true); |
| if (!renderRegion) |
| return 0; |
| |
| LayoutRect flippedRegionRect(renderRegion->regionRect()); |
| flipForWritingMode(flippedRegionRect); |
| |
| transformState.move(renderRegion->contentBoxRect().location() - flippedRegionRect.location()); |
| |
| return renderRegion; |
| } |
| |
| void RenderFlowThread::removeRenderBoxRegionInfo(RenderBox* box) |
| { |
| if (!hasRegions()) |
| return; |
| |
| RenderRegion* startRegion; |
| RenderRegion* endRegion; |
| getRegionRangeForBox(box, startRegion, endRegion); |
| |
| for (RenderRegionList::iterator iter = m_regionList.find(startRegion); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| region->removeRenderBoxRegionInfo(box); |
| if (region == endRegion) |
| break; |
| } |
| |
| delete m_regionRangeMap.take(box); |
| } |
| |
| bool RenderFlowThread::logicalWidthChangedInRegions(const RenderBlock* block, LayoutUnit offsetFromLogicalTopOfFirstPage) |
| { |
| if (!hasRegions() || block == this) // Not necessary, since if any region changes, we do a full pagination relayout anyway. |
| return false; |
| |
| RenderRegion* startRegion; |
| RenderRegion* endRegion; |
| getRegionRangeForBox(block, startRegion, endRegion); |
| |
| for (RenderRegionList::iterator iter = m_regionList.find(startRegion); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| |
| if (!region->isValid()) |
| continue; |
| |
| ASSERT(!region->needsLayout()); |
| |
| RenderBoxRegionInfo* oldInfo = region->takeRenderBoxRegionInfo(block); |
| if (!oldInfo) |
| continue; |
| |
| LayoutUnit oldLogicalWidth = oldInfo->logicalWidth(); |
| delete oldInfo; |
| |
| RenderBoxRegionInfo* newInfo = block->renderBoxRegionInfo(region, offsetFromLogicalTopOfFirstPage); |
| if (!newInfo || newInfo->logicalWidth() != oldLogicalWidth) |
| return true; |
| |
| if (region == endRegion) |
| break; |
| } |
| |
| return false; |
| } |
| |
| LayoutUnit RenderFlowThread::contentLogicalWidthOfFirstRegion() const |
| { |
| if (!hasValidRegions()) |
| return 0; |
| for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| return isHorizontalWritingMode() ? region->contentWidth() : region->contentHeight(); |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| LayoutUnit RenderFlowThread::contentLogicalHeightOfFirstRegion() const |
| { |
| if (!hasValidRegions()) |
| return 0; |
| for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| return isHorizontalWritingMode() ? region->contentHeight() : region->contentWidth(); |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| LayoutUnit RenderFlowThread::contentLogicalLeftOfFirstRegion() const |
| { |
| if (!hasValidRegions()) |
| return 0; |
| for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| return isHorizontalWritingMode() ? region->regionRect().x() : region->regionRect().y(); |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| } |
| |
| RenderRegion* RenderFlowThread::firstRegion() const |
| { |
| if (!hasValidRegions()) |
| return 0; |
| for (RenderRegionList::const_iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| return region; |
| } |
| return 0; |
| } |
| |
| RenderRegion* RenderFlowThread::lastRegion() const |
| { |
| if (!hasValidRegions()) |
| return 0; |
| for (RenderRegionList::const_reverse_iterator iter = m_regionList.rbegin(); iter != m_regionList.rend(); ++iter) { |
| RenderRegion* region = *iter; |
| if (!region->isValid()) |
| continue; |
| return region; |
| } |
| return 0; |
| } |
| |
| void RenderFlowThread::clearRenderObjectCustomStyle(const RenderObject* object, |
| const RenderRegion* oldStartRegion, const RenderRegion* oldEndRegion, |
| const RenderRegion* newStartRegion, const RenderRegion* newEndRegion) |
| { |
| // Clear the styles for the object in the regions. |
| // The styles are not cleared for the regions that are contained in both ranges. |
| bool insideOldRegionRange = false; |
| bool insideNewRegionRange = false; |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| |
| if (oldStartRegion == region) |
| insideOldRegionRange = true; |
| if (newStartRegion == region) |
| insideNewRegionRange = true; |
| |
| if (!(insideOldRegionRange && insideNewRegionRange)) |
| region->clearObjectStyleInRegion(object); |
| |
| if (oldEndRegion == region) |
| insideOldRegionRange = false; |
| if (newEndRegion == region) |
| insideNewRegionRange = false; |
| } |
| } |
| |
| void RenderFlowThread::setRegionRangeForBox(const RenderBox* box, LayoutUnit offsetFromLogicalTopOfFirstPage) |
| { |
| // FIXME: Not right for differing writing-modes. |
| RenderRegion* startRegion = renderRegionForLine(offsetFromLogicalTopOfFirstPage, true); |
| RenderRegion* endRegion = renderRegionForLine(offsetFromLogicalTopOfFirstPage + box->logicalHeight(), true); |
| RenderRegionRange* range = m_regionRangeMap.get(box); |
| if (range) { |
| // If nothing changed, just bail. |
| if (range->startRegion() == startRegion && range->endRegion() == endRegion) |
| return; |
| |
| RenderRegion* oldStartRegion = range->startRegion(); |
| RenderRegion* oldEndRegion = range->endRegion(); |
| // Delete any info that we find before our new startRegion and after our new endRegion. |
| for (RenderRegionList::iterator iter = m_regionList.begin(); iter != m_regionList.end(); ++iter) { |
| RenderRegion* region = *iter; |
| if (region == startRegion) { |
| iter = m_regionList.find(endRegion); |
| continue; |
| } |
| |
| region->removeRenderBoxRegionInfo(box); |
| |
| if (region == range->endRegion()) |
| break; |
| } |
| |
| range->setRange(startRegion, endRegion); |
| clearRenderObjectCustomStyle(box, oldStartRegion, oldEndRegion, startRegion, endRegion); |
| return; |
| } |
| range = new RenderRegionRange(startRegion, endRegion); |
| m_regionRangeMap.set(box, range); |
| } |
| |
| void RenderFlowThread::getRegionRangeForBox(const RenderBox* box, RenderRegion*& startRegion, RenderRegion*& endRegion) const |
| { |
| startRegion = 0; |
| endRegion = 0; |
| RenderRegionRange* range = m_regionRangeMap.get(box); |
| if (!range) |
| return; |
| startRegion = range->startRegion(); |
| endRegion = range->endRegion(); |
| } |
| |
| WebKitNamedFlow* RenderFlowThread::ensureNamedFlow() |
| { |
| if (!m_namedFlow) |
| m_namedFlow = WebKitNamedFlow::create(); |
| |
| return m_namedFlow.get(); |
| } |
| |
| } // namespace WebCore |