// SPDX-License-Identifier: GPL-2.0-or-later #include #include #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 get_preview_document() { char const* buffer = R"A( )A"; return std::shared_ptr(SPDocument::createNewDocFromMem(buffer, strlen(buffer), false)); } // pattern preview document without background std::shared_ptr get_big_preview_document() { char const* buffer = R"A( )A"; return std::shared_ptr(SPDocument::createNewDocFromMem(buffer, strlen(buffer), false)); } PatternManager& PatternManager::get() { struct ConstructiblePatternManager : PatternManager {}; static auto factory = Inkscape::Util::Static(); 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 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(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(); } // special "all patterns" category auto all_patterns = Glib::RefPtr(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& a, const Glib::RefPtr& 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 create_pattern_image(std::shared_ptr& sandbox, char const* name, SPDocument* source, double scale, std::optional checkerboard = std::optional()) { // 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(); } 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 create_pattern_item(std::shared_ptr& sandbox, SPPattern* pattern, bool stock_pattern, double scale) { if (!pattern) return Glib::RefPtr(); auto item = Glib::RefPtr(new PatternItem); // this is a link: this is a root: // // 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 PatternManager::get_image(SPPattern* pattern, int width, int height, double device_scale) { if (!pattern) return Cairo::RefPtr(); _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 PatternManager::get_preview(SPPattern* pattern, int width, int height, unsigned int rgba_background, double device_scale) { if (!pattern) return Cairo::RefPtr(); _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 PatternManager::get_item(SPPattern* pattern) { Glib::RefPtr 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 nil; item = create_pattern_item(nil, pattern, stock, 0); if (stock) { _cache[pattern] = item; } } else { item = it->second; } return item; } Glib::RefPtr PatternManager::get_categories() { return _model; } } // namespace