diff options
Diffstat (limited to 'src/pattern-manager.cpp')
-rw-r--r-- | src/pattern-manager.cpp | 256 |
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 |