diff options
Diffstat (limited to '')
-rw-r--r-- | src/extension/internal/svg.cpp | 1056 |
1 files changed, 1056 insertions, 0 deletions
diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp new file mode 100644 index 0000000..461d174 --- /dev/null +++ b/src/extension/internal/svg.cpp @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is the code that moves all of the SVG loading and saving into + * the module format. Really Inkscape is built to handle these formats + * internally, so this is just calling those internal functions. + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ted Gould <ted@gould.cx> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2002-2003 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm.h> + +#include <giomm/file.h> +#include <giomm/action.h> + +#include "document.h" +#include "inkscape.h" +#include "inkscape-application.h" +#include "preferences.h" +#include "extension/output.h" +#include "extension/input.h" +#include "extension/system.h" +#include "file.h" +#include "svg.h" +#include "file.h" +#include "display/cairo-utils.h" +#include "extension/system.h" +#include "extension/output.h" +#include "xml/attribute-record.h" +#include "xml/simple-document.h" + +#include "object/sp-image.h" +#include "object/sp-root.h" +#include "object/sp-text.h" + +#include "util/units.h" +#include "selection-chemistry.h" + +// TODO due to internal breakage in glibmm headers, this must be last: +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { + +#include "clear-n_.h" + +using Inkscape::XML::Node; + +/* + * Removes all sodipodi and inkscape elements and attributes from an xml tree. + * used to make plain svg output. + */ +static void pruneExtendedNamespaces( Inkscape::XML::Node *repr ) +{ + if (repr) { + if ( repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE ) { + std::vector<gchar const*> attrsRemoved; + for ( const auto & it : repr->attributeList()) { + const gchar* attrName = g_quark_to_string(it.key); + if ((strncmp("inkscape:", attrName, 9) == 0) || (strncmp("sodipodi:", attrName, 9) == 0)) { + attrsRemoved.push_back(attrName); + } + } + // Can't change the set we're iterating over while we are iterating. + for (auto & it : attrsRemoved) { + repr->removeAttribute(it); + } + } + + std::vector<Inkscape::XML::Node *> nodesRemoved; + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + if((strncmp("inkscape:", child->name(), 9) == 0) || strncmp("sodipodi:", child->name(), 9) == 0) { + nodesRemoved.push_back(child); + } else { + pruneExtendedNamespaces(child); + } + } + for (auto & it : nodesRemoved) { + repr->removeChild(it); + } + } +} + +/* + * Similar to the above sodipodi and inkscape prune, but used on all documents + * to remove problematic elements (for example Adobe's i:pgf tag) only removes + * known garbage tags. + */ +static void pruneProprietaryGarbage( Inkscape::XML::Node *repr ) +{ + if (repr) { + std::vector<Inkscape::XML::Node *> nodesRemoved; + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + if((strncmp("i:pgf", child->name(), 5) == 0)) { + nodesRemoved.push_back(child); + g_warning( "An Adobe proprietary tag was found which is known to cause issues. It was removed before saving."); + } else { + pruneProprietaryGarbage(child); + } + } + for (auto & it : nodesRemoved) { + repr->removeChild(it); + } + } +} + +/** + * \return None + * + * \brief Create new markers where necessary to simulate the SVG 2 marker attribute 'orient' + * value 'auto-start-reverse'. + * + * \param repr The current element to check. + * \param defs A pointer to the <defs> element. + * \param css The properties of the element to check. + * \param property Which property to check, either 'marker' or 'marker-start'. + * + */ +static void remove_marker_auto_start_reverse(Inkscape::XML::Node *repr, + Inkscape::XML::Node *defs, + SPCSSAttr *css, + Glib::ustring const &property) +{ + Glib::ustring value = sp_repr_css_property (css, property.c_str(), ""); + + if (!value.empty()) { + + // Find reference <marker> + static Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("url\\(#([^\\)]*)\\)"); + Glib::MatchInfo matchInfo; + regex->match(value, matchInfo); + + if (matchInfo.matches()) { + + auto marker_name = matchInfo.fetch(1).raw(); + Inkscape::XML::Node *marker = sp_repr_lookup_child (defs, "id", marker_name.c_str()); + if (marker) { + + // Does marker use "auto-start-reverse"? + if (strncmp(marker->attribute("orient"), "auto-start-reverse", 17)==0) { + + // See if a reversed marker already exists. + auto marker_name_reversed = marker_name + "_reversed"; + Inkscape::XML::Node *marker_reversed = + sp_repr_lookup_child (defs, "id", marker_name_reversed.c_str()); + + if (!marker_reversed) { + + // No reversed marker, need to create! + marker_reversed = repr->document()->createElement("svg:marker"); + + // Copy attributes + for (const auto & iter : marker->attributeList()) { + marker_reversed->setAttribute(g_quark_to_string(iter.key), iter.value); + } + + // Override attributes + marker_reversed->setAttribute("id", marker_name_reversed); + marker_reversed->setAttribute("orient", "auto"); + + // Find transform + const char* refX = marker_reversed->attribute("refX"); + const char* refY = marker_reversed->attribute("refY"); + std::string transform = "rotate(180"; + if (refX) { + transform += ","; + transform += refX; + + if (refY) { + if (refX) { + transform += ","; + transform += refY; + } else { + transform += ",0,"; + transform += refY; + } + } + } + transform += ")"; + + // We can't set a transform on a marker... must create group first. + Inkscape::XML::Node *group = repr->document()->createElement("svg:g"); + group->setAttribute("transform", transform); + marker_reversed->addChild(group, nullptr); + + // Copy all marker content to group. + for (auto child = marker->firstChild() ; child != nullptr ; child = child->next() ) { + auto new_child = child->duplicate(repr->document()); + group->addChild(new_child, nullptr); + new_child->release(); + } + + // Add new marker to <defs>. + defs->addChild(marker_reversed, marker); + marker_reversed->release(); + } + + // Change url to reference reversed marker. + std::string marker_url("url(#" + marker_name_reversed + ")"); + sp_repr_css_set_property(css, "marker-start", marker_url.c_str()); + + // Also fix up if property is marker shorthand. + if (property == "marker") { + std::string marker_old_url("url(#" + marker_name + ")"); + sp_repr_css_unset_property(css, "marker"); + sp_repr_css_set_property(css, "marker-mid", marker_old_url.c_str()); + sp_repr_css_set_property(css, "marker-end", marker_old_url.c_str()); + } + + sp_repr_css_set(repr, css, "style"); + + } // Uses auto-start-reverse + } + } + } +} + +// Called by remove_marker_context_paint() for each property value ("marker", "marker-start", ...). +static void remove_marker_context_paint (Inkscape::XML::Node *repr, + Inkscape::XML::Node *defs, + Glib::ustring property) +{ + // Value of 'marker', 'marker-start', ... property. + std::string value("url(#"); + value += repr->attribute("id"); + value += ")"; + + // Generate a list of elements that reference this marker. + std::vector<Inkscape::XML::Node *> to_fix_fill_stroke = + sp_repr_lookup_property_many(repr->root(), property, value); + + for (auto it: to_fix_fill_stroke) { + + // Figure out value of fill... could be inherited. + SPCSSAttr* css = sp_repr_css_attr_inherited (it, "style"); + Glib::ustring fill = sp_repr_css_property (css, "fill", ""); + Glib::ustring stroke = sp_repr_css_property (css, "stroke", ""); + + // Name of new marker. + Glib::ustring marker_fixed_id = repr->attribute("id"); + if (!fill.empty()) { + marker_fixed_id += "_F" + fill; + } + if (!stroke.empty()) { + marker_fixed_id += "_S" + stroke; + } + + { + // Replace characters from color value that are invalid in ids + gchar *normalized_id = g_strdup(marker_fixed_id.c_str()); + g_strdelimit(normalized_id, "#%", '-'); + g_strdelimit(normalized_id, "(), \n\t\r", '.'); + marker_fixed_id = normalized_id; + g_free(normalized_id); + } + + // See if a fixed marker already exists. + // Could be more robust, assumes markers are direct children of <defs>. + Inkscape::XML::Node* marker_fixed = sp_repr_lookup_child(defs, "id", marker_fixed_id.c_str()); + + if (!marker_fixed) { + + // Need to create new marker. + + marker_fixed = repr->duplicate(repr->document()); + marker_fixed->setAttribute("id", marker_fixed_id); + + // This needs to be turned into a function that fixes all descendents. + for (auto child = marker_fixed->firstChild() ; child != nullptr ; child = child->next()) { + // Find style. + SPCSSAttr* css = sp_repr_css_attr ( child, "style" ); + + Glib::ustring fill2 = sp_repr_css_property (css, "fill", ""); + if (fill2 == "context-fill" ) { + sp_repr_css_set_property (css, "fill", fill.c_str()); + } + if (fill2 == "context-stroke" ) { + sp_repr_css_set_property (css, "fill", stroke.c_str()); + } + + Glib::ustring stroke2 = sp_repr_css_property (css, "stroke", ""); + if (stroke2 == "context-fill" ) { + sp_repr_css_set_property (css, "stroke", fill.c_str()); + } + if (stroke2 == "context-stroke" ) { + sp_repr_css_set_property (css, "stroke", stroke.c_str()); + } + + sp_repr_css_set(child, css, "style"); + sp_repr_css_attr_unref(css); + } + + defs->addChild(marker_fixed, repr); + marker_fixed->release(); + } + + Glib::ustring marker_value = "url(#" + marker_fixed_id + ")"; + sp_repr_css_set_property (css, property.c_str(), marker_value.c_str()); + sp_repr_css_set (it, css, "style"); + sp_repr_css_attr_unref(css); + } +} + +static void remove_marker_context_paint (Inkscape::XML::Node *repr, + Inkscape::XML::Node *defs) +{ + if (strncmp("svg:marker", repr->name(), 10) == 0) { + + if (!repr->attribute("id")) { + + std::cerr << "remove_marker_context_paint: <marker> without 'id'!" << std::endl; + + } else { + + // First see if we need to do anything. + bool need_to_fix = false; + + // This needs to be turned into a function that searches all descendents. + for (auto child = repr->firstChild() ; child != nullptr ; child = child->next()) { + + // Find style. + SPCSSAttr* css = sp_repr_css_attr ( child, "style" ); + Glib::ustring fill = sp_repr_css_property (css, "fill", ""); + Glib::ustring stroke = sp_repr_css_property (css, "stroke", ""); + if (fill == "context-fill" || + fill == "context-stroke" || + stroke == "context-fill" || + stroke == "context-stroke" ) { + need_to_fix = true; + break; + } + sp_repr_css_attr_unref(css); + } + + if (need_to_fix) { + + // Now we need to search document for all elements that use this marker. + remove_marker_context_paint (repr, defs, "marker"); + remove_marker_context_paint (repr, defs, "marker-start"); + remove_marker_context_paint (repr, defs, "marker-mid"); + remove_marker_context_paint (repr, defs, "marker-end"); + } + } + } +} + +/* + * Recursively insert SVG 1.1 fallback for SVG 2 text (ignored by SVG 2 renderers including ours). + * Notes: + * Text must have been layed out. Access via old document. + */ +static void insert_text_fallback( Inkscape::XML::Node *repr, const SPDocument *original_doc, Inkscape::XML::Node *defs = nullptr ) +{ + if (repr) { + + if (strncmp("svg:text", repr->name(), 8) == 0) { + + auto id = repr->attribute("id"); + // std::cout << "insert_text_fallback: found text! id: " << (id?id:"null") << std::endl; + + // We need to get original SPText object to access layout. + SPText* text = static_cast<SPText *>(original_doc->getObjectById( id )); + if (text == nullptr) { + std::cerr << "insert_text_fallback: bad cast" << std::endl; + return; + } + + if (!text->has_inline_size() && + !text->has_shape_inside()) { + // No SVG 2 text, nothing to do. + return; + } + + // We will keep this text node but replace all children. + // Text object must be visible for the text calculatons to work + bool was_hidden = text->isHidden(); + text->setHidden(false); + + // For text in a shape, We need to unset 'text-anchor' or SVG 1.1 fallback won't work. + // Note 'text' here refers to original document while 'repr' refers to new document copy. + if (text->has_shape_inside()) { + SPCSSAttr *css = sp_repr_css_attr(repr, "style" ); + sp_repr_css_unset_property(css, "text-anchor"); + sp_repr_css_set(repr, css, "style"); + sp_repr_css_attr_unref(css); + } + + // We need to put trailing white space into it's own tspan for inline size so + // it is excluded during calculation of line position in SVG 1.1 renderers. + bool trim = text->has_inline_size() && + !(text->style->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START); + + // Make a list of children to delete at end: + std::vector<Inkscape::XML::Node *> old_children; + for (auto child = repr->firstChild(); child; child = child->next()) { + old_children.push_back(child); + } + + // For round-tripping, xml:space (or 'white-space:pre') must be set. + repr->setAttribute("xml:space", "preserve"); + + double text_x = repr->getAttributeDouble("x", 0.0); + double text_y = repr->getAttributeDouble("y", 0.0); + // std::cout << "text_x: " << text_x << " text_y: " << text_y << std::endl; + + // Loop over all lines in layout. + for (auto it = text->layout.begin() ; it != text->layout.end() ; ) { + + // Create a <tspan> with 'x' and 'y' for each line. + Inkscape::XML::Node *line_tspan = repr->document()->createElement("svg:tspan"); + + // This could be useful if one wants to edit in an old version of Inkscape but we + // need to check if it breaks anything: + // line_tspan->setAttribute("sodipodi:role", "line"); + + // Hide overflow tspan (one line of text). + if (text->layout.isHidden(it)) { + line_tspan->setAttribute("style", "visibility:hidden"); + } + + Geom::Point line_anchor_point = text->layout.characterAnchorPoint(it); + double line_x = line_anchor_point[Geom::X]; + double line_y = line_anchor_point[Geom::Y]; + + // std::cout << " line_anchor_point: " << line_anchor_point << std::endl; + if (line_tspan->childCount() == 0) { + if (text->is_horizontal()) { + // std::cout << " horizontal: " << text_x << " " << line_anchor_point[Geom::Y] << std::endl; + if (text->has_inline_size()) { + // We use text_x as this is the reference for 'text-anchor' + // (line_x is the start of the line which gives wrong position when 'text-anchor' not start). + line_tspan->setAttributeSvgDouble("x", text_x); + } else { + // shape-inside (we don't have to worry about 'text-anchor'). + line_tspan->setAttributeSvgDouble("x", line_x); + } + line_tspan->setAttributeSvgDouble("y", line_y); // FIXME: this will pick up the wrong end of counter-directional runs + } else { + // std::cout << " vertical: " << line_anchor_point[Geom::X] << " " << text_y << std::endl; + line_tspan->setAttributeSvgDouble("x", line_x); // FIXME: this will pick up the wrong end of counter-directional runs + if (text->has_inline_size()) { + line_tspan->setAttributeSvgDouble("y", text_y); + } else { + line_tspan->setAttributeSvgDouble("y", line_y); + } + } + } + + // Inside line <tspan>, create <tspan>s for each change of style or shift. (No shifts in SVG 2 flowed text.) + // For simple lines, this creates an unneeded <tspan> but so be it. + Inkscape::Text::Layout::iterator it_line_end = it; + it_line_end.nextStartOfLine(); + + // Find last span in line so we can put trailing whitespace in its own tspan for SVG 1.1 fallback. + Inkscape::Text::Layout::iterator it_last_span = it; + it_last_span.nextStartOfLine(); + it_last_span.prevStartOfSpan(); + + Glib::ustring trailing_whitespace; + + // Loop over chunks in line + while (it != it_line_end) { + + Inkscape::XML::Node *span_tspan = repr->document()->createElement("svg:tspan"); + + // use kerning to simulate justification and whatnot + Inkscape::Text::Layout::iterator it_span_end = it; + it_span_end.nextStartOfSpan(); + Inkscape::Text::Layout::OptionalTextTagAttrs attrs; + text->layout.simulateLayoutUsingKerning(it, it_span_end, &attrs); + + // 'dx' and 'dy' attributes are used to simulated justified text. + if (!text->is_horizontal()) { + std::swap(attrs.dx, attrs.dy); + } + TextTagAttributes(attrs).writeTo(span_tspan); + SPObject *source_obj = nullptr; + Glib::ustring::iterator span_text_start_iter; + text->layout.getSourceOfCharacter(it, &source_obj, &span_text_start_iter); + + // Set tspan style + Glib::ustring style_text = (dynamic_cast<SPString *>(source_obj) ? source_obj->parent : source_obj) + ->style->writeIfDiff(text->style); + if (!style_text.empty()) { + span_tspan->setAttributeOrRemoveIfEmpty("style", style_text); + } + + // If this tspan has no attributes, discard it and add content directly to parent element. + if (span_tspan->attributeList().empty()) { + Inkscape::GC::release(span_tspan); + span_tspan = line_tspan; + } else { + line_tspan->appendChild(span_tspan); + Inkscape::GC::release(span_tspan); + } + + // Add text node + SPString *str = dynamic_cast<SPString *>(source_obj); + if (str) { + Glib::ustring *string = &(str->string); // TODO fixme: dangerous, unsafe premature-optimization + SPObject *span_end_obj = nullptr; + Glib::ustring::iterator span_text_end_iter; + text->layout.getSourceOfCharacter(it_span_end, &span_end_obj, &span_text_end_iter); + if (span_end_obj != source_obj) { + if (it_span_end == text->layout.end()) { + span_text_end_iter = span_text_start_iter; + for (int i = text->layout.iteratorToCharIndex(it_span_end) - text->layout.iteratorToCharIndex(it) ; i ; --i) + ++span_text_end_iter; + } else + span_text_end_iter = string->end(); // spans will never straddle a source boundary + } + + if (span_text_start_iter != span_text_end_iter) { + Glib::ustring new_string; + while (span_text_start_iter != span_text_end_iter) + new_string += *span_text_start_iter++; // grr. no substr() with iterators + + if (it == it_last_span && trim) { + // Found last span in line + const auto s = new_string.find_last_not_of(" \t"); // Any other white space characters needed? + trailing_whitespace = new_string.substr(s+1, new_string.length()); + new_string.erase(s+1); + } + + Inkscape::XML::Node *new_text = repr->document()->createTextNode(new_string.c_str()); + span_tspan->appendChild(new_text); + Inkscape::GC::release(new_text); + // std::cout << " new_string: |" << new_string << "|" << std::endl; + } + } + it = it_span_end; + } + + // Add line tspan to document + repr->appendChild(line_tspan); + Inkscape::GC::release(line_tspan); + + // For center and end justified text, we need to remove any spaces and put them + // into a separate tspan (alignment is done by "text chunk" and spaces at ends of + // line will mess this up). + if (trim && trailing_whitespace.length() != 0) { + + Inkscape::XML::Node *space_tspan = repr->document()->createElement("svg:tspan"); + // Set either 'x' or 'y' to force a new text chunk. To do: this really should + // be positioned at the end of the line (overhanging). + if (text->is_horizontal()) { + space_tspan->setAttributeSvgDouble("y", line_y); + } else { + space_tspan->setAttributeSvgDouble("x", line_x); + } + Inkscape::XML::Node *space = repr->document()->createTextNode(trailing_whitespace.c_str()); + space_tspan->appendChild(space); + Inkscape::GC::release(space); + line_tspan->appendChild(space_tspan); + Inkscape::GC::release(space_tspan); + } + + } + + for (auto i: old_children) { + repr->removeChild (i); + } + + text->setHidden(was_hidden); + return; // No need to look at children of <text> + } + + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + insert_text_fallback (child, original_doc, defs); + } + } +} + + +static void insert_mesh_polyfill( Inkscape::XML::Node *repr ) +{ + if (repr) { + + Inkscape::XML::Node *defs = sp_repr_lookup_name (repr, "svg:defs"); + + if (defs == nullptr) { + // We always put meshes in <defs>, no defs -> no mesh. + return; + } + + bool has_mesh = false; + for ( Node *child = defs->firstChild(); child; child = child->next() ) { + if (strncmp("svg:meshgradient", child->name(), 16) == 0) { + has_mesh = true; + break; + } + } + + Inkscape::XML::Node *script = sp_repr_lookup_child (repr, "id", "mesh_polyfill"); + + if (has_mesh && script == nullptr) { + + script = repr->document()->createElement("svg:script"); + script->setAttribute ("id", "mesh_polyfill"); + script->setAttribute ("type", "text/javascript"); + repr->root()->appendChild(script); // Must be last + + // Insert JavaScript via raw string literal. + Glib::ustring js = +#include "polyfill/mesh_compressed.include" +; + + Inkscape::XML::Node *script_text = repr->document()->createTextNode(js.c_str()); + script->appendChild(script_text); + } + } +} + + +static void insert_hatch_polyfill( Inkscape::XML::Node *repr ) +{ + if (repr) { + + Inkscape::XML::Node *defs = sp_repr_lookup_name (repr, "svg:defs"); + + if (defs == nullptr) { + // We always put meshes in <defs>, no defs -> no mesh. + return; + } + + bool has_hatch = false; + for ( Node *child = defs->firstChild(); child; child = child->next() ) { + if (strncmp("svg:hatch", child->name(), 16) == 0) { + has_hatch = true; + break; + } + } + + Inkscape::XML::Node *script = sp_repr_lookup_child (repr, "id", "hatch_polyfill"); + + if (has_hatch && script == nullptr) { + + script = repr->document()->createElement("svg:script"); + script->setAttribute ("id", "hatch_polyfill"); + script->setAttribute ("type", "text/javascript"); + repr->root()->appendChild(script); // Must be last + + // Insert JavaScript via raw string literal. + Glib::ustring js = +#include "polyfill/hatch_compressed.include" +; + + Inkscape::XML::Node *script_text = repr->document()->createTextNode(js.c_str()); + script->appendChild(script_text); + } + } +} + +/* + * Recursively transform SVG 2 to SVG 1.1, if possible. + */ +static void transform_2_to_1( Inkscape::XML::Node *repr, Inkscape::XML::Node *defs = nullptr ) +{ + if (repr) { + + // std::cout << "transform_2_to_1: " << repr->name() << std::endl; + + // Things we do once per node. ----------------------- + + // Find defs, if does not exist, create. + if (defs == nullptr) { + defs = sp_repr_lookup_name (repr, "svg:defs"); + } + if (defs == nullptr) { + defs = repr->document()->createElement("svg:defs"); + repr->root()->addChild(defs, nullptr); + } + + // Find style. + SPCSSAttr* css = sp_repr_css_attr ( repr, "style" ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // Individual items ---------------------------------- + + // SVG 2 marker attribute orient:auto-start-reverse: + if ( prefs->getBool("/options/svgexport/marker_autostartreverse", false) ) { + // Do "marker-start" first for efficiency reasons. + remove_marker_auto_start_reverse(repr, defs, css, "marker-start"); + remove_marker_auto_start_reverse(repr, defs, css, "marker"); + } + + // SVG 2 paint values 'context-fill', 'context-stroke': + if ( prefs->getBool("/options/svgexport/marker_contextpaint", false) ) { + remove_marker_context_paint(repr, defs); + } + + // *** To Do *** + // Context fill & stroke outside of markers + // Paint-Order + // Meshes + // Hatches + + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + transform_2_to_1 (child, defs); + } + + sp_repr_css_attr_unref(css); + } +} + + + + +/** + \return None + \brief What would an SVG editor be without loading/saving SVG + files. This function sets that up. + + For each module there is a call to Inkscape::Extension::build_from_mem + with a rather large XML file passed in. This is a constant string + that describes the module. At the end of this call a module is + returned that is basically filled out. The one thing that it doesn't + have is the key function for the operation. And that is linked at + the end of each call. +*/ +void +Svg::init() +{ + // clang-format off + /* SVG in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVG Input") "</name>\n" + "<id>" SP_MODULE_KEY_INPUT_SVG "</id>\n" + SVG_COMMON_INPUT_PARAMS + "<input>\n" + "<extension>.svg</extension>\n" + "<mimetype>image/svg+xml</mimetype>\n" + "<filetypename>" N_("Scalable Vector Graphic (*.svg)") "</filetypename>\n" + "<filetypetooltip>" N_("Inkscape native file format and W3C standard") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new Svg()); + + /* SVG out Inkscape */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVG Output Inkscape") "</name>\n" + "<id>" SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE "</id>\n" + "<output is_exported='true'>\n" + "<extension>.svg</extension>\n" + "<mimetype>image/x-inkscape-svg</mimetype>\n" + "<filetypename>" N_("Inkscape SVG (*.svg)") "</filetypename>\n" + "<filetypetooltip>" N_("SVG format with Inkscape extensions") "</filetypetooltip>\n" + "<dataloss>false</dataloss>\n" + "</output>\n" + "</inkscape-extension>", new Svg()); + + /* SVG out */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVG Output") "</name>\n" + "<id>" SP_MODULE_KEY_OUTPUT_SVG "</id>\n" + "<output is_exported='true'>\n" + "<extension>.svg</extension>\n" + "<mimetype>image/svg+xml</mimetype>\n" + "<filetypename>" N_("Plain SVG (*.svg)") "</filetypename>\n" + "<filetypetooltip>" N_("Scalable Vector Graphics format as defined by the W3C") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new Svg()); + // clang-format on + + return; +} + + +/** + \return A new document just for you! + \brief This function takes in a filename of a SVG document and + turns it into a SPDocument. + \param mod Module to use + \param uri The path or URI to the file (UTF-8) + + This function is really simple, it just calls sp_document_new... + That's BS, it does all kinds of things for importing documents + that probably should be in a separate function. + + Most of the import code was copied from gdkpixpuf-input.cpp. +*/ +SPDocument * +Svg::open (Inkscape::Extension::Input *mod, const gchar *uri) +{ + g_assert(mod != nullptr); + + // This is only used at the end... but it should go here once uri stuff is fixed. + auto file = Gio::File::create_for_commandline_arg(uri); + const auto path = file->get_path(); + + // Fixing this means fixing a whole string of things. + // if (path.empty()) { + // // We lied, the uri wasn't a uri, try as path. + // file = Gio::File::create_for_path(uri); + // } + + // std::cout << "Svg::open: uri in: " << uri << std::endl; + // std::cout << " : uri: " << file->get_uri() << std::endl; + // std::cout << " : scheme: " << file->get_uri_scheme() << std::endl; + // std::cout << " : path: " << file->get_path() << std::endl; + // std::cout << " : parse: " << file->get_parse_name() << std::endl; + // std::cout << " : base: " << file->get_basename() << std::endl; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // Get import preferences. + bool ask_svg = prefs->getBool( "/dialogs/import/ask_svg"); + Glib::ustring import_mode_svg = prefs->getString("/dialogs/import/import_mode_svg"); + Glib::ustring scale = prefs->getString("/dialogs/import/scale"); + + // If we popped up a window asking about import preferences, get values from + // there and update preferences. + if(mod->get_gui() && ask_svg) { + ask_svg = !mod->get_param_bool("do_not_ask"); + import_mode_svg = mod->get_param_optiongroup("import_mode_svg"); + scale = mod->get_param_optiongroup("scale"); + + prefs->setBool( "/dialogs/import/ask_svg", ask_svg); + prefs->setString("/dialogs/import/import_mode_svg", import_mode_svg ); + prefs->setString("/dialogs/import/scale", scale ); + } + + bool import = prefs->getBool("/options/onimport", false); + bool import_pages = (import_mode_svg == "pages"); + // Do we open a new svg instead of import? + if (uri && import && import_mode_svg == "new") { + prefs->setBool("/options/onimport", false); // set back to true in file_import + static auto gapp = InkscapeApplication::instance()->gtk_app(); + auto action = gapp->lookup_action("file-open-window"); + auto file_dnd = Glib::Variant<Glib::ustring>::create(uri); + action->activate(file_dnd); + return SPDocument::createNewDoc (nullptr, true, true); + } + // Do we "import" as <image>? + if (import && import_mode_svg != "include" && !import_pages) { + // We import! + + // New wrapper document. + SPDocument * doc = SPDocument::createNewDoc (nullptr, true, true); + + // Imported document + // SPDocument * ret = SPDocument::createNewDoc(file->get_uri().c_str(), true); + SPDocument * ret = SPDocument::createNewDoc(uri, true); + + if (!ret) { + return nullptr; + } + + // What is display unit doing here? + Glib::ustring display_unit = doc->getDisplayUnit()->abbr; + double width = ret->getWidth().value(display_unit); + double height = ret->getHeight().value(display_unit); + if (width < 0 || height < 0) { + return nullptr; + } + + // Create image node + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *image_node = xml_doc->createElement("svg:image"); + + // Set default value as we honor "preserveAspectRatio". + image_node->setAttribute("preserveAspectRatio", "none"); + + double svgdpi = mod->get_param_float("svgdpi"); + image_node->setAttribute("inkscape:svg-dpi", Glib::ustring::format(svgdpi)); + + image_node->setAttribute("width", Glib::ustring::format(width)); + image_node->setAttribute("height", Glib::ustring::format(height)); + + // This is actually "image-rendering" + Glib::ustring scale = prefs->getString("/dialogs/import/scale"); + if( scale != "auto") { + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "image-rendering", scale.c_str()); + sp_repr_css_set(image_node, css, "style"); + sp_repr_css_attr_unref( css ); + } + + // Do we embed or link? + if (import_mode_svg == "embed") { + std::unique_ptr<Inkscape::Pixbuf> pb(Inkscape::Pixbuf::create_from_file(uri, svgdpi)); + if(pb) { + sp_embed_svg(image_node, uri); + } + } else { + // Convert filename to uri (why do we need to do this, we claimed it was already a uri). + gchar* _uri = g_filename_to_uri(uri, nullptr, nullptr); + if(_uri) { + // if (strcmp(_uri, uri) != 0) { + // std::cout << "Svg::open: _uri != uri! " << _uri << ":" << uri << std::endl; + // } + image_node->setAttribute("xlink:href", _uri); + g_free(_uri); + } else { + image_node->setAttribute("xlink:href", uri); + } + } + + // Add the image to a layer. + Inkscape::XML::Node *layer_node = xml_doc->createElement("svg:g"); + layer_node->setAttribute("inkscape:groupmode", "layer"); + layer_node->setAttribute("inkscape:label", "Image"); + doc->getRoot()->appendChildRepr(layer_node); + layer_node->appendChild(image_node); + Inkscape::GC::release(image_node); + Inkscape::GC::release(layer_node); + fit_canvas_to_drawing(doc); + + // Set viewBox if it doesn't exist. What is display unit doing here? + if (!doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit()))); + } + return doc; + } + + // We are not importing as <image>. Open as new document. + + // Try to open non-local file (when does this occur?). + if (!file->get_uri_scheme().empty()) { + if (path.empty()) { + try { + char *contents; + gsize length; + file->load_contents(contents, length); + return SPDocument::createNewDocFromMem(contents, length, true); + } catch (Gio::Error &e) { + g_warning("Could not load contents of non-local URI %s\n", uri); + return nullptr; + } + } else { + // Do we ever get here and does this actually work? + uri = path.c_str(); + } + } + + SPDocument *doc = SPDocument::createNewDoc(uri, true); + + // Convert single page docs into multi page mode, and visa-versa if + // we are importing. We never change the mode for opening. + if (doc && import) { + doc->setPages(import_pages); + } + + return doc; +} + +/** + \return None + \brief This is the function that does all of the SVG saves in + Inkscape. It detects whether it should do a Inkscape + namespace save internally. + \param mod Extension to use. + \param doc Document to save. + \param uri The filename to save the file to. + + This function first checks its parameters, and makes sure that + we're getting good data. It also checks the module ID of the + incoming module to figure out whether this save should include + the Inkscape namespace stuff or not. The result of that comparison + is stored in the exportExtensions variable. + + If there is not to be Inkscape name spaces a new document is created + without. (I think, I'm not sure on this code) + + All of the internally referenced imageins are also set to relative + paths in the file. And the file is saved. + + This really needs to be fleshed out more, but I don't quite understand + all of this code. I just stole it. +*/ +void +Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) +{ + g_return_if_fail(doc != nullptr); + g_return_if_fail(filename != nullptr); + Inkscape::XML::Document *rdoc = doc->getReprDoc(); + const SPDocument *original_doc = doc->getOriginalDocument(); + + bool const exportExtensions = ( !mod->get_id() + || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE) + || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE)); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool const transform_2_to_1_flag = + prefs->getBool("/dialogs/save_as/enable_svgexport", false); + + bool const insert_text_fallback_flag = + prefs->getBool("/options/svgexport/text_insertfallback", true); + bool const insert_mesh_polyfill_flag = + prefs->getBool("/options/svgexport/mesh_insertpolyfill", true); + bool const insert_hatch_polyfill_flag = + prefs->getBool("/options/svgexport/hatch_insertpolyfill", true); + + // We used to copy the svg here if there was modification, but no need now + // We copy the svg document for ALL exports and saves and the extensions can + // decide what to do without fear. + + // We prune the in-use document and deliberately loose data, because there + // is no known use for this data at the present time. + pruneProprietaryGarbage(rdoc->root()); + + // Start off with a svg 2.0 document + rdoc->setAttribute("standalone", "no"); + rdoc->setAttribute("version", "2.0"); + + if (!exportExtensions) { + pruneExtendedNamespaces(rdoc->root()); + } + + if (transform_2_to_1_flag) { + transform_2_to_1 (rdoc->root()); + rdoc->setAttribute("version", "1.1"); + } + + if (insert_text_fallback_flag && original_doc) { + insert_text_fallback (rdoc->root(), original_doc); + } + + if (insert_mesh_polyfill_flag) { + insert_mesh_polyfill (rdoc->root()); + } + + if (insert_hatch_polyfill_flag) { + insert_hatch_polyfill (rdoc->root()); + } + + if (!sp_repr_save_rebased_file(rdoc, filename, SP_SVG_NS_URI, + doc->getDocumentBase(), // + m_detachbase ? nullptr : filename)) { + throw Inkscape::Extension::Output::save_failed(); + } +} + +} } } /* namespace inkscape, module, implementation */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : |