1/*
2 * Copyright (C) Research In Motion Limited 2010. All rights reserved.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12 * Library General Public License for more details.
13 *
14 * You should have received a copy of the GNU Library General Public License
15 * along with this library; see the file COPYING.LIB.  If not, write to
16 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20#include "config.h"
21
22#include "core/rendering/svg/RenderSVGResourceContainer.h"
23
24#include "core/rendering/RenderLayer.h"
25#include "core/rendering/RenderView.h"
26#include "core/rendering/svg/SVGRenderingContext.h"
27#include "core/rendering/svg/SVGResourcesCache.h"
28#include "core/svg/SVGGraphicsElement.h"
29
30#include "wtf/TemporaryChange.h"
31
32namespace blink {
33
34static inline SVGDocumentExtensions& svgExtensionsFromElement(SVGElement* element)
35{
36    ASSERT(element);
37    return element->document().accessSVGExtensions();
38}
39
40RenderSVGResourceContainer::RenderSVGResourceContainer(SVGElement* node)
41    : RenderSVGHiddenContainer(node)
42    , m_isInLayout(false)
43    , m_id(node->getIdAttribute())
44    , m_invalidationMask(0)
45    , m_registered(false)
46    , m_isInvalidating(false)
47{
48}
49
50RenderSVGResourceContainer::~RenderSVGResourceContainer()
51{
52}
53
54void RenderSVGResourceContainer::layout()
55{
56    // FIXME: Investigate a way to detect and break resource layout dependency cycles early.
57    // Then we can remove this method altogether, and fall back onto RenderSVGHiddenContainer::layout().
58    ASSERT(needsLayout());
59    if (m_isInLayout)
60        return;
61
62    TemporaryChange<bool> inLayoutChange(m_isInLayout, true);
63
64    RenderSVGHiddenContainer::layout();
65
66    clearInvalidationMask();
67}
68
69void RenderSVGResourceContainer::willBeDestroyed()
70{
71    SVGResourcesCache::resourceDestroyed(this);
72    RenderSVGHiddenContainer::willBeDestroyed();
73    if (m_registered)
74        svgExtensionsFromElement(element()).removeResource(m_id);
75}
76
77void RenderSVGResourceContainer::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
78{
79    RenderSVGHiddenContainer::styleDidChange(diff, oldStyle);
80
81    if (!m_registered) {
82        m_registered = true;
83        registerResource();
84    }
85}
86
87void RenderSVGResourceContainer::idChanged()
88{
89    // Invalidate all our current clients.
90    removeAllClientsFromCache();
91
92    // Remove old id, that is guaranteed to be present in cache.
93    SVGDocumentExtensions& extensions = svgExtensionsFromElement(element());
94    extensions.removeResource(m_id);
95    m_id = element()->getIdAttribute();
96
97    registerResource();
98}
99
100void RenderSVGResourceContainer::markAllClientsForInvalidation(InvalidationMode mode)
101{
102    if ((m_clients.isEmpty() && m_clientLayers.isEmpty()) || m_isInvalidating)
103        return;
104
105    if (m_invalidationMask & mode)
106        return;
107
108    m_invalidationMask |= mode;
109    m_isInvalidating = true;
110    bool needsLayout = mode == LayoutAndBoundariesInvalidation;
111    bool markForInvalidation = mode != ParentOnlyInvalidation;
112
113    HashSet<RenderObject*>::iterator end = m_clients.end();
114    for (HashSet<RenderObject*>::iterator it = m_clients.begin(); it != end; ++it) {
115        RenderObject* client = *it;
116        if (client->isSVGResourceContainer()) {
117            toRenderSVGResourceContainer(client)->removeAllClientsFromCache(markForInvalidation);
118            continue;
119        }
120
121        if (markForInvalidation)
122            markClientForInvalidation(client, mode);
123
124        RenderSVGResource::markForLayoutAndParentResourceInvalidation(client, needsLayout);
125    }
126
127    markAllClientLayersForInvalidation();
128
129    m_isInvalidating = false;
130}
131
132void RenderSVGResourceContainer::markAllClientLayersForInvalidation()
133{
134    HashSet<RenderLayer*>::iterator layerEnd = m_clientLayers.end();
135    for (HashSet<RenderLayer*>::iterator it = m_clientLayers.begin(); it != layerEnd; ++it)
136        (*it)->filterNeedsPaintInvalidation();
137}
138
139void RenderSVGResourceContainer::markClientForInvalidation(RenderObject* client, InvalidationMode mode)
140{
141    ASSERT(client);
142    ASSERT(!m_clients.isEmpty());
143
144    switch (mode) {
145    case LayoutAndBoundariesInvalidation:
146    case BoundariesInvalidation:
147        client->setNeedsBoundariesUpdate();
148        break;
149    case PaintInvalidation:
150        client->setShouldDoFullPaintInvalidation(true);
151        break;
152    case ParentOnlyInvalidation:
153        break;
154    }
155}
156
157void RenderSVGResourceContainer::addClient(RenderObject* client)
158{
159    ASSERT(client);
160    m_clients.add(client);
161    clearInvalidationMask();
162}
163
164void RenderSVGResourceContainer::removeClient(RenderObject* client)
165{
166    ASSERT(client);
167    removeClientFromCache(client, false);
168    m_clients.remove(client);
169}
170
171void RenderSVGResourceContainer::addClientRenderLayer(Node* node)
172{
173    ASSERT(node);
174    if (!node->renderer() || !node->renderer()->hasLayer())
175        return;
176    m_clientLayers.add(toRenderLayerModelObject(node->renderer())->layer());
177    clearInvalidationMask();
178}
179
180void RenderSVGResourceContainer::addClientRenderLayer(RenderLayer* client)
181{
182    ASSERT(client);
183    m_clientLayers.add(client);
184    clearInvalidationMask();
185}
186
187void RenderSVGResourceContainer::removeClientRenderLayer(RenderLayer* client)
188{
189    ASSERT(client);
190    m_clientLayers.remove(client);
191}
192
193void RenderSVGResourceContainer::invalidateCacheAndMarkForLayout(SubtreeLayoutScope* layoutScope)
194{
195    if (selfNeedsLayout())
196        return;
197
198    setNeedsLayoutAndFullPaintInvalidation(MarkContainingBlockChain, layoutScope);
199
200    if (everHadLayout())
201        removeAllClientsFromCache();
202}
203
204void RenderSVGResourceContainer::registerResource()
205{
206    SVGDocumentExtensions& extensions = svgExtensionsFromElement(element());
207    if (!extensions.hasPendingResource(m_id)) {
208        extensions.addResource(m_id, this);
209        return;
210    }
211
212    OwnPtrWillBeRawPtr<SVGDocumentExtensions::SVGPendingElements> clients(extensions.removePendingResource(m_id));
213
214    // Cache us with the new id.
215    extensions.addResource(m_id, this);
216
217    // Update cached resources of pending clients.
218    const SVGDocumentExtensions::SVGPendingElements::const_iterator end = clients->end();
219    for (SVGDocumentExtensions::SVGPendingElements::const_iterator it = clients->begin(); it != end; ++it) {
220        ASSERT((*it)->hasPendingResources());
221        extensions.clearHasPendingResourcesIfPossible(*it);
222        RenderObject* renderer = (*it)->renderer();
223        if (!renderer)
224            continue;
225
226        StyleDifference diff;
227        diff.setNeedsFullLayout();
228        SVGResourcesCache::clientStyleChanged(renderer, diff, renderer->style());
229        renderer->setNeedsLayoutAndFullPaintInvalidation();
230    }
231}
232
233static bool shouldTransformOnTextPainting(RenderObject* object, AffineTransform& resourceTransform)
234{
235    ASSERT(object);
236
237    // This method should only be called for RenderObjects that deal with text rendering. Cmp. RenderObject.h's is*() methods.
238    ASSERT(object->isSVGText() || object->isSVGTextPath() || object->isSVGInline());
239
240    // In text drawing, the scaling part of the graphics context CTM is removed, compare SVGInlineTextBox::paintTextWithShadows.
241    // So, we use that scaling factor here, too, and then push it down to pattern or gradient space
242    // in order to keep the pattern or gradient correctly scaled.
243    float scalingFactor = SVGRenderingContext::calculateScreenFontSizeScalingFactor(object);
244    if (scalingFactor == 1)
245        return false;
246    resourceTransform.scale(scalingFactor);
247    return true;
248}
249
250AffineTransform RenderSVGResourceContainer::computeResourceSpaceTransform(RenderObject* object, const AffineTransform& baseTransform, const SVGRenderStyle& svgStyle, unsigned short resourceMode)
251{
252    AffineTransform computedSpaceTransform = baseTransform;
253    if (resourceMode & ApplyToTextMode) {
254        // Depending on the font scaling factor, we may need to apply an
255        // additional transform (scale-factor) the paintserver, since text
256        // painting removes the scale factor from the context. (See
257        // SVGInlineTextBox::paintTextWithShadows.)
258        AffineTransform additionalTextTransformation;
259        if (shouldTransformOnTextPainting(object, additionalTextTransformation))
260            computedSpaceTransform = additionalTextTransformation * computedSpaceTransform;
261    }
262    if (resourceMode & ApplyToStrokeMode) {
263        // Non-scaling stroke needs to reset the transform back to the host transform.
264        if (svgStyle.vectorEffect() == VE_NON_SCALING_STROKE)
265            computedSpaceTransform = transformOnNonScalingStroke(object, computedSpaceTransform);
266    }
267    return computedSpaceTransform;
268}
269
270// FIXME: This does not belong here.
271AffineTransform RenderSVGResourceContainer::transformOnNonScalingStroke(RenderObject* object, const AffineTransform& resourceTransform)
272{
273    if (!object->isSVGShape())
274        return resourceTransform;
275
276    SVGGraphicsElement* element = toSVGGraphicsElement(object->node());
277    AffineTransform transform = element->getScreenCTM(SVGGraphicsElement::DisallowStyleUpdate);
278    transform *= resourceTransform;
279    return transform;
280}
281
282}
283