summaryrefslogtreecommitdiffstats
path: root/src/pattern-manager.cpp
blob: 4ba018e0929e4686ff3e47ddbeeac5849ab39f91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// SPDX-License-Identifier: GPL-2.0-or-later

#include <gtkmm/liststore.h>
#include <glibmm/i18n.h>

#include "pattern-manager.h"
#include "pattern-manipulation.h"
#include "document.h"
#include "manipulation/copy-resource.h"
#include "style.h"
#include "object/sp-pattern.h"
#include "object/sp-defs.h"
#include "object/sp-root.h"
#include "util/statics.h"
#include "util/units.h"
#include "ui/svg-renderer.h"

using Inkscape::UI::Widget::PatternItem;

namespace Inkscape {

// pattern preview for UI list, with light gray background and border
std::shared_ptr<SPDocument> get_preview_document() {
char const* buffer = R"A(
<svg width="40" height="40" viewBox="0 0 40 40"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns="http://www.w3.org/2000/svg">
  <defs id="defs">
  </defs>
  <g id="layer1">
    <rect
       style="fill:#f0f0f0;fill-opacity:1;stroke:none"
       id="rect2620"
       width="100%" height="100%" x="0" y="0" />
    <rect
       style="fill:url(#sample);fill-opacity:1;stroke:black;stroke-opacity:0.3;stroke-width:1px"
       id="rect236"
       width="100%" height="100%" x="0" y="0" />
  </g>
</svg>
)A";
    return std::shared_ptr<SPDocument>(SPDocument::createNewDocFromMem(buffer, strlen(buffer), false));
}

// pattern preview document without background
std::shared_ptr<SPDocument> get_big_preview_document() {
char const* buffer = R"A(
<svg width="100" height="100"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns="http://www.w3.org/2000/svg">
  <defs id="defs">
  </defs>
  <g id="layer1">
    <rect
       style="fill:url(#sample);fill-opacity:1;stroke:none"
       width="100%" height="100%" x="0" y="0" />
  </g>
</svg>
)A";
    return std::shared_ptr<SPDocument>(SPDocument::createNewDocFromMem(buffer, strlen(buffer), false));
}

PatternManager& PatternManager::get() {
    struct ConstructiblePatternManager : PatternManager {};
    static auto factory = Inkscape::Util::Static<ConstructiblePatternManager>();
    return factory.get();
}

PatternManager::PatternManager() {
    _preview_doc = get_preview_document();
    if (!_preview_doc.get() || !_preview_doc->getReprDoc()) {
        throw std::runtime_error("Pattern embedded preview document cannot be loaded");
    }

    _big_preview_doc = get_big_preview_document();
    if (!_big_preview_doc.get() || !_big_preview_doc->getReprDoc()) {
        throw std::runtime_error("Pattern embedded big preview document cannot be loaded");
    }

    auto model = Gtk::ListStore::create(columns);

    _documents = sp_get_stock_patterns();
    std::vector<SPPattern*> all;

    for (auto& doc : _documents) {
        auto patterns = sp_get_pattern_list(doc.get());
        all.insert(all.end(), patterns.begin(), patterns.end());

        Glib::ustring name = doc->getDocumentName();
        name = name.substr(0, name.rfind(".svg"));
        auto category = Glib::RefPtr<Category>(new Category(name, std::move(patterns), false));
        _categories.push_back(category);
    }

    for (auto pat : all) {
        // create empty items for stock patterns
        _cache[pat] = Glib::RefPtr<Inkscape::UI::Widget::PatternItem>();
    }

    // special "all patterns" category
    auto all_patterns = Glib::RefPtr<Category>(new Category(_("All patterns"), std::move(all), true));
    _categories.push_back(all_patterns);

    // sort by name, keep "all patterns" category first
    std::sort(_categories.begin(), _categories.end(), [](const Glib::RefPtr<Category>& a, const Glib::RefPtr<Category>& b){
        if (a->all != b->all) {
            return a->all;
        }
        return a->name < b->name;
    });

    for (auto& category : _categories) {
        auto row = *model->append();
        row[columns.name] = category->name;
        row[columns.category] = category;
        row[columns.all_patterns] = category->all;
    }

    _model = model;
}

