summaryrefslogtreecommitdiffstats
path: root/src/pattern-manager.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 11:50:49 +0000
commitc853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch)
tree7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/pattern-manager.cpp
parentInitial commit. (diff)
downloadinkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.tar.xz
inkscape-c853ffb5b2f75f5a889ed2e3ef89b818a736e87a.zip
Adding upstream version 1.3+ds.upstream/1.3+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pattern-manager.cpp')
-rw-r--r--src/pattern-manager.cpp256
1 files changed, 256 insertions, 0 deletions
diff --git a/src/pattern-manager.cpp b/src/pattern-manager.cpp
new file mode 100644
index 0000000..4ba018e
--- /dev/null
+++ b/src/pattern-manager.cpp
@@ -0,0 +1,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