Index: Source/WebCore/rendering/svg/RenderSVGText.cpp |
=================================================================== |
--- Source/WebCore/rendering/svg/RenderSVGText.cpp (revision 118874) |
+++ Source/WebCore/rendering/svg/RenderSVGText.cpp (working copy) |
@@ -57,12 +57,17 @@ |
RenderSVGText::RenderSVGText(SVGTextElement* node) |
: RenderSVGBlock(node) |
, m_needsReordering(false) |
- , m_needsPositioningValuesUpdate(true) |
+ , m_needsPositioningValuesUpdate(false) |
, m_needsTransformUpdate(true) |
- , m_needsTextMetricsUpdate(true) |
+ , m_needsTextMetricsUpdate(false) |
{ |
} |
+RenderSVGText::~RenderSVGText() |
+{ |
+ ASSERT(m_layoutAttributes.isEmpty()); |
+} |
+ |
bool RenderSVGText::isChildAllowed(RenderObject* child, RenderStyle*) const |
{ |
return child->isInline(); |
@@ -110,31 +115,19 @@ |
SVGRenderSupport::mapLocalToContainer(this, repaintContainer, transformState, wasFixed); |
} |
-static inline void recursiveUpdateLayoutAttributes(RenderObject* start, SVGTextLayoutAttributesBuilder& builder) |
+static inline void collectLayoutAttributes(RenderObject* text, Vector<SVGTextLayoutAttributes*>& attributes) |
{ |
- if (start->isSVGInlineText()) { |
- builder.buildLayoutAttributesForTextRenderer(toRenderSVGInlineText(start)); |
- return; |
+ for (RenderObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { |
+ if (descendant->isSVGInlineText()) |
+ attributes.append(toRenderSVGInlineText(descendant)->layoutAttributes()); |
} |
- |
- for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) |
- recursiveUpdateLayoutAttributes(child, builder); |
} |
-void RenderSVGText::layoutAttributesChanged(RenderObject* child) |
-{ |
- ASSERT(child); |
- if (m_needsPositioningValuesUpdate) |
- return; |
- FontCachePurgePreventer fontCachePurgePreventer; |
- recursiveUpdateLayoutAttributes(child, m_layoutAttributesBuilder); |
- rebuildLayoutAttributes(); |
-} |
- |
static inline bool findPreviousAndNextAttributes(RenderObject* start, RenderSVGInlineText* locateElement, bool& stopAfterNext, SVGTextLayoutAttributes*& previous, SVGTextLayoutAttributes*& next) |
{ |
ASSERT(start); |
ASSERT(locateElement); |
+ // FIXME: Make this iterative. |
for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { |
if (child->isSVGInlineText()) { |
RenderSVGInlineText* text = toRenderSVGInlineText(child); |
@@ -162,40 +155,177 @@ |
return false; |
} |
-void RenderSVGText::layoutAttributesWillBeDestroyed(RenderSVGInlineText* text, Vector<SVGTextLayoutAttributes*>& affectedAttributes) |
+inline bool RenderSVGText::shouldHandleSubtreeMutations() const |
{ |
+ if (beingDestroyed() || !everHadLayout()) { |
+ ASSERT(m_layoutAttributes.isEmpty()); |
+ ASSERT(!m_layoutAttributesBuilder.numberOfTextPositioningElements()); |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+void RenderSVGText::subtreeChildWasAdded(RenderObject* child) |
+{ |
+ ASSERT(child); |
+ if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) |
+ return; |
+ |
+ // The positioning elements cache doesn't include the new 'child' yet. Clear the |
+ // cache, as the next buildLayoutAttributesForTextRenderer() call rebuilds it. |
+ m_layoutAttributesBuilder.clearTextPositioningElements(); |
+ |
+ // Detect changes in layout attributes and only measure those text parts that have changed! |
+ Vector<SVGTextLayoutAttributes*> newLayoutAttributes; |
+ collectLayoutAttributes(this, newLayoutAttributes); |
+ if (newLayoutAttributes.isEmpty()) { |
+ ASSERT(m_layoutAttributes.isEmpty()); |
+ return; |
+ } |
+ |
+ // Compare m_layoutAttributes with newLayoutAttributes to figure out which attribute got added. |
+ size_t size = newLayoutAttributes.size(); |
+ SVGTextLayoutAttributes* attributes = 0; |
+ for (size_t i = 0; i < size; ++i) { |
+ attributes = newLayoutAttributes[i]; |
+ if (m_layoutAttributes.find(attributes) == notFound) { |
+ // Every time this is invoked, there's only a single new entry in the newLayoutAttributes list, compared to the old in m_layoutAttributes. |
+ bool stopAfterNext = false; |
+ SVGTextLayoutAttributes* previous = 0; |
+ SVGTextLayoutAttributes* next = 0; |
+ ASSERT_UNUSED(child, attributes->context() == child); |
+ findPreviousAndNextAttributes(this, attributes->context(), stopAfterNext, previous, next); |
+ |
+ if (previous) |
+ m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(previous->context()); |
+ m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(attributes->context()); |
+ if (next) |
+ m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(next->context()); |
+ break; |
+ } |
+ } |
+ |
+#ifndef NDEBUG |
+ // Verify that m_layoutAttributes only differs by a maximum of one entry. |
+ for (size_t i = 0; i < size; ++i) |
+ ASSERT(m_layoutAttributes.find(newLayoutAttributes[i]) != notFound || newLayoutAttributes[i] == attributes); |
+#endif |
+ |
+ m_layoutAttributes = newLayoutAttributes; |
+} |
+ |
+static inline void checkLayoutAttributesConsistency(RenderSVGText* text, Vector<SVGTextLayoutAttributes*>& expectedLayoutAttributes) |
+{ |
+#ifndef NDEBUG |
+ Vector<SVGTextLayoutAttributes*> newLayoutAttributes; |
+ collectLayoutAttributes(text, newLayoutAttributes); |
+ ASSERT(newLayoutAttributes == expectedLayoutAttributes); |
+#else |
+ UNUSED_PARAM(text); |
+ UNUSED_PARAM(expectedLayoutAttributes); |
+#endif |
+} |
+ |
+void RenderSVGText::willBeDestroyed() |
+{ |
+ m_layoutAttributes.clear(); |
+ m_layoutAttributesBuilder.clearTextPositioningElements(); |
+ |
+ RenderSVGBlock::willBeDestroyed(); |
+} |
+ |
+void RenderSVGText::subtreeChildWillBeRemoved(RenderSVGInlineText* text, Vector<SVGTextLayoutAttributes*, 2>& affectedAttributes) |
+{ |
ASSERT(text); |
- if (m_needsPositioningValuesUpdate) |
+ if (!shouldHandleSubtreeMutations()) |
return; |
+ checkLayoutAttributesConsistency(this, m_layoutAttributes); |
+ |
+ // The positioning elements cache depends on the size of each text renderer in the |
+ // subtree. If this changes, clear the cache. It's going to be rebuilt below. |
+ m_layoutAttributesBuilder.clearTextPositioningElements(); |
+ if (m_layoutAttributes.isEmpty()) |
+ return; |
+ |
+ // This logic requires that the 'text' child is still inserted in the tree. |
bool stopAfterNext = false; |
SVGTextLayoutAttributes* previous = 0; |
SVGTextLayoutAttributes* next = 0; |
- findPreviousAndNextAttributes(this, text, stopAfterNext, previous, next); |
+ if (!documentBeingDestroyed()) |
+ findPreviousAndNextAttributes(this, text, stopAfterNext, previous, next); |
+ |
if (previous) |
affectedAttributes.append(previous); |
if (next) |
affectedAttributes.append(next); |
+ |
+ size_t position = m_layoutAttributes.find(text->layoutAttributes()); |
+ ASSERT(position != notFound); |
+ m_layoutAttributes.remove(position); |
} |
-void RenderSVGText::invalidateTextPositioningElements() |
+void RenderSVGText::subtreeChildWasRemoved(const Vector<SVGTextLayoutAttributes*, 2>& affectedAttributes) |
{ |
- // Clear the text positioning elements. This should be called when either the children |
- // of a DOM text element have changed, or the length of the text in any child element |
- // has changed. Failure to clear may leave us with invalid elements, as other code paths |
- // do not always cause the position elements to be marked invalid before use. |
+ if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) { |
+ ASSERT(affectedAttributes.isEmpty()); |
+ return; |
+ } |
+ |
+ // This is called immediately after subtreeChildWillBeDestroyed, once the RenderSVGInlineText::willBeDestroyed() method |
+ // passes on to the base class, which removes us from the render tree. At this point we can update the layout attributes. |
+ unsigned size = affectedAttributes.size(); |
+ for (unsigned i = 0; i < size; ++i) |
+ m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(affectedAttributes[i]->context()); |
+} |
+ |
+void RenderSVGText::subtreeStyleDidChange(RenderSVGInlineText* text) |
+{ |
+ ASSERT(text); |
+ if (!shouldHandleSubtreeMutations() || documentBeingDestroyed()) |
+ return; |
+ |
+ checkLayoutAttributesConsistency(this, m_layoutAttributes); |
+ |
+ // Only update the metrics cache, but not the text positioning element cache |
+ // nor the layout attributes cached in the leaf #text renderers. |
+ FontCachePurgePreventer fontCachePurgePreventer; |
+ for (RenderObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { |
+ if (descendant->isSVGInlineText()) |
+ m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(toRenderSVGInlineText(descendant)); |
+ } |
+} |
+ |
+void RenderSVGText::subtreeTextDidChange(RenderSVGInlineText* text) |
+{ |
+ ASSERT(text); |
+ ASSERT(!beingDestroyed()); |
+ if (!everHadLayout()) { |
+ ASSERT(m_layoutAttributes.isEmpty()); |
+ ASSERT(!m_layoutAttributesBuilder.numberOfTextPositioningElements()); |
+ return; |
+ } |
+ |
+ // The positioning elements cache depends on the size of each text renderer in the |
+ // subtree. If this changes, clear the cache. It's going to be rebuilt below. |
m_layoutAttributesBuilder.clearTextPositioningElements(); |
+ |
+ checkLayoutAttributesConsistency(this, m_layoutAttributes); |
+ for (RenderObject* descendant = text; descendant; descendant = descendant->nextInPreOrder(text)) { |
+ if (descendant->isSVGInlineText()) |
+ m_layoutAttributesBuilder.buildLayoutAttributesForTextRenderer(toRenderSVGInlineText(descendant)); |
+ } |
} |
-static inline void recursiveUpdateScaledFont(RenderObject* start) |
+static inline void updateFontInAllDescendants(RenderObject* start, SVGTextLayoutAttributesBuilder* builder = 0) |
{ |
- for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { |
- if (child->isSVGInlineText()) { |
- toRenderSVGInlineText(child)->updateScaledFont(); |
+ for (RenderObject* descendant = start; descendant; descendant = descendant->nextInPreOrder(start)) { |
+ if (!descendant->isSVGInlineText()) |
continue; |
- } |
- |
- recursiveUpdateScaledFont(child); |
+ RenderSVGInlineText* text = toRenderSVGInlineText(descendant); |
+ text->updateScaledFont(); |
+ if (builder) |
+ builder->rebuildMetricsForTextRenderer(text); |
} |
} |
@@ -212,23 +342,43 @@ |
updateCachedBoundariesInParents = true; |
} |
- // If the root layout size changed (eg. window size changes) or the positioning values change |
- // or the transform to the root context has changed then recompute the on-screen font size. |
- if (m_needsTextMetricsUpdate || SVGRenderSupport::findTreeRootObject(this)->isLayoutSizeChanged()) { |
- recursiveUpdateScaledFont(this); |
- rebuildLayoutAttributes(true); |
+ if (!everHadLayout()) { |
+ // When laying out initially, collect all layout attributes, build the character data map, |
+ // and propogate resulting SVGLayoutAttributes to all RenderSVGInlineText children in the subtree. |
+ ASSERT(m_layoutAttributes.isEmpty()); |
+ collectLayoutAttributes(this, m_layoutAttributes); |
+ updateFontInAllDescendants(this); |
+ m_layoutAttributesBuilder.buildLayoutAttributesForForSubtree(this); |
+ |
+ m_needsReordering = true; |
+ m_needsTextMetricsUpdate = false; |
+ m_needsPositioningValuesUpdate = false; |
updateCachedBoundariesInParents = true; |
- m_needsTextMetricsUpdate = false; |
- } |
+ } else if (m_needsPositioningValuesUpdate) { |
+ // When the x/y/dx/dy/rotate lists change, recompute the layout attributes, and eventually |
+ // update the on-screen font objects as well in all descendants. |
+ if (m_needsTextMetricsUpdate) { |
+ updateFontInAllDescendants(this); |
+ m_needsTextMetricsUpdate = false; |
+ } |
- if (m_needsPositioningValuesUpdate) { |
- // Perform SVG text layout phase one (see SVGTextLayoutAttributesBuilder for details). |
- m_layoutAttributesBuilder.buildLayoutAttributesForWholeTree(this); |
+ m_layoutAttributesBuilder.buildLayoutAttributesForForSubtree(this); |
m_needsReordering = true; |
m_needsPositioningValuesUpdate = false; |
updateCachedBoundariesInParents = true; |
+ } else if (m_needsTextMetricsUpdate || SVGRenderSupport::findTreeRootObject(this)->isLayoutSizeChanged()) { |
+ // If the root layout size changed (eg. window size changes) or the transform to the root |
+ // context has changed then recompute the on-screen font size. |
+ updateFontInAllDescendants(this, &m_layoutAttributesBuilder); |
+ |
+ ASSERT(!m_needsReordering); |
+ ASSERT(!m_needsPositioningValuesUpdate); |
+ m_needsTextMetricsUpdate = false; |
+ updateCachedBoundariesInParents = true; |
} |
+ checkLayoutAttributesConsistency(this, m_layoutAttributes); |
+ |
// Reduced version of RenderBlock::layoutBlock(), which only takes care of SVG text. |
// All if branches that could cause early exit in RenderBlocks layoutBlock() method are turned into assertions. |
ASSERT(!isInline()); |
@@ -364,9 +514,23 @@ |
void RenderSVGText::addChild(RenderObject* child, RenderObject* beforeChild) |
{ |
RenderSVGBlock::addChild(child, beforeChild); |
- layoutAttributesChanged(child); |
+ subtreeChildWasAdded(child); |
} |
+void RenderSVGText::removeChild(RenderObject* child) |
+{ |
+ if (!child->isSVGInlineText()) { |
+ RenderSVGBlock::removeChild(child); |
+ return; |
+ } |
+ |
+ RenderSVGInlineText* text = toRenderSVGInlineText(child); |
+ Vector<SVGTextLayoutAttributes*, 2> affectedAttributes; |
+ subtreeChildWillBeRemoved(text, affectedAttributes); |
+ RenderSVGBlock::removeChild(child); |
+ subtreeChildWasRemoved(affectedAttributes); |
+} |
+ |
// Fix for <rdar://problem/8048875>. We should not render :first-line CSS Style |
// in a SVG text element context. |
RenderBlock* RenderSVGText::firstLineBlock() const |
@@ -380,61 +544,6 @@ |
{ |
} |
-static inline void recursiveCollectLayoutAttributes(RenderObject* start, Vector<SVGTextLayoutAttributes*>& attributes) |
-{ |
- for (RenderObject* child = start->firstChild(); child; child = child->nextSibling()) { |
- if (child->isSVGInlineText()) { |
- attributes.append(toRenderSVGInlineText(child)->layoutAttributes()); |
- continue; |
- } |
- |
- recursiveCollectLayoutAttributes(child, attributes); |
- } |
} |
-void RenderSVGText::rebuildLayoutAttributes(bool performFullRebuild) |
-{ |
- if (performFullRebuild) |
- m_layoutAttributes.clear(); |
- |
- if (m_layoutAttributes.isEmpty()) { |
- recursiveCollectLayoutAttributes(this, m_layoutAttributes); |
- if (m_layoutAttributes.isEmpty() || !performFullRebuild) |
- return; |
- |
- m_layoutAttributesBuilder.rebuildMetricsForWholeTree(this); |
- return; |
- } |
- |
- Vector<SVGTextLayoutAttributes*> affectedAttributes; |
- rebuildLayoutAttributes(affectedAttributes); |
-} |
- |
-void RenderSVGText::rebuildLayoutAttributes(Vector<SVGTextLayoutAttributes*>& affectedAttributes) |
-{ |
- // Detect changes in layout attributes and only measure those text parts that have changed! |
- Vector<SVGTextLayoutAttributes*> newLayoutAttributes; |
- recursiveCollectLayoutAttributes(this, newLayoutAttributes); |
- if (newLayoutAttributes.isEmpty()) { |
- m_layoutAttributes.clear(); |
- return; |
- } |
- |
- // Compare m_layoutAttributes with newLayoutAttributes to figure out which attributes got added/removed. |
- size_t size = newLayoutAttributes.size(); |
- for (size_t i = 0; i < size; ++i) { |
- SVGTextLayoutAttributes* attributes = newLayoutAttributes[i]; |
- if (m_layoutAttributes.find(attributes) == notFound) |
- m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(attributes->context()); |
- } |
- |
- size = affectedAttributes.size(); |
- for (size_t i = 0; i < size; ++i) |
- m_layoutAttributesBuilder.rebuildMetricsForTextRenderer(affectedAttributes[i]->context()); |
- |
- m_layoutAttributes = newLayoutAttributes; |
-} |
- |
-} |
- |
#endif // ENABLE(SVG) |