1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Authors:
4  *   Ted Gould <ted@gould.cx>
5  *
6  * Copyright (C) 2008 Authors
7  *
8  * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9  */
10 
11 #include "desktop.h"
12 #include "selection.h"
13 #include "extension/extension.h"
14 #include "extension/effect.h"
15 #include "extension/system.h"
16 #include "xml/repr.h"
17 #include "xml/simple-node.h"
18 #include "xml/attribute-record.h"
19 #include "object/sp-defs.h"
20 
21 #include "filter.h"
22 
23 namespace Inkscape {
24 namespace Extension {
25 namespace Internal {
26 namespace Filter {
27 
Filter()28 Filter::Filter() :
29     Inkscape::Extension::Implementation::Implementation(),
30     _filter(nullptr) {
31     return;
32 }
33 
Filter(gchar const * filter)34 Filter::Filter(gchar const * filter) :
35     Inkscape::Extension::Implementation::Implementation(),
36     _filter(filter) {
37     return;
38 }
39 
~Filter()40 Filter::~Filter () {
41     if (_filter != nullptr) {
42         _filter = nullptr;
43     }
44 
45     return;
46 }
47 
load(Inkscape::Extension::Extension *)48 bool Filter::load(Inkscape::Extension::Extension * /*module*/)
49 {
50     return true;
51 }
52 
newDocCache(Inkscape::Extension::Extension *,Inkscape::UI::View::View *)53 Inkscape::Extension::Implementation::ImplementationDocumentCache *Filter::newDocCache(Inkscape::Extension::Extension * /*ext*/,
54 										      Inkscape::UI::View::View * /*doc*/)
55 {
56     return nullptr;
57 }
58 
get_filter_text(Inkscape::Extension::Extension *)59 gchar const *Filter::get_filter_text(Inkscape::Extension::Extension * /*ext*/)
60 {
61     return _filter;
62 }
63 
64 Inkscape::XML::Document *
get_filter(Inkscape::Extension::Extension * ext)65 Filter::get_filter (Inkscape::Extension::Extension * ext) {
66     gchar const * filter = get_filter_text(ext);
67     return sp_repr_read_mem(filter, strlen(filter), nullptr);
68 }
69 
70 void
merge_filters(Inkscape::XML::Node * to,Inkscape::XML::Node * from,Inkscape::XML::Document * doc,gchar const * srcGraphic,gchar const * srcGraphicAlpha)71 Filter::merge_filters( Inkscape::XML::Node * to, Inkscape::XML::Node * from,
72 		       Inkscape::XML::Document * doc,
73 		       gchar const * srcGraphic, gchar const * srcGraphicAlpha)
74 {
75     if (from == nullptr) return;
76 
77     // copy attributes
78     for ( const auto & iter : from->attributeList()) {
79         gchar const * attr = g_quark_to_string(iter.key);
80         //printf("Attribute List: %s\n", attr);
81         if (!strcmp(attr, "id")) continue; // nope, don't copy that one!
82         to->setAttribute(attr, from->attribute(attr));
83 
84         if (!strcmp(attr, "in") || !strcmp(attr, "in2") || !strcmp(attr, "in3")) {
85             if (srcGraphic != nullptr && !strcmp(from->attribute(attr), "SourceGraphic")) {
86                 to->setAttribute(attr, srcGraphic);
87             }
88 
89             if (srcGraphicAlpha != nullptr && !strcmp(from->attribute(attr), "SourceAlpha")) {
90                 to->setAttribute(attr, srcGraphicAlpha);
91             }
92         }
93     }
94 
95     // for each child call recursively
96     for (Inkscape::XML::Node * from_child = from->firstChild();
97          from_child != nullptr ; from_child = from_child->next()) {
98         Glib::ustring name = "svg:";
99         name += from_child->name();
100 
101         Inkscape::XML::Node * to_child = doc->createElement(name.c_str());
102         to->appendChild(to_child);
103         merge_filters(to_child, from_child, doc, srcGraphic, srcGraphicAlpha);
104 
105         if (from_child == from->firstChild() && !strcmp("filter", from->name()) && srcGraphic != nullptr && to_child->attribute("in") == nullptr) {
106             to_child->setAttribute("in", srcGraphic);
107         }
108         Inkscape::GC::release(to_child);
109     }
110 }
111 
112 #define FILTER_SRC_GRAPHIC       "fbSourceGraphic"
113 #define FILTER_SRC_GRAPHIC_ALPHA "fbSourceGraphicAlpha"
114 
effect(Inkscape::Extension::Effect * module,Inkscape::UI::View::View * document,Inkscape::Extension::Implementation::ImplementationDocumentCache *)115 void Filter::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document,
116                     Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
117 {
118     Inkscape::XML::Document *filterdoc = get_filter(module);
119     if (filterdoc == nullptr) {
120         return; // could not parse the XML source of the filter; typically parser will stderr a warning
121     }
122 
123     //printf("Calling filter effect\n");
124     Inkscape::Selection * selection = ((SPDesktop *)document)->selection;
125 
126     // TODO need to properly refcount the items, at least
127     std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
128 
129     Inkscape::XML::Document * xmldoc = document->doc()->getReprDoc();
130     Inkscape::XML::Node * defsrepr = document->doc()->getDefs()->getRepr();
131 
132     for(auto spitem : items) {
133         Inkscape::XML::Node *node = spitem->getRepr();
134 
135         SPCSSAttr * css = sp_repr_css_attr(node, "style");
136         gchar const * filter = sp_repr_css_property(css, "filter", nullptr);
137 
138         if (filter == nullptr) {
139 
140             Inkscape::XML::Node * newfilterroot = xmldoc->createElement("svg:filter");
141             merge_filters(newfilterroot, filterdoc->root(), xmldoc);
142             defsrepr->appendChild(newfilterroot);
143             document->doc()->resources_changed_signals[g_quark_from_string("filter")].emit();
144 
145             Glib::ustring url = "url(#"; url += newfilterroot->attribute("id"); url += ")";
146 
147 
148             Inkscape::GC::release(newfilterroot);
149 
150             sp_repr_css_set_property(css, "filter", url.c_str());
151             sp_repr_css_set(node, css, "style");
152         } else {
153             if (strncmp(filter, "url(#", strlen("url(#")) || filter[strlen(filter) - 1] != ')') {
154                 // This is not url(#id) -- we can't handle it
155                 continue;
156             }
157 
158             gchar * lfilter = g_strndup(filter + 5, strlen(filter) - 6);
159             Inkscape::XML::Node * filternode = nullptr;
160             for (Inkscape::XML::Node * child = defsrepr->firstChild(); child != nullptr; child = child->next()) {
161                 if (!strcmp(lfilter, child->attribute("id"))) {
162                     filternode = child;
163                     break;
164                 }
165             }
166             g_free(lfilter);
167 
168             // no filter
169             if (filternode == nullptr) {
170                 g_warning("no assigned filter found!");
171                 continue;
172             }
173 
174             if (filternode->lastChild() == nullptr) {
175                 // empty filter, we insert
176                 merge_filters(filternode, filterdoc->root(), xmldoc);
177             } else {
178                 // existing filter, we merge
179                 filternode->lastChild()->setAttribute("result", FILTER_SRC_GRAPHIC);
180                 Inkscape::XML::Node * alpha = xmldoc->createElement("svg:feColorMatrix");
181                 alpha->setAttribute("result", FILTER_SRC_GRAPHIC_ALPHA);
182                 alpha->setAttribute("in", FILTER_SRC_GRAPHIC); // not required, but we're being explicit
183                 alpha->setAttribute("values", "0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0");
184 
185                 filternode->appendChild(alpha);
186 
187                 merge_filters(filternode, filterdoc->root(), xmldoc, FILTER_SRC_GRAPHIC, FILTER_SRC_GRAPHIC_ALPHA);
188 
189                 Inkscape::GC::release(alpha);
190             }
191         }
192     }
193 
194     return;
195 }
196 
197 #include "extension/internal/clear-n_.h"
198 
199 void
filter_init(gchar const * id,gchar const * name,gchar const * submenu,gchar const * tip,gchar const * filter)200 Filter::filter_init (gchar const * id, gchar const * name, gchar const * submenu, gchar const * tip, gchar const * filter)
201 {
202     // clang-format off
203     gchar * xml_str = g_strdup_printf(
204         "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
205         "<name>%s</name>\n"
206         "<id>org.inkscape.effect.filter.%s</id>\n"
207         "<effect>\n"
208         "<object-type>all</object-type>\n"
209         "<effects-menu>\n"
210         "<submenu name=\"" N_("Filters") "\" />\n"
211         "<submenu name=\"%s\"/>\n"
212         "</effects-menu>\n"
213         "<menu-tip>%s</menu-tip>\n"
214         "</effect>\n"
215         "</inkscape-extension>\n", name, id, submenu, tip);
216     // clang-format on
217     Inkscape::Extension::build_from_mem(xml_str, new Filter(filter));
218     g_free(xml_str);
219     return;
220 }
221 
222 }; /* namespace Filter */
223 }; /* namespace Internal */
224 }; /* namespace Extension */
225 }; /* namespace Inkscape */
226 
227 /*
228   Local Variables:
229   mode:c++
230   c-file-style:"stroustrup"
231   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
232   indent-tabs-mode:nil
233   fill-column:99
234   End:
235 */
236 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
237