blob: e6e77c7b2c2d4c397cbc779c293d5ba877061ab1 [file] [log] [blame]
/*
* Copyright (C) 2006 Apple Computer, Inc.
* Copyright (C) 2006 Alexander Kellett <lypanov@kde.org>
* Copyright (C) 2006 Oliver Hunt <ojh16@student.canterbury.ac.nz>
* Copyright (C) 2007 Nikolas Zimmermann <zimmermann@kde.org>
* Copyright (C) 2008 Rob Buis <buis@kde.org>
* Copyright (C) 2009 Dirk Schulze <krit@webkit.org>
* Copyright (C) Research In Motion Limited 2010-2012. All rights reserved.
* Copyright (C) 2012 Google Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "core/layout/svg/LayoutSVGText.h"
#include "core/editing/PositionWithAffinity.h"
#include "core/layout/HitTestRequest.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutAnalyzer.h"
#include "core/layout/LayoutState.h"
#include "core/layout/PointerEventsHitRules.h"
#include "core/layout/api/LineLayoutItem.h"
#include "core/layout/svg/LayoutSVGInline.h"
#include "core/layout/svg/LayoutSVGInlineText.h"
#include "core/layout/svg/LayoutSVGRoot.h"
#include "core/layout/svg/SVGLayoutSupport.h"
#include "core/layout/svg/SVGResourcesCache.h"
#include "core/layout/svg/SVGTextLayoutAttributesBuilder.h"
#include "core/layout/svg/line/SVGRootInlineBox.h"
#include "core/paint/SVGTextPainter.h"
#include "core/style/ShadowList.h"
#include "core/svg/SVGTextElement.h"
#include "platform/FloatConversion.h"
#include "platform/geometry/FloatQuad.h"
namespace blink {
namespace {
const LayoutSVGText* findTextRoot(const LayoutObject* start)
{
ASSERT(start);
for (; start; start = start->parent()) {
if (start->isSVGText())
return toLayoutSVGText(start);
}
return nullptr;
}
} // namespace
LayoutSVGText::LayoutSVGText(SVGTextElement* node)
: LayoutSVGBlock(node)
, m_needsReordering(false)
, m_needsPositioningValuesUpdate(false)
, m_needsTransformUpdate(true)
, m_needsTextMetricsUpdate(false)
{
}
LayoutSVGText::~LayoutSVGText()
{
ASSERT(m_descendantTextNodes.isEmpty());
}
void LayoutSVGText::willBeDestroyed()
{
m_descendantTextNodes.clear();
LayoutSVGBlock::willBeDestroyed();
}
bool LayoutSVGText::isChildAllowed(LayoutObject* child, const ComputedStyle&) const
{
return child->isSVGInline() || (child->isText() && SVGLayoutSupport::isLayoutableTextNode(child));
}
LayoutSVGText* LayoutSVGText::locateLayoutSVGTextAncestor(LayoutObject* start)
{
return const_cast<LayoutSVGText*>(findTextRoot(start));
}
const LayoutSVGText* LayoutSVGText::locateLayoutSVGTextAncestor(const LayoutObject* start)
{
return findTextRoot(start);
}
static inline void collectDescendantTextNodes(LayoutSVGText& textRoot, Vector<LayoutSVGInlineText*>& descendantTextNodes)
{
for (LayoutObject* descendant = textRoot.firstChild(); descendant; descendant = descendant->nextInPreOrder(&textRoot)) {
if (descendant->isSVGInlineText())
descendantTextNodes.append(toLayoutSVGInlineText(descendant));
}
}
void LayoutSVGText::invalidatePositioningValues(LayoutInvalidationReasonForTracing reason)
{
m_descendantTextNodes.clear();
setNeedsPositioningValuesUpdate();
setNeedsLayoutAndFullPaintInvalidation(reason);
}
void LayoutSVGText::subtreeChildWasAdded()
{
if (beingDestroyed() || !everHadLayout()) {
ASSERT(m_descendantTextNodes.isEmpty());
return;
}
if (documentBeingDestroyed())
return;
// The positioning elements cache depends on the size of each text layoutObject in the
// subtree. If this changes, clear the cache. It will be rebuilt on the next layout.
invalidatePositioningValues(LayoutInvalidationReason::ChildChanged);
setNeedsTextMetricsUpdate();
}
void LayoutSVGText::subtreeChildWillBeRemoved()
{
if (beingDestroyed() || !everHadLayout()) {
ASSERT(m_descendantTextNodes.isEmpty());
return;
}
// The positioning elements cache depends on the size of each text layoutObject in the
// subtree. If this changes, clear the cache. It will be rebuilt on the next layout.
invalidatePositioningValues(LayoutInvalidationReason::ChildChanged);
setNeedsTextMetricsUpdate();
}
void LayoutSVGText::subtreeTextDidChange()
{
ASSERT(!beingDestroyed());
if (!everHadLayout()) {
ASSERT(m_descendantTextNodes.isEmpty());
return;
}
// The positioning elements cache depends on the size of each text object in
// the subtree. If this changes, clear the cache and mark it for rebuilding
// in the next layout.
invalidatePositioningValues(LayoutInvalidationReason::TextChanged);
setNeedsTextMetricsUpdate();
}
static inline void updateFontAndMetrics(LayoutSVGText& textRoot)
{
bool lastCharacterWasWhiteSpace = true;
for (LayoutObject* descendant = textRoot.firstChild(); descendant; descendant = descendant->nextInPreOrder(&textRoot)) {
if (!descendant->isSVGInlineText())
continue;
LayoutSVGInlineText& text = toLayoutSVGInlineText(*descendant);
text.updateScaledFont();
text.updateMetricsList(lastCharacterWasWhiteSpace);
}
}
static inline void checkDescendantTextNodeConsistency(LayoutSVGText& text, Vector<LayoutSVGInlineText*>& expectedDescendantTextNodes)
{
#if ENABLE(ASSERT)
Vector<LayoutSVGInlineText*> newDescendantTextNodes;
collectDescendantTextNodes(text, newDescendantTextNodes);
ASSERT(newDescendantTextNodes == expectedDescendantTextNodes);
#endif
}
void LayoutSVGText::layout()
{
ASSERT(needsLayout());
// This flag is set and reset as needed only within this function.
ASSERT(!m_needsReordering);
LayoutAnalyzer::Scope analyzer(*this);
bool updateParentBoundaries = false;
if (m_needsTransformUpdate) {
m_localTransform = toSVGTextElement(node())->calculateAnimatedLocalTransform();
m_needsTransformUpdate = false;
updateParentBoundaries = true;
}
// When laying out initially, build the character data map and propagate
// resulting layout attributes to all LayoutSVGInlineText children in the
// subtree.
if (!everHadLayout()) {
m_needsPositioningValuesUpdate = true;
m_needsTextMetricsUpdate = true;
}
// If the root layout size changed (eg. window size changes), or the screen
// scale factor has changed, then recompute the on-screen font size. Since
// the computation of layout attributes uses the text metrics, we need to
// update them before updating the layout attributes.
if (m_needsTextMetricsUpdate) {
updateFontAndMetrics(*this);
m_needsTextMetricsUpdate = false;
updateParentBoundaries = true;
}
// When the x/y/dx/dy/rotate lists change, we need to recompute the layout
// attributes.
if (m_needsPositioningValuesUpdate) {
m_descendantTextNodes.clear();
collectDescendantTextNodes(*this, m_descendantTextNodes);
SVGTextLayoutAttributesBuilder(*this).buildLayoutAttributes();
m_needsPositioningValuesUpdate = false;
m_needsReordering = true;
updateParentBoundaries = true;
}
checkDescendantTextNodeConsistency(*this, m_descendantTextNodes);
// Reduced version of LayoutBlock::layoutBlock(), which only takes care of SVG text.
// All if branches that could cause early exit in LayoutBlocks layoutBlock() method are turned into assertions.
ASSERT(!isInline());
ASSERT(!simplifiedLayout());
ASSERT(!scrollsOverflow());
ASSERT(!hasControlClip());
ASSERT(!positionedObjects());
ASSERT(!isAnonymousBlock());
if (!firstChild())
setChildrenInline(true);
// FIXME: We need to find a way to only layout the child boxes, if needed.
FloatRect oldBoundaries = objectBoundingBox();
ASSERT(childrenInline());
rebuildFloatsFromIntruding();
LayoutUnit beforeEdge = borderBefore() + paddingBefore();
LayoutUnit afterEdge = borderAfter() + paddingAfter() + scrollbarLogicalHeight();
setLogicalHeight(beforeEdge);
LayoutState state(*this, locationOffset());
layoutInlineChildren(true, afterEdge);
m_needsReordering = false;
// If we don't have any line boxes, then make sure the frame rect is still cleared.
if (!firstLineBox())
setFrameRect(LayoutRect());
m_overflow.reset();
addVisualEffectOverflow();
if (!updateParentBoundaries)
updateParentBoundaries = oldBoundaries != objectBoundingBox();
// Invalidate all resources of this client if our layout changed.
if (everHadLayout() && selfNeedsLayout())
SVGResourcesCache::clientLayoutChanged(this);
// If our bounds changed, notify the parents.
if (updateParentBoundaries)
LayoutSVGBlock::setNeedsBoundariesUpdate();
ASSERT(!m_needsReordering);
ASSERT(!m_needsTransformUpdate);
ASSERT(!m_needsTextMetricsUpdate);
ASSERT(!m_needsPositioningValuesUpdate);
clearNeedsLayout();
}
RootInlineBox* LayoutSVGText::createRootInlineBox()
{
RootInlineBox* box = new SVGRootInlineBox(LineLayoutItem(this));
box->setHasVirtualLogicalHeight();
return box;
}
bool LayoutSVGText::nodeAtFloatPoint(HitTestResult& result, const FloatPoint& pointInParent, HitTestAction hitTestAction)
{
// We only draw in the foreground phase, so we only hit-test then.
if (hitTestAction != HitTestForeground)
return false;
PointerEventsHitRules hitRules(PointerEventsHitRules::SVG_TEXT_HITTESTING, result.hitTestRequest(), style()->pointerEvents());
bool isVisible = (style()->visibility() == EVisibility::Visible);
if (isVisible || !hitRules.requireVisible) {
if ((hitRules.canHitBoundingBox && !objectBoundingBox().isEmpty())
|| (hitRules.canHitStroke && (style()->svgStyle().hasStroke() || !hitRules.requireStroke))
|| (hitRules.canHitFill && (style()->svgStyle().hasFill() || !hitRules.requireFill))) {
FloatPoint localPoint;
if (!SVGLayoutSupport::transformToUserSpaceAndCheckClipping(this, localToSVGParentTransform(), pointInParent, localPoint))
return false;
HitTestLocation hitTestLocation(localPoint);
if (LayoutBlock::nodeAtPoint(result, hitTestLocation, LayoutPoint(), hitTestAction))
return true;
// Consider the bounding box if requested.
if (hitRules.canHitBoundingBox && objectBoundingBox().contains(localPoint)) {
const LayoutPoint& localLayoutPoint = roundedLayoutPoint(localPoint);
updateHitTestResult(result, localLayoutPoint);
if (result.addNodeToListBasedTestResult(node(), localLayoutPoint) == StopHitTesting)
return true;
}
}
}
return false;
}
PositionWithAffinity LayoutSVGText::positionForPoint(const LayoutPoint& pointInContents)
{
RootInlineBox* rootBox = firstRootBox();
if (!rootBox)
return createPositionWithAffinity(0);
LayoutPoint clippedPointInContents(pointInContents);
clippedPointInContents.clampNegativeToZero();
ASSERT(!rootBox->nextRootBox());
ASSERT(childrenInline());
InlineBox* closestBox = toSVGRootInlineBox(rootBox)->closestLeafChildForPosition(clippedPointInContents);
if (!closestBox)
return createPositionWithAffinity(0);
return closestBox->getLineLayoutItem().positionForPoint(LayoutPoint(clippedPointInContents.x(), closestBox->y()));
}
void LayoutSVGText::absoluteQuads(Vector<FloatQuad>& quads) const
{
quads.append(localToAbsoluteQuad(strokeBoundingBox()));
}
void LayoutSVGText::paint(const PaintInfo& paintInfo, const LayoutPoint&) const
{
SVGTextPainter(*this).paint(paintInfo);
}
FloatRect LayoutSVGText::strokeBoundingBox() const
{
FloatRect strokeBoundaries = objectBoundingBox();
const SVGComputedStyle& svgStyle = style()->svgStyle();
if (!svgStyle.hasStroke())
return strokeBoundaries;
ASSERT(node());
ASSERT(node()->isSVGElement());
SVGLengthContext lengthContext(toSVGElement(node()));
strokeBoundaries.inflate(lengthContext.valueForLength(svgStyle.strokeWidth()));
return strokeBoundaries;
}
FloatRect LayoutSVGText::paintInvalidationRectInLocalSVGCoordinates() const
{
FloatRect paintInvalidationRect = strokeBoundingBox();
SVGLayoutSupport::intersectPaintInvalidationRectWithResources(this, paintInvalidationRect);
if (const ShadowList* textShadow = style()->textShadow())
textShadow->adjustRectForShadow(paintInvalidationRect);
return paintInvalidationRect;
}
bool LayoutSVGText::isObjectBoundingBoxValid() const
{
// If we don't have any line boxes, then consider the bbox invalid.
return firstLineBox();
}
void LayoutSVGText::addChild(LayoutObject* child, LayoutObject* beforeChild)
{
LayoutSVGBlock::addChild(child, beforeChild);
SVGResourcesCache::clientWasAddedToTree(child, child->styleRef());
subtreeChildWasAdded();
}
void LayoutSVGText::removeChild(LayoutObject* child)
{
SVGResourcesCache::clientWillBeRemovedFromTree(child);
subtreeChildWillBeRemoved();
LayoutSVGBlock::removeChild(child);
}
void LayoutSVGText::invalidateTreeIfNeeded(const PaintInvalidationState& paintInvalidationState)
{
// TODO(wangxianzhu): Verify if the inherited LayoutBoxModelObject::invalidateTreeIfNeeded()
// is applicable here. If yes, remove this overriding method.
LayoutObject::invalidateTreeIfNeeded(paintInvalidationState);
}
} // namespace blink