Cairo::RefPtr<Cairo::Surface> create_pattern_image(std::shared_ptr<SPDocument>& sandbox,
    char const* name, SPDocument* source, double scale,
    std::optional<unsigned int> checkerboard = std::optional<unsigned int>()) {

    // Retrieve the pattern named 'name' from the source SVG document
    SPObject const* pattern = source->getObjectById(name);
    if (pattern == nullptr) {
        g_warning("bad name: %s", name);
        return Cairo::RefPtr<Cairo::Surface>();
    }

    auto list = sandbox->getDefs()->childList(true);
    for (auto obj : list) {
        obj->deleteObject();
        sp_object_unref(obj);
    }

    SPDocument::install_reference_document scoped(sandbox.get(), source);

    // Create a copy of the pattern, name it "sample"
    auto copy = sp_copy_resource(pattern, sandbox.get());
    copy->getRepr()->setAttribute("id", "sample");

    sandbox->getRoot()->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
    sandbox->ensureUpToDate();

    svg_renderer renderer(sandbox);
    if (checkerboard.has_value()) {
        renderer.set_checkerboard_color(*checkerboard);
    }
    auto surface = renderer.render_surface(scale);
    if (surface) {
        cairo_surface_set_device_scale(surface->cobj(), scale, scale);
    }

    // delete sample to relese href to the original pattern, if any has been referenced by 'copy'
    SPObject* oldpattern = sandbox->getObjectById("sample");
    if (oldpattern) {
        oldpattern->deleteObject(false);
    }
    return surface;
}

// given a pattern, create a PatternItem instance that describes it;
// input pattern can be a link or a root pattern
Glib::RefPtr<PatternItem> create_pattern_item(std::shared_ptr<SPDocument>& sandbox, SPPattern* pattern, bool stock_pattern, double scale) {
    if (!pattern) return Glib::RefPtr<PatternItem>();

    auto item = Glib::RefPtr<PatternItem>(new PatternItem);

    //  this is a link:       this is a root:
    // <pattern href="abc"/> <pattern id="abc"/>
    // if pattern is a root one to begin with, then both pointers will be the same:
    auto link_pattern = pattern;
    auto root_pattern = pattern->rootPattern();

    // get label and ID from root pattern
    Inkscape::XML::Node* repr = root_pattern->getRepr();
    if (auto id = repr->attribute("id")) {
        item->id = id;
    }
    item->label = sp_get_pattern_label(root_pattern);
    item->stock = stock_pattern;
    // read transformation from a link pattern
    item->transform = link_pattern->get_this_transform();
    item->offset = Geom::Point(link_pattern->x(), link_pattern->y());

    // reading color style form "root" pattern; linked one won't have any effect, as it's not a parrent
    if (root_pattern->style && root_pattern->style->isSet(SPAttr::FILL) && root_pattern->style->fill.isColor()) {
        item->color.emplace(SPColor(root_pattern->style->fill.value.color));
    }
    // uniform scaling?
    if (link_pattern->aspect_set) {
        auto preserve = link_pattern->getAttribute("preserveAspectRatio");
        item->uniform_scale = preserve && strcmp(preserve, "none") != 0;
    }
    // pattern tile gap (only valid for link patterns)
    item->gap = link_pattern != root_pattern ? sp_pattern_get_gap(link_pattern) : Geom::Scale(0, 0);

    if (sandbox) {
        // generate preview
        item->pix = create_pattern_image(sandbox, link_pattern->getId(), link_pattern->document, scale);
    }

    // which collection stock pattern comes from
    item->collection = stock_pattern ? pattern->document : nullptr;

    return item;
}

Cairo::RefPtr<Cairo::Surface> PatternManager::get_image(SPPattern* pattern, int width, int height, double device_scale) {
    if (!pattern) return Cairo::RefPtr<Cairo::Surface>();

    _preview_doc->setWidth(Inkscape::Util::Quantity(width, "px"));
    _preview_doc->setHeight(Inkscape::Util::Quantity(height, "px"));
    return create_pattern_image(_preview_doc, pattern->getId(), pattern->document, device_scale);
}

Cairo::RefPtr<Cairo::Surface> PatternManager::get_preview(SPPattern* pattern, int width, int height, unsigned int rgba_background, double device_scale) {
    if (!pattern) return Cairo::RefPtr<Cairo::Surface>();

    _big_preview_doc->setWidth(Inkscape::Util::Quantity(width, "px"));
    _big_preview_doc->setHeight(Inkscape::Util::Quantity(height, "px"));

    return create_pattern_image(_big_preview_doc, pattern->getId(), pattern->document, device_scale, rgba_background);
}

Glib::RefPtr<Inkscape::UI::Widget::PatternItem> PatternManager::get_item(SPPattern* pattern) {
    Glib::RefPtr<Inkscape::UI::Widget::PatternItem> item;
    if (!pattern) return item;

    auto it = _cache.find(pattern);
    // if pattern entry was present in the cache, then it is a stock pattern
    bool stock = it != _cache.end();
    if (!stock || !it->second) {
        // generate item
        std::shared_ptr<SPDocument> nil;
        item = create_pattern_item(nil, pattern, stock, 0);

        if (stock) {
            _cache[pattern] = item;
        }
    }
    else {
        item = it->second;
    }

    return item;
}

Glib::RefPtr<Gtk::TreeModel> PatternManager::get_categories() {
    return _model;
}

} // namespace