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#include "RenderSVGResourceContainer.h"
22
23#include "RenderLayer.h"
24#include "RenderSVGRoot.h"
25#include "RenderView.h"
26#include "SVGRenderingContext.h"
27#include "SVGResourcesCache.h"
28#include <wtf/StackStats.h>
29
30namespace WebCore {
31
32static inline SVGDocumentExtensions& svgExtensionsFromElement(SVGElement& element)
33{
34    // FIXME: accessSVGExtensions() should return a reference.
35    return *element.document().accessSVGExtensions();
36}
37
38RenderSVGResourceContainer::RenderSVGResourceContainer(SVGElement& element, PassRef<RenderStyle> style)
39    : RenderSVGHiddenContainer(element, WTF::move(style))
40    , m_id(element.getIdAttribute())
41    , m_registered(false)
42    , m_isInvalidating(false)
43{
44}
45
46RenderSVGResourceContainer::~RenderSVGResourceContainer()
47{
48    if (m_registered)
49        svgExtensionsFromElement(element()).removeResource(m_id);
50}
51
52void RenderSVGResourceContainer::layout()
53{
54    StackStats::LayoutCheckPoint layoutCheckPoint;
55    // Invalidate all resources if our layout changed.
56    if (everHadLayout() && selfNeedsLayout())
57        RenderSVGRoot::addResourceForClientInvalidation(this);
58
59    RenderSVGHiddenContainer::layout();
60}
61
62void RenderSVGResourceContainer::willBeDestroyed()
63{
64    SVGResourcesCache::resourceDestroyed(*this);
65    RenderSVGHiddenContainer::willBeDestroyed();
66}
67
68void RenderSVGResourceContainer::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
69{
70    RenderSVGHiddenContainer::styleDidChange(diff, oldStyle);
71
72    if (!m_registered) {
73        m_registered = true;
74        registerResource();
75    }
76}
77
78void RenderSVGResourceContainer::idChanged()
79{
80    // Invalidate all our current clients.
81    removeAllClientsFromCache();
82
83    // Remove old id, that is guaranteed to be present in cache.
84    svgExtensionsFromElement(element()).removeResource(m_id);
85    m_id = element().getIdAttribute();
86
87    registerResource();
88}
89
90void RenderSVGResourceContainer::markAllClientsForInvalidation(InvalidationMode mode)
91{
92    if ((m_clients.isEmpty() && m_clientLayers.isEmpty()) || m_isInvalidating)
93        return;
94
95    m_isInvalidating = true;
96    bool needsLayout = mode == LayoutAndBoundariesInvalidation;
97    bool markForInvalidation = mode != ParentOnlyInvalidation;
98
99    for (auto* client : m_clients) {
100        if (client->isSVGResourceContainer()) {
101            toRenderSVGResourceContainer(*client).removeAllClientsFromCache(markForInvalidation);
102            continue;
103        }
104
105        if (markForInvalidation)
106            markClientForInvalidation(*client, mode);
107
108        RenderSVGResource::markForLayoutAndParentResourceInvalidation(*client, needsLayout);
109    }
110
111    markAllClientLayersForInvalidation();
112
113    m_isInvalidating = false;
114}
115
116void RenderSVGResourceContainer::markAllClientLayersForInvalidation()
117{
118#if ENABLE(CSS_FILTERS)
119    for (auto* clientLayer : m_clientLayers)
120        clientLayer->filterNeedsRepaint();
121#endif
122}
123
124void RenderSVGResourceContainer::markClientForInvalidation(RenderObject& client, InvalidationMode mode)
125{
126    ASSERT(!m_clients.isEmpty());
127
128    switch (mode) {
129    case LayoutAndBoundariesInvalidation:
130    case BoundariesInvalidation:
131        client.setNeedsBoundariesUpdate();
132        break;
133    case RepaintInvalidation:
134        if (!client.documentBeingDestroyed())
135            client.repaint();
136        break;
137    case ParentOnlyInvalidation:
138        break;
139    }
140}
141
142void RenderSVGResourceContainer::addClient(RenderElement& client)
143{
144    m_clients.add(&client);
145}
146
147void RenderSVGResourceContainer::removeClient(RenderElement& client)
148{
149    removeClientFromCache(client, false);
150    m_clients.remove(&client);
151}
152
153void RenderSVGResourceContainer::addClientRenderLayer(RenderLayer* client)
154{
155    ASSERT(client);
156    m_clientLayers.add(client);
157}
158
159void RenderSVGResourceContainer::removeClientRenderLayer(RenderLayer* client)
160{
161    ASSERT(client);
162    m_clientLayers.remove(client);
163}
164
165void RenderSVGResourceContainer::registerResource()
166{
167    SVGDocumentExtensions& extensions = svgExtensionsFromElement(element());
168    if (!extensions.isIdOfPendingResource(m_id)) {
169        extensions.addResource(m_id, this);
170        return;
171    }
172
173    std::unique_ptr<SVGDocumentExtensions::PendingElements> clients = extensions.removePendingResource(m_id);
174
175    // Cache us with the new id.
176    extensions.addResource(m_id, this);
177
178    // Update cached resources of pending clients.
179    for (auto* client : *clients) {
180        ASSERT(client->hasPendingResources());
181        extensions.clearHasPendingResourcesIfPossible(client);
182        auto* renderer = client->renderer();
183        if (!renderer)
184            continue;
185        SVGResourcesCache::clientStyleChanged(*renderer, StyleDifferenceLayout, renderer->style());
186        renderer->setNeedsLayout();
187    }
188}
189
190bool RenderSVGResourceContainer::shouldTransformOnTextPainting(const RenderElement& renderer, AffineTransform& resourceTransform)
191{
192#if USE(CG)
193    UNUSED_PARAM(renderer);
194    UNUSED_PARAM(resourceTransform);
195    return false;
196#else
197    // This method should only be called for RenderObjects that deal with text rendering. Cmp. RenderObject.h's is*() methods.
198    ASSERT(renderer.isSVGText() || renderer.isSVGTextPath() || renderer.isSVGInline());
199
200    // In text drawing, the scaling part of the graphics context CTM is removed, compare SVGInlineTextBox::paintTextWithShadows.
201    // So, we use that scaling factor here, too, and then push it down to pattern or gradient space
202    // in order to keep the pattern or gradient correctly scaled.
203    float scalingFactor = SVGRenderingContext::calculateScreenFontSizeScalingFactor(renderer);
204    if (scalingFactor == 1)
205        return false;
206    resourceTransform.scale(scalingFactor);
207    return true;
208#endif
209}
210
211// FIXME: This does not belong here.
212AffineTransform RenderSVGResourceContainer::transformOnNonScalingStroke(RenderObject* object, const AffineTransform& resourceTransform)
213{
214    if (!object->isSVGShape())
215        return resourceTransform;
216
217    SVGGraphicsElement* element = toSVGGraphicsElement(object->node());
218    AffineTransform transform = element->getScreenCTM(SVGLocatable::DisallowStyleUpdate);
219    transform *= resourceTransform;
220    return transform;
221}
222
223}
224