summaryrefslogtreecommitdiffstats
path: root/src/io
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/io
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/io')
-rw-r--r--src/io/CMakeLists.txt36
-rw-r--r--src/io/README35
-rw-r--r--src/io/dir-util.cpp264
-rw-r--r--src/io/dir-util.h75
-rw-r--r--src/io/file-export-cmd.cpp960
-rw-r--r--src/io/file-export-cmd.h103
-rw-r--r--src/io/file.cpp192
-rw-r--r--src/io/file.h58
-rw-r--r--src/io/fix-broken-links.cpp416
-rw-r--r--src/io/fix-broken-links.h30
-rw-r--r--src/io/http.cpp153
-rw-r--r--src/io/http.h41
-rw-r--r--src/io/resource.cpp559
-rw-r--r--src/io/resource.h126
-rw-r--r--src/io/stream/README13
-rw-r--r--src/io/stream/bufferstream.cpp144
-rw-r--r--src/io/stream/bufferstream.h98
-rw-r--r--src/io/stream/gzipstream.cpp463
-rw-r--r--src/io/stream/gzipstream.h120
-rw-r--r--src/io/stream/inkscapestream.cpp800
-rw-r--r--src/io/stream/inkscapestream.h670
-rw-r--r--src/io/stream/stringstream.cpp125
-rw-r--r--src/io/stream/stringstream.h105
-rw-r--r--src/io/stream/uristream.cpp171
-rw-r--r--src/io/stream/uristream.h101
-rw-r--r--src/io/stream/xsltstream.cpp248
-rw-r--r--src/io/stream/xsltstream.h140
-rw-r--r--src/io/sys.cpp360
-rw-r--r--src/io/sys.h70
29 files changed, 6676 insertions, 0 deletions
diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt
new file mode 100644
index 0000000..2d5198c
--- /dev/null
+++ b/src/io/CMakeLists.txt
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(io_SRC
+ dir-util.cpp
+ file.cpp
+ file-export-cmd.cpp
+ resource.cpp
+ fix-broken-links.cpp
+ stream/bufferstream.cpp
+ stream/gzipstream.cpp
+ stream/inkscapestream.cpp
+ stream/stringstream.cpp
+ stream/uristream.cpp
+ stream/xsltstream.cpp
+ sys.cpp
+ http.cpp
+
+ # -------
+ # Headers
+ dir-util.h
+ file.h
+ file-export-cmd.h
+ resource.h
+ fix-broken-links.h
+ stream/bufferstream.h
+ stream/gzipstream.h
+ stream/inkscapestream.h
+ stream/stringstream.h
+ stream/uristream.h
+ stream/xsltstream.h
+ sys.h
+ http.h
+)
+
+# add_inkscape_lib(io_LIB "${io_SRC}")
+add_inkscape_source("${io_SRC}")
diff --git a/src/io/README b/src/io/README
new file mode 100644
index 0000000..9d971b7
--- /dev/null
+++ b/src/io/README
@@ -0,0 +1,35 @@
+
+This directory contains code related to input and output.
+
+
+Note that input and output to particular file formats may be implemented elsewhere:
+
+1. Internal extensions. See src/extensions/internal
+
+ Input: CDR, EMF, WMF VSD WPG
+ Output: SVG, PNG (via Cairo), PS, EPS, PDF, POV, ODF, EMF, WMF, XAML, LaTex.
+
+2. External extensions (Python). See share/extensions
+
+ Input: PS, EPS, PDF, SK, XAML, DXF, DIA, AI, ?
+ Output: SVG (Layers/Optimized), SK1, XCF, HTML5, SIF, PTL, HPGL, DXF, FXG, XAML(?), CANVAS, ?
+
+3. SVG (XML) low level code: src/xml/repr-io.h
+
+
+To do:
+
+1. Move all file related code here (other than extensions).
+2. Move extension input/output code into subdirectories within src/extensions/internal and share/extensions.
+3. Separate out creating a document and creating a document window. The former belongs here, the later in src/ui.
+4. Use std::string for all file names and use glibmm file utilities.
+5. Use Glib::ustring for URI's and use Inkscape's URI utilities (if not available in glibmm).
+6. Rewrite file export code to share a common base and to allow easy export of objects. Should be Gio::Action based.
+ Things like cropping, selecting an object, hiding other objects, etc. should be done before the document is passed
+ to the file type specific export code. This way, we can use the same export options for all file types (where they
+ make sense). Only type specific options (e.g. PS Level) should be handled by the type specific code.
+
+
+Files to move here:
+ src/util/ziptool.h (used by ODF, note SVG uses Inkscape::IO::GzipInputStream, can that be used instead?).
+ src/helper/png-write.h
diff --git a/src/io/dir-util.cpp b/src/io/dir-util.cpp
new file mode 100644
index 0000000..abd5c8f
--- /dev/null
+++ b/src/io/dir-util.cpp
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/**
+ * @file
+ * Utility functions for filenames.
+ */
+
+#include <cerrno>
+#include <string>
+#include <cstring>
+#include <glib.h>
+#include "dir-util.h"
+
+std::string sp_relative_path_from_path( std::string const &path, std::string const &base)
+{
+ std::string result;
+ if ( !base.empty() && !path.empty() ) {
+ size_t base_len = base.length();
+ while (base_len != 0
+ && (base[base_len - 1] == G_DIR_SEPARATOR))
+ {
+ --base_len;
+ }
+
+ if ( (path.substr(0, base_len) == base.substr(0, base_len))
+ && (path[base_len] == G_DIR_SEPARATOR))
+ {
+ size_t retPos = base_len + 1;
+ while ( (retPos < path.length()) && (path[retPos] == G_DIR_SEPARATOR) ) {
+ retPos++;
+ }
+ if ( (retPos + 1) < path.length() ) {
+ result = path.substr(retPos);
+ }
+ }
+
+ }
+ if ( result.empty() ) {
+ result = path;
+ }
+ return result;
+}
+
+char const *sp_extension_from_path(char const *const path)
+{
+ if (path == nullptr) {
+ return nullptr;
+ }
+
+ char const *p = path;
+ while (*p != '\0') p++;
+
+ while ((p >= path) && (*p != G_DIR_SEPARATOR) && (*p != '.')) p--;
+ if (* p != '.') return nullptr;
+ p++;
+
+ return p;
+}
+
+
+/* current == "./", parent == "../" */
+static char const dots[] = {'.', '.', G_DIR_SEPARATOR, '\0'};
+static char const *const parent = dots;
+static char const *const current = dots + 1;
+
+char *inkscape_rel2abs(const char *path, const char *base, char *result, const size_t size)
+{
+ const char *pp, *bp;
+ /* endp points the last position which is safe in the result buffer. */
+ const char *endp = result + size - 1;
+ char *rp;
+ int length;
+ if (*path == G_DIR_SEPARATOR)
+ {
+ if (strlen (path) >= size)
+ goto erange;
+ strcpy (result, path);
+ goto finish;
+ }
+ else if (*base != G_DIR_SEPARATOR || !size)
+ {
+ errno = EINVAL;
+ return (nullptr);
+ }
+ else if (size == 1)
+ goto erange;
+ if (!strcmp (path, ".") || !strcmp (path, current))
+ {
+ if (strlen (base) >= size)
+ goto erange;
+ strcpy (result, base);
+ /* rp points the last char. */
+ rp = result + strlen (base) - 1;
+ if (*rp == G_DIR_SEPARATOR)
+ *rp = 0;
+ else
+ rp++;
+ /* rp point NULL char */
+ if (*++path == G_DIR_SEPARATOR)
+ {
+ /* Append G_DIR_SEPARATOR to the tail of path name. */
+ *rp++ = G_DIR_SEPARATOR;
+ if (rp > endp)
+ goto erange;
+ *rp = 0;
+ }
+ goto finish;
+ }
+ bp = base + strlen (base);
+ if (*(bp - 1) == G_DIR_SEPARATOR)
+ --bp;
+ /* up to root. */
+ for (pp = path; *pp && *pp == '.';)
+ {
+ if (!strncmp (pp, parent, 3))
+ {
+ pp += 3;
+ while (bp > base && *--bp != G_DIR_SEPARATOR)
+ ;
+ }
+ else if (!strncmp (pp, current, 2))
+ {
+ pp += 2;
+ }
+ else if (!strncmp (pp, "..\0", 3))
+ {
+ pp += 2;
+ while (bp > base && *--bp != G_DIR_SEPARATOR)
+ ;
+ }
+ else
+ break;
+ }
+ /* down to leaf. */
+ length = bp - base;
+ if (length >= static_cast<int>(size))
+ goto erange;
+ strncpy (result, base, length);
+ rp = result + length;
+ if (*pp || *(pp - 1) == G_DIR_SEPARATOR || length == 0)
+ *rp++ = G_DIR_SEPARATOR;
+ if (rp + strlen (pp) > endp)
+ goto erange;
+ strcpy (rp, pp);
+finish:
+ return result;
+erange:
+ errno = ERANGE;
+ return (nullptr);
+}
+
+char *inkscape_abs2rel(const char *path, const char *base, char *result, const size_t size)
+{
+ const char *pp, *bp, *branch;
+ // endp points the last position which is safe in the result buffer.
+ const char *endp = result + size - 1;
+ char *rp;
+
+ if (*path != G_DIR_SEPARATOR)
+ {
+ if (strlen (path) >= size)
+ goto erange;
+ strcpy (result, path);
+ goto finish;
+ }
+ else if (*base != G_DIR_SEPARATOR || !size)
+ {
+ errno = EINVAL;
+ return (nullptr);
+ }
+ else if (size == 1)
+ goto erange;
+ /* seek to branched point. */
+ branch = path;
+ for (pp = path, bp = base; *pp && *bp && *pp == *bp; pp++, bp++)
+ if (*pp == G_DIR_SEPARATOR)
+ branch = pp;
+ if (((*pp == 0) || ((*pp == G_DIR_SEPARATOR) && (*(pp + 1) == 0))) &&
+ ((*bp == 0) || ((*bp == G_DIR_SEPARATOR) && (*(bp + 1) == 0))))
+ {
+ rp = result;
+ *rp++ = '.';
+ if (*pp == G_DIR_SEPARATOR || *(pp - 1) == G_DIR_SEPARATOR)
+ *rp++ = G_DIR_SEPARATOR;
+ if (rp > endp)
+ goto erange;
+ *rp = 0;
+ goto finish;
+ }
+ if (((*pp == 0) && (*bp == G_DIR_SEPARATOR)) || ((*pp == G_DIR_SEPARATOR) && (*bp == 0)))
+ branch = pp;
+ /* up to root. */
+ rp = result;
+ for (bp = base + (branch - path); *bp; bp++)
+ if (*bp == G_DIR_SEPARATOR && *(bp + 1) != 0)
+ {
+ if (rp + 3 > endp)
+ goto erange;
+ *rp++ = '.';
+ *rp++ = '.';
+ *rp++ = G_DIR_SEPARATOR;
+ }
+ if (rp > endp)
+ goto erange;
+ *rp = 0;
+ /* down to leaf. */
+ if (*branch)
+ {
+ if (rp + strlen (branch + 1) > endp)
+ goto erange;
+ strcpy (rp, branch + 1);
+ }
+ else
+ *--rp = 0;
+finish:
+ return result;
+erange:
+ errno = ERANGE;
+ return (nullptr);
+}
+
+char *prepend_current_dir_if_relative(gchar const *uri)
+{
+ if (!uri) {
+ return nullptr;
+ }
+
+ gchar *full_path = (gchar *) g_malloc (1001);
+ gchar *cwd = g_get_current_dir();
+
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError* error = nullptr;
+ gchar* cwd_utf8 = g_filename_to_utf8 ( cwd,
+ -1,
+ &bytesRead,
+ &bytesWritten,
+ &error);
+
+ inkscape_rel2abs (uri, cwd_utf8, full_path, 1000);
+ gchar *ret = g_strdup (full_path);
+ g_free(cwd_utf8);
+ g_free (full_path);
+ g_free (cwd);
+ return ret;
+}
+
+/*
+ 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:
+*/
+// vi: set autoindent shiftwidth=4 tabstop=8 filetype=cpp expandtab softtabstop=4 fileencoding=utf-8 textwidth=99 :
diff --git a/src/io/dir-util.h b/src/io/dir-util.h
new file mode 100644
index 0000000..91f07c8
--- /dev/null
+++ b/src/io/dir-util.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2016 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_DIR_UTIL_H
+#define SEEN_DIR_UTIL_H
+
+/*
+ * path-util.h
+ *
+ * here are functions sp_relative_path & cousins
+ * maybe they are already implemented in standard libs
+ *
+ */
+
+#include <cstdlib>
+#include <string>
+
+/**
+ * Returns a form of \a path relative to \a base if that is easy to construct (eg if \a path
+ * appears to be in the directory specified by \a base), otherwise returns \a path.
+ *
+ * @param path is expected to be an absolute path.
+ * @param base is expected to be either empty or the absolute path of a directory.
+ *
+ * @return a relative version of the path, if reasonable.
+ *
+ * @see inkscape_abs2rel for a more sophisticated version.
+ * @see prepend_current_dir_if_relative.
+*/
+std::string sp_relative_path_from_path(std::string const &path, std::string const &base);
+
+char const *sp_extension_from_path(char const *path);
+
+/**
+ * Convert a relative path name into absolute. If path is already absolute, does nothing except copying path to result.
+ *
+ * @param path relative path.
+ * @param base base directory (must be absolute path).
+ * @param result result buffer.
+ * @param size size of result buffer.
+ *
+ * @return != NULL: absolute path
+ * == NULL: error
+ *
+ * based on functions by Shigio Yamaguchi.
+ * FIXME:TODO: force it to also do path normalization of the entire resulting path,
+ * i.e. get rid of any .. and . in any place, even if 'path' is already absolute
+ * (now it returns it unchanged in this case)
+ *
+ */
+char *inkscape_rel2abs(char const *path, char const *base, char *result, size_t const size);
+
+char *inkscape_abs2rel(char const *path, char const *base, char *result, size_t const size);
+
+char *prepend_current_dir_if_relative(char const *filename);
+
+
+#endif // !SEEN_DIR_UTIL_H
+
+/*
+ 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/file-export-cmd.cpp b/src/io/file-export-cmd.cpp
new file mode 100644
index 0000000..1663a82
--- /dev/null
+++ b/src/io/file-export-cmd.cpp
@@ -0,0 +1,960 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File export from the command line. This code, greatly modified, use to be in main.cpp. It should
+ * be replaced by code shared with the file dialog (Gio::Actions?).
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * Git blame shows that bulia byak is the main author of the original export code from
+ * main.cpp. Other authors of note include Nicolas Dufour, Vinicius dos Santos Oliveira, and Bob
+ * Jamison; none of whom bothered to add their names to the copyright of main.cc.
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "file-export-cmd.h"
+
+#include <boost/algorithm/string.hpp>
+#include <iostream>
+#include <png.h> // PNG export
+#include <string>
+
+#include "document.h"
+#include "extension/db.h"
+#include "extension/extension.h"
+#include "extension/init.h"
+#include "extension/output.h"
+#include "extension/system.h"
+#include "helper/png-write.h" // PNG Export
+#include "object/object-set.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-item.h"
+#include "object/sp-namedview.h"
+#include "object/sp-object-group.h"
+#include "object/sp-page.h"
+#include "object/sp-root.h"
+#include "object/sp-text.h"
+#include "page-manager.h"
+#include "path-chemistry.h" // sp_item_list_to_curves
+#include "selection-chemistry.h" // fit_canvas_to_drawing
+#include "svg/svg-color.h" // Background color
+#include "text-editing.h" // te_update_layout_now_recursive
+#include "util/parse-int-range.h"
+#include "io/sys.h"
+
+// Temporary dependency : once all compilers we want to support have support for
+// C++17 std::filesystem (with #include <filesystem> ) then we drop this dep
+// (Dev meeting, 2020-09-25)
+
+#ifdef G_OS_WIN32
+#include <filesystem>
+namespace filesystem = std::filesystem;
+#else
+#include <boost/filesystem.hpp>
+namespace filesystem = boost::filesystem;
+#endif
+
+InkFileExportCmd::InkFileExportCmd()
+ : export_overwrite(false)
+ , export_margin(0)
+ , export_area_snap(false)
+ , export_use_hints(false)
+ , export_width(0)
+ , export_height(0)
+ , export_dpi(0)
+ , export_ignore_filters(false)
+ , export_text_to_path(false)
+ , export_ps_level(3)
+ , export_pdf_level("1.5")
+ , export_latex(false)
+ , export_id_only(false)
+ , export_background_opacity(-1) // default is unset != actively set to 0
+ , export_plain_svg(false)
+{
+}
+
+void
+InkFileExportCmd::do_export(SPDocument* doc, std::string filename_in)
+{
+ std::string export_type_filename;
+ std::vector<Glib::ustring> export_type_list;
+
+ // Get export type from filename supplied with --export-filename
+ if (!export_filename.empty() && export_filename != "-") {
+
+ // Attempt to result variable and home path use in export filenames.
+ Glib::RefPtr<Gio::File> gfile = Gio::File::create_for_parse_name(export_filename);
+ export_filename = gfile->get_parse_name();
+
+#ifdef G_OS_WIN32
+ auto fn = filesystem::u8path(export_filename);
+#else
+ auto fn = filesystem::path(export_filename);
+#endif
+ if (!fn.has_extension()) {
+ if (export_type.empty() && export_extension.empty()) {
+ std::cerr << "InkFileExportCmd::do_export: No export type specified. "
+ << "Append a supported file extension to filename provided with --export-filename or "
+ << "provide one or more extensions separately using --export-type" << std::endl;
+ return;
+ } else {
+ // no extension is fine if --export-type is given
+ // explicitly stated extensions are handled later
+ }
+ } else {
+ export_type_filename = fn.extension().string().substr(1);
+ boost::algorithm::to_lower(export_type_filename);
+ export_filename = (fn.parent_path() / fn.stem()).string();
+ }
+ }
+
+ // Get export type(s) from string supplied with --export-type
+ if (!export_type.empty()) {
+ export_type_list = Glib::Regex::split_simple("[,;]", export_type);
+ }
+
+ // Determine actual type(s) for export.
+ if (export_use_hints) {
+ // Override type if --export-use-hints is used (hints presume PNG export for now)
+ // TODO: There's actually no reason to presume. We could allow to export to any format using hints!
+ if (export_id.empty() && export_area_type != ExportAreaType::Drawing) {
+ std::cerr << "InkFileExportCmd::do_export: "
+ << "--export-use-hints can only be used with --export-id or --export-area-drawing." << std::endl;
+ return;
+ }
+ if (export_type_list.size() > 1 || (export_type_list.size() == 1 && export_type_list[0] != "png")) {
+ std::cerr << "InkFileExportCmd::do_export: --export-use-hints can only be used with PNG export! "
+ << "Ignoring --export-type=" << export_type.raw() << "." << std::endl;
+ }
+ if (!export_filename.empty()) {
+ std::cerr << "InkFileExportCmd::do_export: --export-filename is ignored when using --export-use-hints!" << std::endl;
+ }
+ export_type_list.clear();
+ export_type_list.emplace_back("png");
+ } else if (export_type_list.empty()) {
+ if (!export_type_filename.empty()) {
+ export_type_list.emplace_back(export_type_filename); // use extension from filename
+ } else if (!export_extension.empty()) {
+ // guess export type from extension
+ auto ext =
+ dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get(export_extension.data()));
+ if (ext) {
+ export_type_list.emplace_back(std::string(ext->get_extension()).substr(1));
+ } else {
+ std::cerr << "InkFileExportCmd::do_export: "
+ << "The supplied --export-extension was not found. Specify a file extension "
+ << "to get a list of available extensions for this file type.";
+ return;
+ }
+ } else {
+ export_type_list.emplace_back("svg"); // fall-back to SVG by default
+ }
+ }
+ // Export filename should be used when specified as the output file
+ if (export_filename.empty()) {
+ export_filename = filename_in;
+ }
+ // check if multiple export files are requested, but --export_extension was supplied
+ if (!export_extension.empty() && export_type_list.size() != 1) {
+ std::cerr
+ << "InkFileExportCmd::do_export: You may only specify one export type if --export-extension is supplied";
+ return;
+ }
+ Inkscape::Extension::DB::OutputList extension_list;
+ Inkscape::Extension::db.get_output_list(extension_list);
+
+ for (auto const &Type : export_type_list) {
+ // use lowercase type for following comparisons
+ auto type = Type.lowercase();
+ g_info("exporting '%s' to type '%s'", filename_in.c_str(), type.c_str());
+
+ export_type_current = type;
+
+ // Check for consistency between extension of --export-filename and --export-type if both are given
+ if (!export_type_filename.empty() && (type != export_type_filename)) {
+ std::cerr << "InkFileExportCmd::do_export: "
+ << "Ignoring extension of export filename (" << export_type_filename << ") "
+ << "as it does not match the current export type (" << type.raw() << ")." << std::endl;
+ }
+ bool export_extension_forced = !export_extension.empty();
+ // For PNG export, there is no extension, so the method below can not be used.
+ if (type == "png") {
+ if (!export_extension_forced) {
+ do_export_png(doc, export_filename);
+ } else {
+ std::cerr << "InkFileExportCmd::do_export: "
+ << "The parameter --export-extension is invalid for PNG export" << std::endl;
+ }
+ continue;
+ }
+ // for SVG export, we let the do_export_svg function handle the selection of the extension, unless
+ // an extension ID was explicitly given. This makes handling of --export-plain-svg easier (which
+ // should also work when multiple file types are given, unlike --export-extension)
+ if (type == "svg" && !export_extension_forced) {
+ do_export_svg(doc, export_filename);
+ continue;
+ }
+
+ bool extension_for_fn_exists = false;
+ bool exported = false;
+ // if no extension is found, the entire list of extensions is walked through,
+ // so we can use the same loop to construct the list of available formats for the error message
+ std::list<std::string> filetypes({".svg", ".png", ".ps", ".eps", ".pdf"});
+ std::list<std::string> exts_for_fn;
+ for (auto oext : extension_list) {
+ if (oext->deactivated()) {
+ continue;
+ }
+ auto name = Glib::ustring(oext->get_extension()).lowercase();
+ filetypes.emplace_back(name);
+ if (name == "." + type) {
+ extension_for_fn_exists = true;
+ exts_for_fn.emplace_back(oext->get_id());
+ if (!export_extension_forced ||
+ (export_extension == Glib::ustring(oext->get_id()).lowercase())) {
+ if (type == "svg") {
+ do_export_vector(doc, export_filename, *oext);
+ } else if (type == "ps") {
+ do_export_ps_pdf(doc, export_filename, "image/x-postscript", *oext);
+ } else if (type == "eps") {
+ do_export_ps_pdf(doc, export_filename, "image/x-e-postscript", *oext);
+ } else if (type == "pdf") {
+ do_export_ps_pdf(doc, export_filename, "application/pdf", *oext);
+ } else {
+ do_export_extension(doc, export_filename, oext);
+ }
+ exported = true;
+ break;
+ }
+ }
+ }
+ if (!exported) {
+ if (export_extension_forced && extension_for_fn_exists) {
+ // the located extension for this file type did not match the provided --export-extension parameter
+ std::cerr << "InkFileExportCmd::do_export: "
+ << "The supplied extension ID (" << export_extension
+ << ") does not match any of the extensions "
+ << "available for this file type." << std::endl
+ << "Supported IDs for this file type: [";
+ copy(exts_for_fn.begin(), exts_for_fn.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
+ std::cerr << "\b\b]" << std::endl;
+ } else {
+ std::cerr << "InkFileExportCmd::do_export: Unknown export type: " << type.raw() << ". Allowed values: [";
+ filetypes.sort();
+ filetypes.unique();
+ copy(filetypes.begin(), filetypes.end(), std::ostream_iterator<std::string>(std::cerr, ", "));
+ std::cerr << "\b\b]" << std::endl;
+ }
+ }
+ }
+}
+
+// File names use std::string. HTML5 and presumably SVG 2 allows UTF-8 characters. Do we need to convert "object_id" here?
+std::string
+InkFileExportCmd::get_filename_out(std::string filename_in, std::string object_id)
+{
+ // Pipe out
+ if (export_filename == "-") {
+ return "-";
+ }
+
+ auto const export_type_current_native = Glib::filename_from_utf8(export_type_current);
+
+ // Use filename provided with --export-filename if given
+ if (!export_filename.empty()) {
+ auto ext = Inkscape::IO::get_file_extension(export_filename);
+ auto cmp = "." + export_type_current_native;
+ return export_filename + (ext == cmp ? "" : cmp);
+ }
+
+ // Check for pipe
+ if (filename_in == "-") {
+ return "-";
+ }
+
+ // Construct output filename from input filename and export_type.
+ auto extension_pos = filename_in.find_last_of('.');
+ if (extension_pos == std::string::npos) {
+ std::cerr << "InkFileExportCmd::get_filename_out: cannot determine input file type from filename extension: " << filename_in << std::endl;
+ return (std::string());
+ }
+
+ std::string extension = filename_in.substr(extension_pos+1);
+ if (export_overwrite && export_type_current_native == extension) {
+ return filename_in;
+ } else {
+ std::string tag;
+ if (export_type_current_native == extension) {
+ tag = "_out";
+ }
+ if (!object_id.empty()) {
+ tag = "_" + object_id;
+ }
+ return filename_in.substr(0, extension_pos) + tag + "." + export_type_current_native;
+ }
+}
+
+/**
+ * Perform an SVG export
+ *
+ * \param doc Document to export.
+ * \param export_filename Filename for export
+ */
+int InkFileExportCmd::do_export_svg(SPDocument *doc, std::string const &export_filename)
+{
+ Inkscape::Extension::Output *oext;
+ if (export_plain_svg) {
+ oext =
+ dynamic_cast<Inkscape::Extension::Output *>(Inkscape::Extension::db.get("org.inkscape.output.svg.plain"));
+ } else {
+ oext = dynamic_cast<Inkscape::Extension::Output *>(
+ Inkscape::Extension::db.get("org.inkscape.output.svg.inkscape"));
+ }
+ return do_export_vector(doc, export_filename, *oext);
+}
+/**
+ * Perform a vector file export (SVG, PDF, or PS)
+ *
+ * \param doc Document to export.
+ * \param export_filename Filename for export
+ * \param extension Output extension used for exporting
+ */
+int InkFileExportCmd::do_export_vector(SPDocument *doc, std::string const &export_filename,
+ Inkscape::Extension::Output &extension)
+{
+ // Start with options that are once per document.
+ if (export_text_to_path) {
+ Inkscape::convert_text_to_curves(doc);
+ }
+
+ if (export_margin != 0) {
+ gdouble margin = export_margin;
+ doc->ensureUpToDate();
+ SPNamedView *nv;
+ Inkscape::XML::Node *nv_repr;
+ if ((nv = doc->getNamedView()) && (nv_repr = nv->getRepr())) {
+ nv_repr->setAttributeSvgDouble("fit-margin-top", margin);
+ nv_repr->setAttributeSvgDouble("fit-margin-left", margin);
+ nv_repr->setAttributeSvgDouble("fit-margin-right", margin);
+ nv_repr->setAttributeSvgDouble("fit-margin-bottom", margin);
+ }
+ }
+
+ if (export_area_type == ExportAreaType::Drawing) {
+ fit_canvas_to_drawing(doc, export_margin != 0 ? true : false);
+ } else if (export_area_type == ExportAreaType::Page || export_id.empty()) {
+ if (export_margin) {
+ doc->ensureUpToDate();
+ doc->fitToRect(*(doc->preferredBounds()), export_margin);
+ }
+ }
+
+ // Export pages instead of objects
+ if (!export_page.empty()) {
+ std::string base = export_filename;
+ std::string ext = "svg";
+ // Strip any possible extension
+ std::string tmp_out = get_filename_out(export_filename, "");
+ auto extension_pos = tmp_out.find_last_of('.');
+ if (extension_pos != std::string::npos) {
+ base = tmp_out.substr(0, extension_pos);
+ ext = tmp_out.substr(extension_pos+1);
+ }
+
+ auto pages = Inkscape::parseIntRange(export_page);
+ for (auto page_num : pages) {
+ // And if only one page is selected then we assume the user knows the filename they intended.
+ std::string filename_out = base + (pages.size() > 1 ? "_p" + std::to_string(page_num) : "") + "." + ext;
+
+ auto copy_doc = doc->copy();
+ copy_doc->prunePages(std::to_string(page_num), true);
+ copy_doc->ensureUpToDate();
+ copy_doc->vacuumDocument();
+
+ try {
+ extension.set_gui(false);
+ Inkscape::Extension::save(dynamic_cast<Inkscape::Extension::Extension *>(&extension), copy_doc.get(),
+ filename_out.c_str(), false, false, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY);
+ } catch (Inkscape::Extension::Output::save_failed const &) {
+ std::cerr << "InkFileExportCmd::do_export_vector: Failed to save " << (export_plain_svg ? "" : "Inkscape")
+ << " file to: " << filename_out << std::endl;
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ // Export each object in list (or root if empty). Use ';' so in future it could be possible to selected multiple objects to export together.
+ std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
+ if (objects.empty()) {
+ objects.emplace_back(); // So we do loop at least once for root.
+ }
+
+ for (auto object : objects) {
+ auto copy_doc = doc->copy();
+
+ std::string filename_out = get_filename_out(export_filename, Glib::filename_from_utf8(object));
+ if (filename_out.empty()) {
+ return 1;
+ }
+
+ if(!object.empty()) {
+ copy_doc->ensureUpToDate();
+
+ // "crop" the document to the specified object, cleaning as we go.
+ SPObject *obj = copy_doc->getObjectById(object);
+ if (obj == nullptr) {
+ std::cerr << "InkFileExportCmd::do_export_vector: Object " << object.raw() << " not found in document, nothing to export." << std::endl;
+ return 1;
+ }
+ if (export_id_only) {
+ // If -j then remove all other objects to complete the "crop"
+ copy_doc->getRoot()->cropToObject(obj);
+ }
+ if (export_area_type != ExportAreaType::Drawing && export_area_type != ExportAreaType::Page) {
+ Inkscape::ObjectSet s(copy_doc.get());
+ s.set(obj);
+ s.fitCanvas((bool)export_margin);
+ }
+ }
+ try {
+ extension.set_gui(false);
+ Inkscape::Extension::save(dynamic_cast<Inkscape::Extension::Extension *>(&extension), copy_doc.get(),
+ filename_out.c_str(), false, false,
+ export_plain_svg ? Inkscape::Extension::FILE_SAVE_METHOD_SAVE_COPY
+ : Inkscape::Extension::FILE_SAVE_METHOD_INKSCAPE_SVG);
+ } catch (Inkscape::Extension::Output::save_failed &e) {
+ std::cerr << "InkFileExportCmd::do_export_vector: Failed to save " << (export_plain_svg ? "" : "Inkscape")
+ << " file to: " << filename_out << std::endl;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+guint32 InkFileExportCmd::get_bgcolor(SPDocument *doc) {
+ guint32 bgcolor = 0x00000000;
+ if (!export_background.empty()) {
+ // override the page color
+ bgcolor = sp_svg_read_color(export_background.c_str(), 0xffffff00);
+ // default is opaque if a color is given on commandline
+ if (export_background_opacity < -.5 ) {
+ export_background_opacity = 255;
+ }
+ } else {
+ // read from namedview
+ Inkscape::XML::Node *nv = doc->getReprNamedView();
+ if (nv && nv->attribute("pagecolor")){
+ bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00);
+ }
+ }
+
+ if (export_background_opacity > -.5) { // if the value is manually set
+ if (export_background_opacity > 1.0) {
+ float value = CLAMP (export_background_opacity, 1.0f, 255.0f);
+ bgcolor |= (guint32) floor(value);
+ } else {
+ float value = CLAMP (export_background_opacity, 0.0f, 1.0f);
+ bgcolor |= SP_COLOR_F_TO_U(value);
+ }
+ } else {
+ Inkscape::XML::Node *nv = doc->getReprNamedView();
+ if (nv && nv->attribute("inkscape:pageopacity")){
+ double opacity = nv->getAttributeDouble("inkscape:pageopacity", 1.0);
+ bgcolor |= SP_COLOR_F_TO_U(opacity);
+ } // else it's transparent
+ }
+ return bgcolor;
+}
+
+/**
+ * Perform a PNG export
+ *
+ * \param doc Document to export.
+ * \param export_filename filename for export
+ */
+int
+InkFileExportCmd::do_export_png(SPDocument *doc, std::string const &export_filename)
+{
+ bool filename_from_hint = false;
+ gdouble dpi = 0.0;
+
+ auto prefs = Inkscape::Preferences::get();
+ bool old_dither = prefs->getBool("/options/dithering/value", true);
+ prefs->setBool("/options/dithering/value", export_png_use_dithering);
+
+ // Export each object in list (or root if empty). Use ';' so in future it could be possible to selected multiple objects to export together.
+ std::vector<Glib::ustring> objects = Glib::Regex::split_simple("\\s*;\\s*", export_id);
+
+ std::vector<SPItem*> items;
+ std::vector<Glib::ustring> objects_found;
+ for (auto object_id : objects) {
+ // Find export object. (Either root or object with specified id.)
+ auto object = doc->getObjectById(object_id);
+
+ if (!object) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Object with id=\"" << object_id.raw()
+ << "\" was not found in the document. Skipping." << std::endl;
+ continue;
+ }
+
+ if (!is<SPItem>(object)) {
+ std::cerr << "InkFileExportCmd::do_export_png: "
+ << "Object with id=\"" << object_id.raw()
+ << "\" is not a visible item. Skipping." << std::endl;
+ continue;
+ }
+
+ items.push_back(cast<SPItem>(object));
+ objects_found.push_back(object_id);
+ }
+
+ // Export pages instead of objects
+ if (!export_page.empty()) {
+ auto &pm = doc->getPageManager();
+ std::string base = export_filename;
+ // Strip any possible extension
+ auto extension_pos = export_filename.find_last_of('.');
+ if (extension_pos != std::string::npos)
+ base = export_filename.substr(0, extension_pos);
+
+ auto pages = Inkscape::parseIntRange(export_page);
+ for (auto page_num : pages) {
+ // We always use the png extension and ignore the extension given by the user
+ // And if only one page is selected then we assume the user knows the filename they intended.
+ std::string filename_out = base + (pages.size() > 1 ? "_p" + std::to_string(page_num) : "") + ".png";
+ if (auto page = pm.getPage(page_num - 1)) {
+ do_export_png_now(doc, filename_out, page->getDesktopRect(), dpi, items);
+ }
+ }
+ return 0;
+ }
+
+ if (objects.empty()) {
+ objects_found.emplace_back(); // So we do loop at least once for root.
+ }
+
+ for (auto object_id : objects_found) {
+ SPObject *object = doc->getRoot();
+ if (!object_id.empty()) {
+ object = doc->getObjectById(object_id);
+ }
+
+ std::string filename_out = get_filename_out(export_filename, Glib::filename_from_utf8(object_id));
+
+ if (export_id_only) {
+ std::cerr << "Exporting only object with id=\""
+ << object_id.raw() << "\"; all other objects hidden." << std::endl;
+ }
+
+ // Find file name and dpi from hints.
+ if (export_use_hints) {
+
+ // Retrieve export filename hint.
+ const gchar *fn_hint = object->getRepr()->attribute("inkscape:export-filename");
+ if (fn_hint) {
+ filename_out = fn_hint;
+ filename_from_hint = true;
+ } else {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export filename hint not found for object " << object_id.raw() << ". Skipping." << std::endl;
+ continue;
+ }
+
+ // Retrieve export dpi hint. Only xdpi as ydpi is always the same now.
+ const gchar *dpi_hint = object->getRepr()->attribute("inkscape:export-xdpi");
+ if (dpi_hint) {
+ if (export_dpi || export_width || export_height) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Using bitmap dimensions from the command line "
+ << "(--export-dpi, --export-width, or --export-height). "
+ << "DPI hint " << dpi_hint << " is ignored." << std::endl;
+ } else {
+ dpi = g_ascii_strtod(dpi_hint, nullptr);
+ }
+ } else {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export DPI hint not found for the object." << std::endl;
+ }
+ }
+
+ // ------------------------- File name -------------------------
+
+ // Check we have a filename.
+ if (filename_out.empty()) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "No valid export filename given and no filename hint. Skipping." << std::endl;
+ continue;
+ }
+
+ if (filename_from_hint) {
+ //Make relative paths go from the document location, if possible:
+ if (!Glib::path_is_absolute(filename_out) && doc->getDocumentFilename()) {
+ std::string dirname = Glib::path_get_dirname(doc->getDocumentFilename());
+ if (!dirname.empty()) {
+ filename_out = Glib::build_filename(dirname, filename_out);
+ }
+ }
+ }
+
+ // Check if directory exists
+ std::string directory = Glib::path_get_dirname(filename_out);
+ if (!Glib::file_test(directory, Glib::FILE_TEST_IS_DIR)) {
+ std::cerr << "File path " << filename_out << " includes directory that doesn't exist. Skipping." << std::endl;
+ continue;
+ }
+
+ // ------------------------- Area -------------------------------
+
+ Geom::Rect area;
+ doc->ensureUpToDate();
+
+ if (export_area_type == ExportAreaType::Unset) {
+ // Default to drawing if has object, otherwise export page
+ if (object_id.empty()) {
+ export_area_type = ExportAreaType::Page;
+ } else {
+ export_area_type = ExportAreaType::Drawing;
+ }
+ }
+ // Three choices: 1. Command-line export_area 2. Page area 3. Drawing area
+ switch (export_area_type) {
+ case ExportAreaType::Unset:
+ std::cerr << "ExportAreaType::not_set should be handled before" << std::endl;
+ return 1;
+ case ExportAreaType::Page: {
+ // Export area page (explicit or if no object is given).
+ Geom::Point origin(doc->getRoot()->x.computed, doc->getRoot()->y.computed);
+ area = Geom::Rect(origin, origin + doc->getDimensions());
+ break;
+ }
+ case ExportAreaType::Area: {
+ // Export area command-line
+
+ /* Try to parse area (given in SVG pixels) */
+ gdouble x0, y0, x1, y1;
+ if (sscanf(export_area.c_str(), "%lg:%lg:%lg:%lg", &x0, &y0, &x1, &y1) != 4) {
+ g_warning("Cannot parse export area '%s'; use 'x0:y0:x1:y1'. Nothing exported.",
+ export_area.c_str());
+ return 1; // If it fails once, it will fail for all objects.
+ }
+ area = Geom::Rect(Geom::Interval(x0, x1), Geom::Interval(y0, y1));
+ break;
+ }
+ case ExportAreaType::Drawing: {
+ // Export area drawing (explicit or if object is given).
+ Geom::OptRect areaMaybe = static_cast<SPItem *>(object)->documentVisualBounds();
+ if (areaMaybe) {
+ area = *areaMaybe;
+ } else {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Unable to determine a valid bounding box. Skipping." << std::endl;
+ continue;
+ }
+ break;
+ }
+ }
+
+ if (export_area_snap) {
+ area = area.roundOutwards();
+ }
+ // End finding area.
+ do_export_png_now(doc, filename_out, area, dpi, items);
+
+ } // End loop over objects.
+ prefs->setBool("/options/dithering/value", old_dither);
+ return 0;
+}
+
+void
+InkFileExportCmd::do_export_png_now(SPDocument *doc, std::string const &filename_out, Geom::Rect area, double dpi_in, const std::vector<SPItem *> &items)
+{
+ // -------------------------- DPI -------------------------------
+
+ double dpi = dpi_in;
+
+ if (export_dpi != 0.0 && dpi == 0.0) {
+ dpi = export_dpi;
+ if ((dpi < 0.1) || (dpi > 10000.0)) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "DPI value " << export_dpi
+ << " out of range [0.1 - 10000.0]. Skipping.";
+ return;
+ }
+ }
+
+ // default dpi
+ if (dpi == 0.0) {
+ dpi = Inkscape::Util::Quantity::convert(1, "in", "px");
+ }
+
+ // -------------------------- Width and Height ---------------------------------
+
+ unsigned long int width = 0;
+ unsigned long int height = 0;
+ double xdpi = dpi;
+ double ydpi = dpi;
+
+ if (export_height != 0) {
+ height = export_height;
+ if ((height < 1) || (height > PNG_UINT_31_MAX)) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export height " << height << " out of range (1 to " << PNG_UINT_31_MAX << ")" << std::endl;
+ return;
+ }
+ ydpi = Inkscape::Util::Quantity::convert(height, "in", "px") / area.height();
+ xdpi = ydpi;
+ dpi = ydpi;
+ }
+
+ if (export_width != 0) {
+ width = export_width;
+ if ((width < 1) || (width > PNG_UINT_31_MAX)) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Export width " << width << " out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
+ return;
+ }
+ xdpi = Inkscape::Util::Quantity::convert(width, "in", "px") / area.width();
+ ydpi = export_height ? ydpi : xdpi;
+ dpi = xdpi;
+ }
+
+ if (width == 0) {
+ width = (unsigned long int) (Inkscape::Util::Quantity::convert(area.width(), "px", "in") * dpi + 0.5);
+ }
+
+ if (height == 0) {
+ height = (unsigned long int) (Inkscape::Util::Quantity::convert(area.height(), "px", "in") * dpi + 0.5);
+ }
+
+ if ((width < 1) || (height < 1) || (width > PNG_UINT_31_MAX) || (height > PNG_UINT_31_MAX)) {
+ std::cerr << "InkFileExport::do_export_png: Dimensions " << width << "x" << height << " are out of range (1 to " << PNG_UINT_31_MAX << ")." << std::endl;
+ return;
+ }
+
+ // -------------------------- Bit Depth and Color Type --------------------
+
+ int bit_depth = 8; // default of sp_export_png_file function
+ int color_type = PNG_COLOR_TYPE_RGB_ALPHA; // default of sp_export_png_file function
+
+ if (!export_png_color_mode.empty()) {
+ // data as in ui/dialog/export.cpp:
+ const std::map<std::string, std::pair<int, int>> color_modes = {
+ {"Gray_1", {PNG_COLOR_TYPE_GRAY, 1}},
+ {"Gray_2", {PNG_COLOR_TYPE_GRAY, 2}},
+ {"Gray_4", {PNG_COLOR_TYPE_GRAY, 4}},
+ {"Gray_8", {PNG_COLOR_TYPE_GRAY, 8}},
+ {"Gray_16", {PNG_COLOR_TYPE_GRAY, 16}},
+ {"RGB_8", {PNG_COLOR_TYPE_RGB, 8}},
+ {"RGB_16", {PNG_COLOR_TYPE_RGB, 16}},
+ {"GrayAlpha_8", {PNG_COLOR_TYPE_GRAY_ALPHA, 8}},
+ {"GrayAlpha_16", {PNG_COLOR_TYPE_GRAY_ALPHA, 16}},
+ {"RGBA_8", {PNG_COLOR_TYPE_RGB_ALPHA, 8}},
+ {"RGBA_16", {PNG_COLOR_TYPE_RGB_ALPHA, 16}},
+ };
+ auto it = color_modes.find(export_png_color_mode);
+ if (it == color_modes.end()) {
+ std::cerr << "InkFileExport::do_export_png: "
+ << "Color mode " << export_png_color_mode.raw() << " is invalid. It must be one of Gray_1/Gray_2/Gray_4/Gray_8/Gray_16/RGB_8/RGB_16/GrayAlpha_8/GrayAlpha_16/RGBA_8/RGBA_16." << std::endl;
+ return;
+ } else {
+ std::tie(color_type, bit_depth) = it->second;
+ }
+ }
+
+ guint32 bgcolor = get_bgcolor(doc);
+ // ---------------------- Generate the PNG -------------------------------
+#ifdef DEBUG
+ std::cerr << "Background RRGGBBAA: " << std::hex << bgcolor << std::dec << std::endl;
+ std::cerr << "Area "
+ << area[Geom::X][0] << ":" << area[Geom::Y][0] << ":"
+ << area[Geom::X][1] << ":" << area[Geom::Y][1] << " exported to "
+ << width << " x " << height << " pixels (" << dpi << " dpi)" << std::endl;
+#endif
+
+ if( sp_export_png_file(doc, filename_out.c_str(), area, width, height, xdpi, ydpi,
+ bgcolor, nullptr, nullptr, true, export_id_only ? items : std::vector<SPItem*>(),
+ false, color_type, bit_depth) == 1 ) {
+ } else {
+ std::cerr << "InkFileExport::do_export_png: Failed to export to " << filename_out << std::endl;
+ }
+}
+
+
+/**
+ * Perform a PDF/PS/EPS export
+ *
+ * \param doc Document to export.
+ * \param filename File to write to.
+ * \param mime MIME type to export as.
+ */
+int
+InkFileExportCmd::do_export_ps_pdf(SPDocument* doc, std::string const &filename_in, std::string const & mime_type)
+{
+ // Check if we support mime type.
+ Inkscape::Extension::DB::OutputList o;
+ Inkscape::Extension::db.get_output_list(o);
+ Inkscape::Extension::DB::OutputList::const_iterator i = o.begin();
+ while (i != o.end() && strcmp( (*i)->get_mimetype(), mime_type.c_str() ) != 0) {
+ ++i;
+ }
+
+ if (i == o.end()) {
+ std::cerr << "InkFileExportCmd::do_export_ps_pdf: Could not find an extension to export to MIME type: " << mime_type << std::endl;
+ return 1;
+ }
+ return do_export_ps_pdf(doc, filename_in, mime_type, *dynamic_cast<Inkscape::Extension::Output *>(*i));
+}
+
+/**
+ * Perform a PDF/PS/EPS export
+ *
+ * \param doc Document to export.
+ * \param filename File to write to.
+ * \param mime MIME type to export as.
+ * \param Extension used for exporting
+ */
+int InkFileExportCmd::do_export_ps_pdf(SPDocument *doc, std::string const &filename_in, std::string const & mime_type,
+ Inkscape::Extension::Output &extension)
+{
+ // check if the passed extension conforms to the mime type.
+ assert(std::string(extension.get_mimetype()) == mime_type);
+ // Start with options that are once per document.
+
+ // Set export options.
+ if (export_text_to_path) {
+ extension.set_param_optiongroup("textToPath", "paths");
+ } else if (export_latex) {
+ extension.set_param_optiongroup("textToPath", "LaTeX");
+ } else {
+ extension.set_param_optiongroup("textToPath", "embed");
+ }
+
+ if (export_ignore_filters) {
+ extension.set_param_bool("blurToBitmap", false);
+ } else {
+ extension.set_param_bool("blurToBitmap", true);
+
+ gdouble dpi = 96.0;
+ if (export_dpi) {
+ dpi = export_dpi;
+ if ((dpi < 1) || (dpi > 10000.0)) {
+ g_warning("DPI value %lf out of range [1 - 10000]. Using 96 dpi instead.", export_dpi);
+ dpi = 96;
+ }
+ }
+
+ extension.set_param_int("resolution", (int)dpi);
+ }
+
+ // handle --export-pdf-version
+ if (mime_type == "application/pdf") {
+ bool set_export_pdf_version_fail = true;
+ const gchar *pdfver_param_name = "PDFversion";
+ if (!export_pdf_level.empty()) {
+ // combine "PDF " and the given command line
+ std::string version_gui_string = std::string("PDF-") + export_pdf_level.raw();
+ try {
+ // first, check if the given pdf version is selectable in the ComboBox
+ if (extension.get_param_optiongroup_contains("PDFversion", version_gui_string.c_str())) {
+ extension.set_param_optiongroup(pdfver_param_name, version_gui_string.c_str());
+ set_export_pdf_version_fail = false;
+ } else {
+ g_warning("Desired PDF export version \"%s\" not supported! Hint: input one of the versions found in the pdf export dialog e.g. \"1.4\".",
+ export_pdf_level.c_str());
+ }
+ } catch (...) {
+ // can be thrown along the way:
+ // throw Extension::param_not_exist();
+ // throw Extension::param_not_enum_param();
+ g_warning("Parameter or Enum \"%s\" might not exist", pdfver_param_name);
+ }
+ }
+
+ // set default pdf export version to 1.4, also if something went wrong
+ if(set_export_pdf_version_fail) {
+ extension.set_param_optiongroup(pdfver_param_name, "PDF-1.4");
+ }
+ }
+
+ if (mime_type == "image/x-postscript" || mime_type == "image/x-e-postscript") {
+ if ( export_ps_level < 2 || export_ps_level > 3 ) {
+ g_warning("Only supported PostScript levels are 2 and 3."
+ " Defaulting to 2.");
+ export_ps_level = 2;
+ }
+
+ extension.set_param_optiongroup("PSlevel", (export_ps_level == 3) ? "PS3" : "PS2");
+ }
+
+ return do_export_vector(doc, filename_in, extension);
+}
+
+/**
+ * Export a document using an export extension
+ *
+ * \param doc Document to export.
+ * \param filename to export to.
+ * \param output extension used for export
+ */
+int InkFileExportCmd::do_export_extension(SPDocument *doc, std::string const &filename_in,
+ Inkscape::Extension::Output *extension)
+{
+ std::string filename_out = get_filename_out(filename_in);
+ if (extension) {
+ extension->set_state(Inkscape::Extension::Extension::STATE_LOADED);
+ try {
+ extension->set_gui(false);
+ extension->save(doc, filename_out.c_str());
+ } catch (Inkscape::Extension::Output::save_failed const &) {
+
+ std::cerr << __PRETTY_FUNCTION__ << ": Failed to save " << extension->get_id()
+ << " to: " << filename_out << std::endl;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+std::string export_area_type_string(ExportAreaType type)
+{
+ switch (type) {
+ case ExportAreaType::Area:
+ return "--export-area";
+ case ExportAreaType::Page:
+ return "--export-area-page";
+ case ExportAreaType::Drawing:
+ return "--export-area-drawing";
+ default:
+ return "default";
+ }
+}
+
+void InkFileExportCmd::set_export_area_type(ExportAreaType type)
+{
+ if ((export_area_type != ExportAreaType::Unset) && (export_area_type != type)) {
+ std::cerr << "Warning: multiple export area types have been set, overriding "
+ << export_area_type_string(export_area_type) << " with " << export_area_type_string(type)
+ << std::endl;
+ }
+ export_area_type = type;
+}
+
+void InkFileExportCmd::set_export_area(const Glib::ustring &area)
+{
+ export_area = area;
+ set_export_area_type(ExportAreaType::Area);
+}
+
+/*
+ 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 :
diff --git a/src/io/file-export-cmd.h b/src/io/file-export-cmd.h
new file mode 100644
index 0000000..3b34a2a
--- /dev/null
+++ b/src/io/file-export-cmd.h
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File export from the command line. This code use to be in main.cpp. It should be
+ * replaced by shared code (Gio::Actions?) for export from the file dialog.
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_FILE_EXPORT_CMD_H
+#define INK_FILE_EXPORT_CMD_H
+
+#include <iostream>
+#include <glibmm.h>
+#include "2geom/rect.h"
+
+class SPDocument;
+class SPItem;
+namespace Inkscape {
+namespace Extension {
+class Output;
+}
+} // namespace Inkscape
+
+enum class ExportAreaType
+{
+ Unset,
+ Drawing,
+ Page,
+ Area,
+};
+
+class InkFileExportCmd {
+
+public:
+ InkFileExportCmd();
+
+ void do_export(SPDocument* doc, std::string filename_in="");
+
+private:
+ ExportAreaType export_area_type{ExportAreaType::Unset};
+ Glib::ustring export_area{};
+ guint32 get_bgcolor(SPDocument *doc);
+ std::string get_filename_out(std::string filename_in = "", std::string object_id = "");
+ int do_export_svg(SPDocument *doc, std::string const &filename_in);
+ int do_export_vector(SPDocument *doc, std::string const &filename_in, Inkscape::Extension::Output &extension);
+ int do_export_png(SPDocument *doc, std::string const &filename_in);
+ int do_export_ps_pdf(SPDocument *doc, std::string const &filename_in, std::string const &mime_type);
+ int do_export_ps_pdf(SPDocument *doc, std::string const &filename_in, std::string const &mime_type,
+ Inkscape::Extension::Output &extension);
+ int do_export_extension(SPDocument *doc, std::string const &filename_in, Inkscape::Extension::Output *extension);
+ Glib::ustring export_type_current;
+
+ void do_export_png_now(SPDocument *doc, std::string const &filename_out, Geom::Rect area, double dpi_in, const std::vector<SPItem *> &items);
+public:
+ // Should be private, but this is just temporary code (I hope!).
+
+ // One-to-one correspondence with command line options
+ std::string export_filename; // Only if one file is processed!
+
+ Glib::ustring export_type;
+ Glib::ustring export_extension;
+ bool export_overwrite;
+
+ int export_margin;
+ bool export_area_snap;
+ int export_width;
+ int export_height;
+
+ Glib::ustring export_page;
+
+ double export_dpi;
+ bool export_ignore_filters;
+ bool export_text_to_path;
+ int export_ps_level;
+ Glib::ustring export_pdf_level;
+ bool export_latex;
+ Glib::ustring export_id;
+ bool export_id_only;
+ bool export_use_hints;
+ Glib::ustring export_background;
+ double export_background_opacity;
+ Glib::ustring export_png_color_mode;
+ bool export_plain_svg;
+ bool export_png_use_dithering;
+ void set_export_area(const Glib::ustring &area);
+ void set_export_area_type(ExportAreaType type);
+};
+
+#endif // INK_FILE_EXPORT_CMD_H
+
+/*
+ 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 :
diff --git a/src/io/file.cpp b/src/io/file.cpp
new file mode 100644
index 0000000..4c509dc
--- /dev/null
+++ b/src/io/file.cpp
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File operations (independent of GUI)
+ *
+ * Copyright (C) 2018, 2019 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#include "io/file.h"
+
+#include <iostream>
+#include <unistd.h>
+#include <giomm.h>
+
+#include "document.h"
+#include "document-undo.h"
+
+#include "extension/system.h" // Extension::open()
+#include "extension/extension.h"
+#include "extension/db.h"
+#include "extension/output.h"
+#include "extension/input.h"
+
+#include "object/sp-root.h"
+
+#include "xml/repr.h"
+
+
+/**
+ * Create a blank document, remove any template data.
+ * Input: Empty string or template file name.
+ */
+SPDocument*
+ink_file_new(const std::string &Template)
+{
+ SPDocument *doc = SPDocument::createNewDoc ((Template.empty() ? nullptr : Template.c_str()), true, true );
+
+ if (doc) {
+ // Remove all the template info from xml tree
+ Inkscape::XML::Node *myRoot = doc->getReprRoot();
+ Inkscape::XML::Node *nodeToRemove;
+
+ nodeToRemove = sp_repr_lookup_name(myRoot, "inkscape:templateinfo");
+ if (nodeToRemove != nullptr) {
+ Inkscape::DocumentUndo::ScopedInsensitive no_undo(doc);
+ sp_repr_unparent(nodeToRemove);
+ delete nodeToRemove;
+ }
+ nodeToRemove = sp_repr_lookup_name(myRoot, "inkscape:_templateinfo"); // backwards-compatibility
+ if (nodeToRemove != nullptr) {
+ Inkscape::DocumentUndo::ScopedInsensitive no_undo(doc);
+ sp_repr_unparent(nodeToRemove);
+ delete nodeToRemove;
+ }
+ } else {
+ std::cerr << "ink_file_new: Did not create new document!" << std::endl;
+ }
+
+ return doc;
+}
+
+/**
+ * Open a document from memory.
+ */
+SPDocument*
+ink_file_open(const Glib::ustring& data)
+{
+ SPDocument *doc = SPDocument::createNewDocFromMem (data.c_str(), data.length(), true);
+
+ if (doc == nullptr) {
+ std::cerr << "ink_file_open: cannot open file in memory (pipe?)" << std::endl;
+ } else {
+
+ // This is the only place original values should be set.
+ SPRoot *root = doc->getRoot();
+ root->original.inkscape = root->version.inkscape;
+ root->original.svg = root->version.svg;
+ }
+
+ return doc;
+}
+
+/**
+ * Open a document.
+ */
+SPDocument*
+ink_file_open(const Glib::RefPtr<Gio::File>& file, bool *cancelled_param)
+{
+ bool cancelled = false;
+
+ SPDocument *doc = nullptr;
+
+ std::string path = file->get_path();
+
+ // TODO: It's useless to catch these exceptions here (and below) unless we do something with them.
+ // If we can't properly handle them (e.g. by showing a user-visible message) don't catch them!
+ try {
+ doc = Inkscape::Extension::open(nullptr, path.c_str());
+ } catch (Inkscape::Extension::Input::no_extension_found &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_failed &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_cancelled &e) {
+ cancelled = true;
+ doc = nullptr;
+ }
+
+ // Try to open explicitly as SVG.
+ // TODO: Why is this necessary? Shouldn't this be handled by the first call already?
+ if (doc == nullptr && !cancelled) {
+ try {
+ doc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), path.c_str());
+ } catch (Inkscape::Extension::Input::no_extension_found &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_failed &e) {
+ doc = nullptr;
+ } catch (Inkscape::Extension::Input::open_cancelled &e) {
+ cancelled = true;
+ doc = nullptr;
+ }
+ }
+
+ if (doc != nullptr) {
+ // This is the only place original values should be set.
+ SPRoot *root = doc->getRoot();
+ root->original.inkscape = root->version.inkscape;
+ root->original.svg = root->version.svg;
+ } else if (!cancelled) {
+ std::cerr << "ink_file_open: '" << path << "' cannot be opened!" << std::endl;
+ }
+
+ if (cancelled_param) {
+ *cancelled_param = cancelled;
+ }
+ return doc;
+}
+
+namespace Inkscape {
+namespace IO {
+
+/**
+ * Create a temporary filename, which is closed and deleted when deconstructed.
+ */
+TempFilename::TempFilename(const std::string &pattern)
+ : _filename("")
+ , _tempfd(0)
+{
+ try {
+ _tempfd = Glib::file_open_tmp(_filename, pattern.c_str());
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+}
+
+TempFilename::~TempFilename()
+{
+ close(_tempfd);
+ unlink(_filename.c_str());
+}
+
+/**
+ * Takes an absolute file path and returns a second file at the same
+ * directory location, if and only if the filename exists and is a file.
+ *
+ * Returns the empty string if the new file is not found.
+ */
+Glib::ustring find_original_file(Glib::ustring filepath, Glib::ustring name)
+{
+ auto path = Glib::path_get_dirname(filepath);
+ auto filename = Glib::build_filename(path, name);
+
+ if (Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR)) {
+ return filename;
+ }
+ return "";
+}
+
+}}
+
+/*
+ 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 :
diff --git a/src/io/file.h b/src/io/file.h
new file mode 100644
index 0000000..82aa747
--- /dev/null
+++ b/src/io/file.h
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * File operations (independent of GUI)
+ *
+ * Copyright (C) 2018 Tavmjong Bah
+ *
+ * The contents of this file may be used under the GNU General Public License Version 2 or later.
+ *
+ */
+
+#ifndef INK_FILE_IO_H
+#define INK_FILE_IO_H
+
+#include <string>
+
+namespace Gio {
+class File;
+}
+
+namespace Glib {
+class ustring;
+
+template <class T>
+class RefPtr;
+}
+
+class SPDocument;
+
+SPDocument* ink_file_new(const std::string &Template = "");
+SPDocument* ink_file_open(const Glib::ustring &data);
+SPDocument* ink_file_open(const Glib::RefPtr<Gio::File>& file, bool *cancelled = nullptr);
+
+namespace Inkscape {
+namespace IO {
+
+class TempFilename {
+ public:
+ TempFilename(const std::string &pattern);
+ ~TempFilename();
+
+ std::string get_filename() const { return _filename; }
+ private:
+ std::string _filename;
+ int _tempfd;
+};
+
+Glib::ustring find_original_file(Glib::ustring filepath, Glib::ustring name);
+
+}}
+
+// To do:
+// ink_file_save()
+// ink_file_export()
+// ink_file_import()
+
+
+
+#endif // INK_FILE_IO_H
diff --git a/src/io/fix-broken-links.cpp b/src/io/fix-broken-links.cpp
new file mode 100644
index 0000000..0abf837
--- /dev/null
+++ b/src/io/fix-broken-links.cpp
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * tracks external resources such as image and css files.
+ *
+ * Copyright 2011 Jon A. Cruz <jon@joncruz.org>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+#include <vector>
+#include <set>
+#include <algorithm>
+
+#include <gtkmm/recentmanager.h>
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/uriutils.h>
+#include <glibmm/convert.h>
+
+#include "fix-broken-links.h"
+
+#include "document.h"
+#include "document-undo.h"
+
+#include "object/sp-object.h"
+
+#include "ui/icon-names.h"
+
+#include "xml/node.h"
+#include "xml/href-attribute-helper.h"
+
+namespace Inkscape {
+
+static std::vector<std::string> splitPath( std::string const &path )
+{
+ std::vector<std::string> parts;
+
+ std::string prior;
+ std::string tmp = path;
+ while ( !tmp.empty() && (tmp != prior) ) {
+ prior = tmp;
+
+ parts.push_back( Glib::path_get_basename(tmp) );
+ tmp = Glib::path_get_dirname(tmp);
+ }
+ if ( !parts.empty() ) {
+ std::reverse(parts.begin(), parts.end());
+ if ( (parts[0] == ".") && (path[0] != '.') ) {
+ parts.erase(parts.begin());
+ }
+ }
+
+ return parts;
+}
+
+std::string convertPathToRelative( std::string const &path, std::string const &docbase )
+{
+ std::string result = path;
+
+ if ( !path.empty() && Glib::path_is_absolute(path) ) {
+ // Whack the parts into pieces
+
+ std::vector<std::string> parts = splitPath(path);
+ std::vector<std::string> baseParts = splitPath(docbase);
+
+ // TODO debug g_message("+++++++++++++++++++++++++");
+ for ( std::vector<std::string>::iterator it = parts.begin(); it != parts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message(" - - - - - - - - - - - - - - - ");
+ for ( std::vector<std::string>::iterator it = baseParts.begin(); it != baseParts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message("+++++++++++++++++++++++++");
+
+ if ( !parts.empty() && !baseParts.empty() && (parts[0] == baseParts[0]) ) {
+ // Both paths have the same root. We can proceed.
+ while ( !parts.empty() && !baseParts.empty() && (parts[0] == baseParts[0]) ) {
+ parts.erase( parts.begin() );
+ baseParts.erase( baseParts.begin() );
+ }
+
+ // TODO debug g_message("+++++++++++++++++++++++++");
+ for ( std::vector<std::string>::iterator it = parts.begin(); it != parts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message(" - - - - - - - - - - - - - - - ");
+ for ( std::vector<std::string>::iterator it = baseParts.begin(); it != baseParts.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ // TODO debug g_message("+++++++++++++++++++++++++");
+
+ if ( !parts.empty() ) {
+ result.clear();
+
+ for ( size_t i = 0; i < baseParts.size(); ++i ) {
+ parts.insert(parts.begin(), "..");
+ }
+ result = Glib::build_filename( parts );
+ // TODO debug g_message("----> [%s]", result.c_str());
+ }
+ }
+ }
+
+ return result;
+}
+
+
+bool fixBrokenLinks(SPDocument *doc);
+
+
+/**
+ * Walk all links in a document and create a listing of unique broken links.
+ *
+ * @return a list of all broken links.
+ */
+static std::vector<Glib::ustring> findBrokenLinks(SPDocument *doc);
+
+/**
+ * Resolve broken links as a whole and return a map for those that can be found.
+ *
+ * Note: this will allow for future enhancements including relinking to new locations
+ * with the most broken files found, etc.
+ *
+ * @return a map of found links.
+ */
+static std::map<Glib::ustring, Glib::ustring> locateLinks(Glib::ustring const & docbase, std::vector<Glib::ustring> const & brokenLinks);
+
+
+/**
+ * Try to parse href into a local filename using standard methods.
+ *
+ * @return true if successful.
+ */
+static bool extractFilepath(Glib::ustring const &href, std::string &filename);
+
+/**
+ * Try to parse href into a local filename using some non-standard methods.
+ * This means the href is likely invalid and should be rewritten.
+ *
+ * @return true if successful.
+ */
+static bool reconstructFilepath(Glib::ustring const &href, std::string &filename);
+
+static bool searchUpwards( std::string const &base, std::string const &subpath, std::string &dest );
+
+
+
+static bool extractFilepath(Glib::ustring const &href, std::string &filename)
+{
+ bool isFile = false;
+
+ filename.clear();
+
+ auto scheme = Glib::uri_parse_scheme(href.raw());
+ if ( !scheme.empty() ) {
+ // TODO debug g_message("Scheme is now [%s]", scheme.c_str());
+ if ( scheme == "file" ) {
+ // TODO debug g_message("--- is a file URI [%s]", href.c_str());
+
+ // throws Glib::ConvertError:
+ try {
+ filename = Glib::filename_from_uri(href);
+ isFile = true;
+ } catch(Glib::ConvertError e) {
+ g_warning("%s", e.what().c_str());
+ }
+ }
+ } else {
+ // No scheme. Assuming it is a file path (absolute or relative).
+ // throws Glib::ConvertError:
+ filename = Glib::filename_from_utf8(href);
+ isFile = true;
+ }
+
+ return isFile;
+}
+
+static bool reconstructFilepath(Glib::ustring const &href, std::string &filename)
+{
+ bool isFile = false;
+
+ filename.clear();
+
+ auto scheme = Glib::uri_parse_scheme(href.raw());
+ if ( !scheme.empty() ) {
+ if ( scheme == "file" ) {
+ // try to build a relative filename for URIs like "file:image.png"
+ // they're not standard conformant but not uncommon
+ Glib::ustring href_new = Glib::ustring(href, 5);
+ filename = Glib::filename_from_utf8(href_new);
+ isFile = true;
+ }
+ }
+ return isFile;
+}
+
+
+static std::vector<Glib::ustring> findBrokenLinks( SPDocument *doc )
+{
+ std::vector<Glib::ustring> result;
+ std::set<Glib::ustring> uniques;
+
+ if ( doc ) {
+ std::vector<SPObject *> images = doc->getResourceList("image");
+ for (auto image : images) {
+ Inkscape::XML::Node *ir = image->getRepr();
+
+ gchar const *href = Inkscape::getHrefAttribute(*ir).second;
+ if ( href && ( uniques.find(href) == uniques.end() ) ) {
+ std::string filename;
+ if (extractFilepath(href, filename)) {
+ if (Glib::path_is_absolute(filename)) {
+ if (!Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) {
+ result.emplace_back(href);
+ uniques.insert(href);
+ }
+ } else {
+ std::string combined = Glib::build_filename(doc->getDocumentBase(), filename);
+ if ( !Glib::file_test(combined, Glib::FILE_TEST_EXISTS) ) {
+ result.emplace_back(href);
+ uniques.insert(href);
+ }
+ }
+ } else if (reconstructFilepath(href, filename)) {
+ result.emplace_back(href);
+ uniques.insert(href);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+
+static std::map<Glib::ustring, Glib::ustring> locateLinks(Glib::ustring const & docbase, std::vector<Glib::ustring> const & brokenLinks)
+{
+ std::map<Glib::ustring, Glib::ustring> result;
+
+
+ // Note: we use a vector because we want them to stay in order:
+ std::vector<std::string> priorLocations;
+
+ Glib::RefPtr<Gtk::RecentManager> recentMgr = Gtk::RecentManager::get_default();
+ std::vector< Glib::RefPtr<Gtk::RecentInfo> > recentItems = recentMgr->get_items();
+ for (auto & recentItem : recentItems) {
+ Glib::ustring uri = recentItem->get_uri();
+ auto scheme = Glib::uri_parse_scheme(uri.raw());
+ if ( scheme == "file" ) {
+ try {
+ std::string path = Glib::filename_from_uri(uri);
+ path = Glib::path_get_dirname(path);
+ if ( std::find(priorLocations.begin(), priorLocations.end(), path) == priorLocations.end() ) {
+ // TODO debug g_message(" ==>[%s]", path.c_str());
+ priorLocations.push_back(path);
+ }
+ } catch (Glib::ConvertError e) {
+ g_warning("%s", e.what().c_str());
+ }
+ }
+ }
+
+ // At the moment we expect this list to contain file:// references, or simple relative or absolute paths.
+ for (const auto & brokenLink : brokenLinks) {
+ // TODO debug g_message("========{%s}", it->c_str());
+
+ std::string filename;
+ if (extractFilepath(brokenLink, filename) || reconstructFilepath(brokenLink, filename)) {
+ auto const docbase_native = Glib::filename_from_utf8(docbase);
+
+ // We were able to get some path. Check it
+ std::string origPath = filename;
+
+ if (!Glib::path_is_absolute(filename)) {
+ filename = Glib::build_filename(docbase_native, filename);
+ }
+
+ bool exists = Glib::file_test(filename, Glib::FILE_TEST_EXISTS);
+
+ // search in parent folders
+ if (!exists) {
+ exists = searchUpwards(docbase_native, origPath, filename);
+ }
+
+ // Check if the MRU bases point us to it.
+ if ( !exists ) {
+ if ( !Glib::path_is_absolute(origPath) ) {
+ for ( std::vector<std::string>::iterator it = priorLocations.begin(); !exists && (it != priorLocations.end()); ++it ) {
+ exists = searchUpwards(*it, origPath, filename);
+ }
+ }
+ }
+
+ if ( exists ) {
+ if (Glib::path_is_absolute(filename)) {
+ filename = convertPathToRelative(filename, docbase_native);
+ }
+
+ bool isAbsolute = Glib::path_is_absolute(filename);
+ Glib::ustring replacement =
+ isAbsolute ? Glib::filename_to_uri(filename) : Glib::filename_to_utf8(filename);
+ result[brokenLink] = replacement;
+ }
+ }
+ }
+
+ return result;
+}
+
+bool fixBrokenLinks(SPDocument *doc)
+{
+ bool changed = false;
+ if ( doc ) {
+ // TODO debug g_message("FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP FIXUP");
+ // TODO debug g_message(" base is [%s]", doc->getDocumentBase());
+
+ std::vector<Glib::ustring> brokenHrefs = findBrokenLinks(doc);
+ if ( !brokenHrefs.empty() ) {
+ // TODO debug g_message(" FOUND SOME LINKS %d", static_cast<int>(brokenHrefs.size()));
+ for ( std::vector<Glib::ustring>::iterator it = brokenHrefs.begin(); it != brokenHrefs.end(); ++it ) {
+ // TODO debug g_message(" [%s]", it->c_str());
+ }
+ }
+
+ Glib::ustring base;
+ if (doc->getDocumentBase()) {
+ base = doc->getDocumentBase();
+ }
+
+ std::map<Glib::ustring, Glib::ustring> mapping = locateLinks(base, brokenHrefs);
+ for ( std::map<Glib::ustring, Glib::ustring>::iterator it = mapping.begin(); it != mapping.end(); ++it )
+ {
+ // TODO debug g_message(" [%s] ==> {%s}", it->first.c_str(), it->second.c_str());
+ }
+
+ DocumentUndo::ScopedInsensitive _no_undo(doc);
+
+ std::vector<SPObject *> images = doc->getResourceList("image");
+ for (auto image : images) {
+ Inkscape::XML::Node *ir = image->getRepr();
+
+ auto [href_key, href] = Inkscape::getHrefAttribute(*ir);
+ if ( href ) {
+ // TODO debug g_message(" consider [%s]", href);
+
+ if ( mapping.find(href) != mapping.end() ) {
+ // TODO debug g_message(" Found a replacement");
+
+ ir->setAttributeOrRemoveIfEmpty(href_key, mapping[href]);
+ if ( ir->attribute( "sodipodi:absref" ) ) {
+ ir->removeAttribute("sodipodi:absref"); // Remove this attribute
+ }
+
+ SPObject *updated = doc->getObjectByRepr(ir);
+ if (updated) {
+ // force immediate update of dependent attributes
+ updated->updateRepr();
+ }
+
+ changed = true;
+ }
+ }
+ }
+ if ( changed ) {
+ DocumentUndo::done( doc, _("Fixup broken links"), INKSCAPE_ICON("dialog-xml-editor"));
+ }
+ }
+
+ return changed;
+}
+
+static bool searchUpwards( std::string const &base, std::string const &subpath, std::string &dest )
+{
+ bool exists = false;
+ // TODO debug g_message("............");
+
+ std::vector<std::string> parts = splitPath(subpath);
+ std::vector<std::string> baseParts = splitPath(base);
+
+ while ( !exists && !baseParts.empty() ) {
+ std::vector<std::string> current;
+ current.insert(current.begin(), parts.begin(), parts.end());
+ // TODO debug g_message(" ---{%s}", Glib::build_filename( baseParts ).c_str());
+ while ( !exists && !current.empty() ) {
+ std::vector<std::string> combined;
+ combined.insert( combined.end(), baseParts.begin(), baseParts.end() );
+ combined.insert( combined.end(), current.begin(), current.end() );
+ std::string filepath = Glib::build_filename( combined );
+ exists = Glib::file_test(filepath, Glib::FILE_TEST_EXISTS);
+ // TODO debug g_message(" ...[%s] %s", filepath.c_str(), (exists ? "XXX" : ""));
+ if ( exists ) {
+ dest = filepath;
+ }
+ current.erase( current.begin() );
+ }
+ baseParts.pop_back();
+ }
+
+ return exists;
+}
+
+} // namespace Inkscape
+
+/*
+ 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 :
diff --git a/src/io/fix-broken-links.h b/src/io/fix-broken-links.h
new file mode 100644
index 0000000..525f9bf
--- /dev/null
+++ b/src/io/fix-broken-links.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Manages external resources such as image and css files.
+ *
+ * Copyright 2011 Jon A. Cruz <jon@joncruz.org>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <string>
+
+class SPDocument;
+
+namespace Inkscape {
+
+std::string convertPathToRelative(std::string const &path, std::string const &docbase);
+bool fixBrokenLinks(SPDocument *doc);
+
+}
+
+/*
+ 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/http.cpp b/src/io/http.cpp
new file mode 100644
index 0000000..6f28db7
--- /dev/null
+++ b/src/io/http.cpp
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::IO::HTTP - make internet requests using libsoup
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+/*
+ * How to use:
+ *
+ * #include "io/http.cpp"
+ * void _async_test_call(Glib::ustring filename) {
+ * g_warning("HTTP request saved to %s", filename.c_str());
+ * }
+ * uint timeout = 20 * 60 * 60; // 20 hours
+ * Glib::ustring filename = Inkscape::IO::HTTP::get_file("https://media.inkscape.org/media/messages.xml", timeout, _async_test_call);
+ *
+ */
+
+#include <glib/gstdio.h>
+#include <libsoup/soup.h>
+#include <string>
+#include <ctime>
+
+#include "io/sys.h"
+#include "io/http.h"
+#include "io/resource.h"
+
+typedef std::function<void(Glib::ustring)> callback;
+
+namespace Resource = Inkscape::IO::Resource;
+
+namespace Inkscape {
+namespace IO {
+namespace HTTP {
+
+void _save_data_as_file(Glib::ustring filename, const char *result) {
+ FILE *fileout = Inkscape::IO::fopen_utf8name(filename.c_str(), "wb");
+ if (!fileout) {
+ g_warning("HTTP Cache: Can't open %s for write.", filename.c_str());
+ return;
+ }
+
+ fputs(result, fileout);
+ fflush(fileout);
+ if (ferror(fileout)) {
+ g_warning("HTTP Cache: Error writing data to %s.", filename.c_str());
+ }
+
+ fclose(fileout);
+}
+
+void _get_file_callback(SoupSession *session, SoupMessage *msg, gpointer user_data) {
+ auto data = static_cast<std::pair<callback, Glib::ustring>*>(user_data);
+ data->first(data->second);
+ delete data;
+}
+
+/*
+ * Downloads a file, caches it in the local filesystem and then returns the
+ * new filename of the cached file.
+ *
+ * Returns: filename where the file has been (blocking) or will be (async) stored.
+ *
+ * uri - The uri of the desired resource, the filename comes from this uri
+ * timeout - Number of seconds to keep the cache, default is forever
+ * func - An optional function, if given the function becomes asynchronous
+ * the returned filename will be where the file 'hopes' to be saved
+ * and this function will be called when the http request is complete.
+ *
+ * NOTE: If the cache file exists, then it's returned without any async request
+ * your func will be called in a blocking way BEFORE this function returns.
+ *
+ */
+Glib::ustring get_file(Glib::ustring uri, unsigned int timeout, callback func) {
+
+ SoupURI *s_uri = soup_uri_new(uri.c_str());
+ std::string path = std::string(soup_uri_decode(soup_uri_get_path(s_uri)));
+ std::string filepart;
+
+ // Parse the url into a filename suitable for caching.
+ if(path.back() == '/') {
+ filepart = path.replace(path.begin(), path.end(), '/', '_');
+ filepart += ".url";
+ } else {
+ filepart = path.substr(path.rfind("/") + 1);
+ }
+
+ const char *ret = get_path(Resource::CACHE, Resource::NONE, filepart.c_str());
+ Glib::ustring filename = Glib::ustring(ret);
+
+ // We test first if the cache already exists
+ if(file_test(filename.c_str(), G_FILE_TEST_EXISTS) && timeout > 0) {
+ GStatBuf st;
+ if(g_stat(filename.c_str(), &st) != -1) {
+ time_t changed = st.st_mtime;
+ time_t now = time(nullptr);
+ // The cache hasn't timed out, so return the filename.
+ if(now - changed < timeout) {
+ if(func) {
+ // Non-async func callback return, may block.
+ func(filename);
+ }
+ return filename;
+ }
+ g_debug("HTTP Cache is stale: %s", filename.c_str());
+ }
+ }
+
+ // Only then do we get the http request
+ SoupMessage *msg = soup_message_new_from_uri("GET", s_uri);
+ SoupSession *session = soup_session_new();
+
+#ifdef DEBUG_HTTP
+ SoupLogger *logger;
+ logger = soup_logger_new(SOUP_LOGGER_LOG_BODY, -1);
+ soup_session_add_feature(session, SOUP_SESSION_FEATURE (logger));
+ g_object_unref (logger);
+#endif
+
+ if(func) {
+ auto *user_data = new std::pair<callback, Glib::ustring>(func, filename);
+ soup_session_queue_message(session, msg, _get_file_callback, user_data);
+ } else {
+ guint status = soup_session_send_message (session, msg);
+ if(status == SOUP_STATUS_OK) {
+ g_debug("HTTP Cache saved to: %s", filename.c_str());
+ _save_data_as_file(filename, msg->response_body->data);
+ } else {
+ g_warning("Can't download %s", uri.c_str());
+ }
+ }
+ return filename;
+}
+
+
+}
+}
+}
+
+/*
+ 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/http.h b/src/io/http.h
new file mode 100644
index 0000000..398d699
--- /dev/null
+++ b/src/io/http.h
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::IO::HTTP - make internet requests using libsoup
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_IO_HTTP_H
+#define SEEN_INKSCAPE_IO_HTTP_H
+
+#include <functional>
+#include <glibmm/ustring.h>
+
+/**
+ * simple libsoup based resource API
+ */
+
+namespace Inkscape {
+namespace IO {
+namespace HTTP {
+
+ Glib::ustring get_file(Glib::ustring uri, unsigned int timeout=0, std::function<void(Glib::ustring)> func=nullptr);
+
+}
+}
+}
+
+#endif
+/*
+ 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/resource.cpp b/src/io/resource.cpp
new file mode 100644
index 0000000..aa8af77
--- /dev/null
+++ b/src/io/resource.cpp
@@ -0,0 +1,559 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Inkscape::IO::Resource - simple resource API
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Martin Owens <doctormo@gmail.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef _WIN32
+#include <shlobj.h> // for SHGetSpecialFolderLocation
+#endif
+
+#include <glibmm/convert.h>
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/stringutils.h>
+#include <glibmm/fileutils.h>
+
+#include "path-prefix.h"
+#include "io/sys.h"
+#include "io/resource.h"
+#include "inkscape-application.h"
+#include "preferences.h"
+
+using Inkscape::IO::file_test;
+
+namespace Inkscape {
+
+namespace IO {
+
+namespace Resource {
+
+
+static void get_foldernames_from_path(std::vector<Glib::ustring> &folders, std::string const &path,
+ std::vector<const char *> const &exclusions = {});
+
+#define INKSCAPE_PROFILE_DIR "inkscape"
+
+gchar *_get_path(Domain domain, Type type, char const *filename)
+{
+ if (domain == USER || domain == SHARED) {
+ switch (type) {
+ case ATTRIBUTES:
+ case EXAMPLES:
+ case DOCS:
+ case SCREENS:
+ case TUTORIALS:
+ // Happens for example with `get_filename_string(SCREENS, ...)`
+ // but we don't want a user configurable about screen.
+ return nullptr;
+ }
+ }
+
+ char const *name = nullptr;
+ char const *sysdir = nullptr;
+ char const *envor = nullptr;
+
+ switch (domain) {
+ case CREATE: {
+ sysdir = "create";
+ switch (type) {
+ case PAINT: name = "paint"; break;
+ case PALETTES: name = "swatches"; break;
+ default: return nullptr;
+ }
+ } break;
+ case CACHE: {
+ g_assert(type == NONE);
+ return g_build_filename(g_get_user_cache_dir(), "inkscape", filename, nullptr);
+ } break;
+
+ case SYSTEM:
+ sysdir = "inkscape";
+ case SHARED:
+ case USER: {
+ switch (type) {
+ case ATTRIBUTES: name = "attributes"; break;
+ case DOCS: name = "doc"; break;
+ case EXAMPLES: name = "examples"; break;
+ case EXTENSIONS: name = "extensions"; envor = "INKSCAPE_EXTENSIONS_DIR"; break;
+ case FILTERS: name = "filters"; break;
+ case FONTS: name = "fonts"; break;
+ case FONTCOLLECTIONS: name = "fontcollections"; break;
+ case ICONS: name = "icons"; break;
+ case KEYS: name = "keys"; break;
+ case MARKERS: name = "markers"; break;
+ case PAINT: name = "paint"; break;
+ case PALETTES: name = "palettes"; break;
+ case SCREENS: name = "screens"; break;
+ case SYMBOLS: name = "symbols"; break;
+ case TEMPLATES: name = "templates"; break;
+ case THEMES: name = "themes"; break;
+ case TUTORIALS: name = "tutorials"; break;
+ case UIS: name = "ui"; break;
+ case PIXMAPS: name = "pixmaps"; break;
+ default: g_assert_not_reached();
+ return nullptr;
+ }
+ } break;
+ }
+ // Look for an over-ride in the local environment
+ if (envor && domain == USER) {
+ std::string env_dir = Glib::getenv(envor);
+ if (!env_dir.empty()) {
+ return g_build_filename(env_dir.c_str(), filename, nullptr);
+ }
+ }
+
+ if (!name) {
+ return nullptr;
+ }
+
+ if (sysdir) {
+ return g_build_filename(get_inkscape_datadir(), sysdir, name, filename, nullptr);
+ } else if (domain == SHARED) {
+ if (shared_path().empty()) {
+ return nullptr;
+ }
+ return g_build_filename(shared_path().c_str(), name, filename, nullptr);
+ } else {
+ if (profile_path().empty()) {
+ return nullptr;
+ }
+ return g_build_filename(profile_path().c_str(), name, filename, nullptr);
+ }
+}
+
+
+
+Util::ptr_shared get_path(Domain domain, Type type, char const *filename)
+{
+ char *path = _get_path(domain, type, filename);
+ if (!path) {
+ return Util::ptr_shared();
+ }
+ Util::ptr_shared result=Util::share_string(path);
+ g_free(path);
+ return result;
+}
+Glib::ustring get_path_ustring(Domain domain, Type type, char const *filename)
+{
+ Glib::ustring result;
+ char *path = _get_path(domain, type, filename);
+ if(path) {
+ result = Glib::ustring(path);
+ g_free(path);
+ }
+ return result;
+}
+std::string get_path_string(Domain domain, Type type, char const *filename)
+{
+ std::string result;
+ char *path = _get_path(domain, type, filename);
+ if (path) {
+ result = path;
+ g_free(path);
+ }
+ return result;
+}
+
+/*
+ * Same as get_path, but checks for file's existence and falls back
+ * from USER to SYSTEM modes.
+ *
+ * type - The type of file to get, such as extension, template, ui etc
+ * filename - The filename to get, i.e. preferences.xml
+ * localized - Prefer a localized version of the file, i.e. default.de.svg instead of default.svg.
+ * (will use gettext to determine the preferred language of the user)
+ * silent - do not warn if file doesn't exist
+ *
+ */
+Glib::ustring get_filename(Type type, char const *filename, bool localized, bool silent)
+{
+ return get_filename_string(type, filename, localized, silent);
+}
+
+std::string get_filename_string(Type type, char const *filename, bool localized, bool silent)
+{
+ std::string result;
+
+ char *user_filename = nullptr;
+ char *shared_filename = nullptr;
+ char *sys_filename = nullptr;
+ char *user_filename_localized = nullptr;
+ char *sys_filename_localized = nullptr;
+
+ // TRANSLATORS: 'en' is an ISO 639-1 language code.
+ // Replace with language code for your language, i.e. the name of your .po file
+ localized = localized && strcmp(_("en"), "en");
+
+ if (localized) {
+ std::string localized_filename = filename;
+ localized_filename.insert(localized_filename.rfind('.'), ".");
+ localized_filename.insert(localized_filename.rfind('.'), _("en"));
+
+ user_filename_localized = _get_path(USER, type, localized_filename.c_str());
+ sys_filename_localized = _get_path(SYSTEM, type, localized_filename.c_str());
+ }
+ user_filename = _get_path(USER, type, filename);
+ shared_filename = _get_path(SHARED, type, filename);
+ sys_filename = _get_path(SYSTEM, type, filename);
+
+ // impose the following load order:
+ // USER (localized) > USER > SYSTEM (localized) > SYSTEM
+ if (localized && file_test(user_filename_localized, G_FILE_TEST_EXISTS)) {
+ result = user_filename_localized;
+ g_info("Found localized version of resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(user_filename, G_FILE_TEST_EXISTS)) {
+ result = user_filename;
+ g_info("Found resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(shared_filename, G_FILE_TEST_EXISTS)) {
+ result = shared_filename;
+ g_info("Found resource file '%s' in profile directory:\n\t%s", filename, result.c_str());
+ } else if (localized && file_test(sys_filename_localized, G_FILE_TEST_EXISTS)) {
+ result = sys_filename_localized;
+ g_info("Found localized version of resource file '%s' in system directory:\n\t%s", filename, result.c_str());
+ } else if (file_test(sys_filename, G_FILE_TEST_EXISTS)) {
+ result = sys_filename;
+ g_info("Found resource file '%s' in system directory:\n\t%s", filename, result.c_str());
+ } else if (!silent) {
+ if (localized) {
+ g_warning("Failed to find resource file '%s'. Looked in:\n\t%s\n\t%s\n\t%s\n\t%s\n\t%s",
+ filename, user_filename_localized, user_filename, shared_filename, sys_filename_localized, sys_filename);
+ } else {
+ g_warning("Failed to find resource file '%s'. Looked in:\n\t%s\n\t%s\n\t%s",
+ filename, user_filename, shared_filename, sys_filename);
+ }
+ }
+
+ g_free(user_filename);
+ g_free(shared_filename);
+ g_free(sys_filename);
+ g_free(user_filename_localized);
+ g_free(sys_filename_localized);
+
+ return result;
+}
+
+/*
+ * Similar to get_filename, but takes a path (or filename) for relative resolution
+ *
+ * path - A directory or filename that is considered local to the path resolution.
+ * filename - The filename that we are looking for.
+ */
+Glib::ustring get_filename(Glib::ustring path, Glib::ustring filename)
+{
+ return get_filename(Glib::filename_from_utf8(path), //
+ Glib::filename_from_utf8(filename));
+}
+
+std::string get_filename(std::string const& path, std::string const& filename)
+{
+ // Test if it's a filename and get the parent directory instead
+ if (Glib::file_test(path, Glib::FILE_TEST_IS_REGULAR)) {
+ auto dirname = Glib::path_get_dirname(path);
+ g_assert(!Glib::file_test(dirname, Glib::FILE_TEST_IS_REGULAR)); // recursion sanity check
+ return get_filename(dirname, filename);
+ }
+ if (g_path_is_absolute(filename.c_str())) {
+ if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) {
+ return filename;
+ }
+ } else {
+ auto ret = Glib::build_filename(path, filename);
+ if (Glib::file_test(ret, Glib::FILE_TEST_EXISTS)) {
+ return ret;
+ }
+ }
+ return {};
+}
+
+/*
+ * Gets all the files in a given type, for all domain types.
+ *
+ * domain - Optional domain (overload), will check return domains if not.
+ * type - The type of files, e.g. TEMPLATES
+ * extensions - A list of extensions to return, e.g. xml, svg
+ * exclusions - A list of names to exclude e.g. default.xml
+ */
+std::vector<Glib::ustring> get_filenames(Type type, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, get_path_string(USER, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_string(SHARED, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_string(SYSTEM, type), extensions, exclusions);
+ get_filenames_from_path(ret, get_path_string(CREATE, type), extensions, exclusions);
+ return ret;
+}
+
+std::vector<Glib::ustring> get_filenames(Domain domain, Type type, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, get_path_string(domain, type), extensions, exclusions);
+ return ret;
+}
+std::vector<Glib::ustring> get_filenames(Glib::ustring path, std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_filenames_from_path(ret, Glib::filename_from_utf8(path), extensions, exclusions);
+ return ret;
+}
+
+/*
+ * Gets all folders inside each type, for all domain types.
+ *
+ * domain - Optional domain (overload), will check return domains if not.
+ * type - The type of files, e.g. TEMPLATES
+ * extensions - A list of extensions to return, e.g. xml, svg
+ * exclusions - A list of names to exclude e.g. default.xml
+ */
+std::vector<Glib::ustring> get_foldernames(Type type, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, get_path_ustring(USER, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(SHARED, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(SYSTEM, type), exclusions);
+ get_foldernames_from_path(ret, get_path_ustring(CREATE, type), exclusions);
+ return ret;
+}
+
+std::vector<Glib::ustring> get_foldernames(Domain domain, Type type, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, get_path_ustring(domain, type), exclusions);
+ return ret;
+}
+std::vector<Glib::ustring> get_foldernames(Glib::ustring path, std::vector<const char *> const &exclusions)
+{
+ std::vector<Glib::ustring> ret;
+ get_foldernames_from_path(ret, path, exclusions);
+ return ret;
+}
+
+
+/*
+ * Get all the files from a specific path and any sub-dirs, populating &files vector
+ *
+ * &files - Output list to populate, will be populated with full paths
+ * path - The directory to parse, will add nothing if directory doesn't exist
+ * extensions - Only add files with these extensions, they must be duplicated
+ * exclusions - Exclude files that exactly match these names.
+ */
+void get_filenames_from_path(std::vector<Glib::ustring> &files, std::string const &path,
+ std::vector<const char *> const &extensions, std::vector<const char *> const &exclusions)
+{
+ if(!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+ return;
+ }
+
+ Glib::Dir dir(path);
+ std::string file = dir.read_name();
+ while (!file.empty()){
+ // If not extensions are specified, don't reject ANY files.
+ bool reject = !extensions.empty();
+
+ // Unreject any file which has one of the extensions.
+ for (auto &ext: extensions) {
+ reject ^= Glib::str_has_suffix(file, ext);
+ }
+
+ // Reject any file which matches the exclusions.
+ for (auto &exc: exclusions) {
+ reject |= Glib::str_has_prefix(file, exc);
+ }
+
+ // Reject any filename which isn't a regular file
+ auto filename = Glib::build_filename(path, file);
+
+ if(Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) {
+ get_filenames_from_path(files, filename, extensions, exclusions);
+ } else if(Glib::file_test(filename, Glib::FILE_TEST_IS_REGULAR) && !reject) {
+ files.push_back(Glib::filename_to_utf8(filename));
+ }
+ file = dir.read_name();
+ }
+}
+
+/*
+ * Get all the files from a specific path and any sub-dirs, populating &files vector
+ *
+ * &folders - Output list to populate, will be poulated with full paths
+ * path - The directory to parse, will add nothing if directory doesn't exist
+ * exclusions - Exclude files that exactly match these names.
+ */
+void get_foldernames_from_path(std::vector<Glib::ustring> &folders, Glib::ustring path,
+ std::vector<const char *> exclusions)
+{
+ get_foldernames_from_path(folders, Glib::filename_from_utf8(path), exclusions);
+}
+
+void get_foldernames_from_path(std::vector<Glib::ustring> &folders, std::string const &path,
+ std::vector<const char *> const &exclusions)
+{
+ if (!Glib::file_test(path, Glib::FILE_TEST_IS_DIR)) {
+ return;
+ }
+
+ Glib::Dir dir(path);
+ std::string file = dir.read_name();
+ while (!file.empty()) {
+ // If not extensions are specified, don't reject ANY files.
+ bool reject = false;
+
+ // Reject any file which matches the exclusions.
+ for (auto &exc : exclusions) {
+ reject |= Glib::str_has_prefix(file, exc);
+ }
+
+ // Reject any filename which isn't a regular file
+ auto filename = Glib::build_filename(path, file);
+
+ if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR) && !reject) {
+ folders.push_back(Glib::filename_to_utf8(filename));
+ }
+ file = dir.read_name();
+ }
+}
+
+
+/**
+ * Get, or guess, or decide the location where the preferences.xml
+ * file should be located. This also indicates where all other inkscape
+ * shared files may optionally exist.
+ */
+std::string profile_path(const char *filename)
+{
+ if (profile_path().empty()) {
+ return std::string("");
+ }
+ return Glib::build_filename(profile_path(), filename);
+}
+
+std::string profile_path()
+{
+
+ static std::string prefdir = "";
+
+ if (prefdir.empty()) {
+ // Check if profile directory is overridden using environment variable
+ prefdir = Glib::getenv("INKSCAPE_PROFILE_DIR");
+#ifdef _WIN32
+ // prefer c:\Documents and Settings\UserName\Application Data\ to c:\Documents and Settings\userName\;
+ // TODO: CSIDL_APPDATA is C:\Users\UserName\AppData\Roaming these days
+ // should we migrate to AppData\Local? Then we can simply use the portable g_get_user_config_dir()
+ if (prefdir.empty()) {
+ ITEMIDLIST *pidl = 0;
+ if ( SHGetFolderLocation( NULL, CSIDL_APPDATA, NULL, 0, &pidl ) == S_OK ) {
+ gchar * utf8Path = NULL;
+
+ {
+ wchar_t pathBuf[MAX_PATH+1];
+ g_assert(sizeof(wchar_t) == sizeof(gunichar2));
+
+ if ( SHGetPathFromIDListW( pidl, pathBuf ) ) {
+ utf8Path = g_utf16_to_utf8( (gunichar2*)(&pathBuf[0]), -1, NULL, NULL, NULL );
+ }
+ }
+
+ if ( utf8Path ) {
+ if (!g_utf8_validate(utf8Path, -1, NULL)) {
+ g_warning( "SHGetPathFromIDListW() resulted in invalid UTF-8");
+ g_free( utf8Path );
+ utf8Path = 0;
+ } else {
+ prefdir = utf8Path;
+ }
+ }
+ }
+
+ if (!prefdir.empty()) {
+ prefdir = Glib::build_filename(prefdir, INKSCAPE_PROFILE_DIR);
+ }
+ }
+#endif
+ if (prefdir.empty()) {
+ prefdir = Glib::build_filename(get_user_config_dir(), INKSCAPE_PROFILE_DIR);
+ // In case the XDG user config dir of the moment does not yet exist...
+ int mode = S_IRWXU;
+#ifdef S_IRGRP
+ mode |= S_IRGRP;
+#endif
+#ifdef S_IXGRP
+ mode |= S_IXGRP;
+#endif
+#ifdef S_IXOTH
+ mode |= S_IXOTH;
+#endif
+ if (g_mkdir_with_parents(prefdir.c_str(), mode) == -1 ) {
+ int problem = errno;
+ g_warning("Unable to create profile directory (%s) (%d)", g_strerror(problem), problem);
+ } else {
+ gchar const *userDirs[] = { "keys", "templates", "icons", "extensions", "ui",
+ "symbols", "paint", "themes", "palettes", "fontcollections", nullptr };
+ for (gchar const** name = userDirs; *name; ++name) {
+ gchar *dir = g_build_filename(prefdir.c_str(), *name, nullptr);
+ g_mkdir_with_parents(dir, mode);
+ g_free(dir);
+ }
+ }
+ }
+ }
+ return prefdir;
+}
+
+std::string shared_path(const char *filename)
+{
+ return shared_path().empty() ? shared_path() : Glib::build_filename(shared_path(), filename);
+}
+
+std::string shared_path()
+{
+ if (InkscapeApplication::instance()) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ std::string shared_dir = prefs->getString("/options/resources/sharedpath");
+ if (!shared_dir.empty() && Glib::file_test(shared_dir, Glib::FILE_TEST_IS_DIR)) {
+ return shared_dir;
+ }
+ }
+ return std::string("");
+}
+
+/*
+ * We return the profile_path because that is where most documentation
+ * days log files will be generated in inkscape 0.92
+ */
+std::string log_path(const char *filename)
+{
+ return profile_path(filename);
+}
+
+std::string homedir_path()
+{
+ return g_get_home_dir();
+}
+
+}
+
+}
+
+}
+
+/*
+ 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/resource.h b/src/io/resource.h
new file mode 100644
index 0000000..e2105de
--- /dev/null
+++ b/src/io/resource.h
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors:
+ * MenTaLguY <mental@rydia.net>
+ * Martin Owens <doctormo@gmail.com>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_IO_RESOURCE_H
+#define SEEN_INKSCAPE_IO_RESOURCE_H
+
+#include <vector>
+#include <glibmm/ustring.h>
+#include "util/share.h"
+
+namespace Inkscape {
+
+namespace IO {
+
+/**
+ * simple resource API
+ */
+namespace Resource {
+
+enum Type {
+ ATTRIBUTES,
+ EXAMPLES,
+ EXTENSIONS,
+ FONTS,
+ FONTCOLLECTIONS,
+ ICONS,
+ KEYS,
+ MARKERS,
+ NONE,
+ PAINT,
+ PALETTES,
+ SCREENS,
+ TEMPLATES,
+ TUTORIALS,
+ SYMBOLS,
+ FILTERS,
+ THEMES,
+ UIS,
+ PIXMAPS,
+ DOCS
+};
+
+enum Domain {
+ SYSTEM,
+ CREATE,
+ CACHE,
+ SHARED,
+ USER
+};
+
+Util::ptr_shared get_path(Domain domain, Type type,
+ char const *filename=nullptr);
+
+Glib::ustring get_path_ustring(Domain domain, Type type,
+ char const *filename=nullptr);
+std::string get_path_string(Domain domain, Type type, char const *filename = nullptr);
+
+std::string get_filename_string(Type type, char const *filename, bool localized = false, bool silent = false);
+Glib::ustring get_filename(Type type, char const *filename, bool localized = false, bool silent = false);
+// TODO consolidate with Glib::StdStringView
+Glib::ustring get_filename(Glib::ustring path, Glib::ustring filename);
+std::string get_filename(std::string const& path, std::string const& filename);
+inline std::string get_filename(const char *path, const char *filename)
+{
+ return get_filename(std::string(path), std::string(filename));
+}
+
+std::vector<Glib::ustring> get_filenames(Type type,
+ std::vector<const char *> const &extensions={},
+ std::vector<const char *> const &exclusions={});
+
+std::vector<Glib::ustring> get_filenames(Domain domain, Type type,
+ std::vector<const char *> const &extensions={},
+ std::vector<const char *> const &exclusions={});
+
+std::vector<Glib::ustring> get_filenames(Glib::ustring path,
+ std::vector<const char *> const &extensions={},
+ std::vector<const char *> const &exclusions={});
+
+std::vector<Glib::ustring> get_foldernames(Type type, std::vector<const char *> const &exclusions = {});
+
+std::vector<Glib::ustring> get_foldernames(Domain domain, Type type, std::vector<const char *> const &exclusions = {});
+
+std::vector<Glib::ustring> get_foldernames(Glib::ustring path, std::vector<const char *> const &exclusions = {});
+
+void get_foldernames_from_path(std::vector<Glib::ustring> &files, Glib::ustring path,
+ std::vector<const char *> exclusions = {});
+
+void get_filenames_from_path(std::vector<Glib::ustring> &files, std::string const &path,
+ std::vector<const char *> const &extensions = {},
+ std::vector<const char *> const &exclusions = {});
+
+
+std::string profile_path();
+std::string profile_path(const char *filename);
+std::string shared_path();
+std::string shared_path(const char *filename);
+std::string homedir_path();
+std::string log_path(const char *filename);
+
+}
+
+}
+
+}
+
+#endif
+/*
+ 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:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/stream/README b/src/io/stream/README
new file mode 100644
index 0000000..67d8721
--- /dev/null
+++ b/src/io/stream/README
@@ -0,0 +1,13 @@
+
+This directory contains code related to streams.
+
+Base class:
+ inkscapestream.h Used by other stream handling code and odf, filter-file
+
+Derived classes:
+ bufferstream.h Used by odf
+ gzipstream.h Used by repr-io.cpp
+ stringstream.h Used by rerp-io.cpp and ui/clipboard.cpp
+ Note file with same name in src/svg
+ uristream.h Used by repr-io.cpp
+ xsltstream.h Used by none one (except testfile)
diff --git a/src/io/stream/bufferstream.cpp b/src/io/stream/bufferstream.cpp
new file mode 100644
index 0000000..1723ee1
--- /dev/null
+++ b/src/io/stream/bufferstream.cpp
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * @file
+ * Phoebe DOM Implementation.
+ *
+ * This is a C++ approximation of the W3C DOM model, which follows
+ * fairly closely the specifications in the various .idl files, copies of
+ * which are provided for reference. Most important is this one:
+ *
+ * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
+ *//*
+ * Authors:
+ * see git history
+ * Bob Jamison
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+
+/**
+ * This class provided buffered endpoints for input and output.
+ */
+
+#include "bufferstream.h"
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# B U F F E R I N P U T S T R E A M
+//#########################################################################
+/**
+ *
+ */
+BufferInputStream::BufferInputStream(
+ const std::vector<unsigned char> &sourceBuffer)
+ : buffer(sourceBuffer)
+{
+ position = 0;
+ closed = false;
+}
+
+/**
+ *
+ */
+BufferInputStream::~BufferInputStream()
+= default;
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int BufferInputStream::available()
+{
+ if (closed)
+ return -1;
+ return buffer.size() - position;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void BufferInputStream::close()
+{
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int BufferInputStream::get()
+{
+ if (closed)
+ return -1;
+ if (position >= (int)buffer.size())
+ return -1;
+ int ch = (int) buffer[position++];
+ return ch;
+}
+
+
+
+
+//#########################################################################
+//# B U F F E R O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+BufferOutputStream::BufferOutputStream()
+{
+ closed = false;
+}
+
+/**
+ *
+ */
+BufferOutputStream::~BufferOutputStream()
+= default;
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void BufferOutputStream::close()
+{
+ closed = true;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void BufferOutputStream::flush()
+{
+ //nothing to do
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int BufferOutputStream::put(char ch)
+{
+ if (closed)
+ return -1;
+ buffer.push_back(ch);
+ return 1;
+}
+
+
+
+
+} //namespace IO
+} //namespace Inkscape
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/bufferstream.h b/src/io/stream/bufferstream.h
new file mode 100644
index 0000000..811ab0d
--- /dev/null
+++ b/src/io/stream/bufferstream.h
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/**
+ * @file
+ * Phoebe DOM Implementation.
+ *
+ * This is a C++ approximation of the W3C DOM model, which follows
+ * fairly closely the specifications in the various .idl files, copies of
+ * which are provided for reference. Most important is this one:
+ *
+ * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
+ *//*
+ * Authors:
+ * see git history
+ * Bob Jamison
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_BUFFERSTREAM_H
+#define SEEN_BUFFERSTREAM_H
+
+
+#include <vector>
+#include "inkscapestream.h"
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# S T R I N G I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for reading character from a DOMString
+ *
+ */
+class BufferInputStream : public InputStream
+{
+
+public:
+
+ BufferInputStream(const std::vector<unsigned char> &sourceBuffer);
+ ~BufferInputStream() override;
+ int available() override;
+ void close() override;
+ int get() override;
+
+private:
+ const std::vector<unsigned char> &buffer;
+ long position;
+ bool closed;
+
+}; // class BufferInputStream
+
+
+
+
+//#########################################################################
+//# B U F F E R O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for sending a stream to a character buffer
+ *
+ */
+class BufferOutputStream : public OutputStream
+{
+
+public:
+
+ BufferOutputStream();
+ ~BufferOutputStream() override;
+ void close() override;
+ void flush() override;
+ int put(char ch) override;
+ virtual std::vector<unsigned char> &getBuffer()
+ { return buffer; }
+
+ virtual void clear()
+ { buffer.clear(); }
+
+private:
+ std::vector<unsigned char> buffer;
+ bool closed;
+
+}; // class BufferOutputStream
+
+
+
+} //namespace IO
+} //namespace Inkscape
+
+
+
+#endif // SEEN_BUFFERSTREAM_H
diff --git a/src/io/stream/gzipstream.cpp b/src/io/stream/gzipstream.cpp
new file mode 100644
index 0000000..d1aa760
--- /dev/null
+++ b/src/io/stream/gzipstream.cpp
@@ -0,0 +1,463 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/** @file
+ * Zlib-enabled input and output streams
+ *//*
+ * Authors:
+ * see git history
+ * Bob Jamison <rjamison@titan.com>
+ *
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+/*
+ * This is a thin wrapper of libz calls, in order
+ * to provide a simple interface to our developers
+ * for gzip input and output.
+ */
+
+#include "gzipstream.h"
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <string>
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# G Z I P I N P U T S T R E A M
+//#########################################################################
+
+#define OUT_SIZE 4000
+
+/**
+ *
+ */
+GzipInputStream::GzipInputStream(InputStream &sourceStream)
+ : BasicInputStream(sourceStream),
+ loaded(false),
+ outputBuf(nullptr),
+ srcBuf(nullptr),
+ crc(0),
+ srcCrc(0),
+ srcSiz(0),
+ srcLen(0),
+ outputBufPos(0),
+ outputBufLen(0)
+{
+ memset( &d_stream, 0, sizeof(d_stream) );
+}
+
+/**
+ *
+ */
+GzipInputStream::~GzipInputStream()
+{
+ close();
+ if ( srcBuf ) {
+ delete[] srcBuf;
+ srcBuf = nullptr;
+ }
+ if ( outputBuf ) {
+ delete[] outputBuf;
+ outputBuf = nullptr;
+ }
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int GzipInputStream::available()
+{
+ if (closed || !outputBuf)
+ return 0;
+ return outputBufLen - outputBufPos;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void GzipInputStream::close()
+{
+ if (closed)
+ return;
+
+ int zerr = inflateEnd(&d_stream);
+ if (zerr != Z_OK) {
+ printf("inflateEnd: Some kind of problem: %d\n", zerr);
+ }
+
+ if ( srcBuf ) {
+ delete[] srcBuf;
+ srcBuf = nullptr;
+ }
+ if ( outputBuf ) {
+ delete[] outputBuf;
+ outputBuf = nullptr;
+ }
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int GzipInputStream::get()
+{
+ int ch = -1;
+ if (closed) {
+ // leave return value -1
+ }
+ else if (!loaded && !load()) {
+ closed=true;
+ } else {
+ loaded = true;
+
+ if ( outputBufPos >= outputBufLen ) {
+ // time to read more, if we can
+ fetchMore();
+ }
+
+ if ( outputBufPos < outputBufLen ) {
+ ch = (int)outputBuf[outputBufPos++];
+ }
+ }
+
+ return ch;
+}
+
+#define FTEXT 0x01
+#define FHCRC 0x02
+#define FEXTRA 0x04
+#define FNAME 0x08
+#define FCOMMENT 0x10
+
+bool GzipInputStream::load()
+{
+ crc = crc32(0L, Z_NULL, 0);
+
+ std::vector<Byte> inputBuf;
+ while (true)
+ {
+ int ch = source.get();
+ if (ch<0)
+ break;
+ inputBuf.push_back(static_cast<Byte>(ch & 0xff));
+ }
+ long inputBufLen = inputBuf.size();
+
+ if (inputBufLen < 19) //header + tail + 1
+ {
+ return false;
+ }
+
+ srcLen = inputBuf.size();
+ srcBuf = new (std::nothrow) Byte [srcLen];
+ if (!srcBuf) {
+ return false;
+ }
+
+ outputBuf = new (std::nothrow) unsigned char [OUT_SIZE];
+ if ( !outputBuf ) {
+ delete[] srcBuf;
+ srcBuf = nullptr;
+ return false;
+ }
+ outputBufLen = 0; // Not filled in yet
+
+ std::vector<unsigned char>::iterator iter;
+ Bytef *p = srcBuf;
+ for (iter=inputBuf.begin() ; iter != inputBuf.end() ; ++iter)
+ {
+ *p++ = *iter;
+ }
+
+ size_t headerLen = 10;
+
+ int flags = static_cast<int>(srcBuf[3]);
+
+ constexpr size_t size_XLEN = 2;
+ constexpr size_t size_CRC16 = 2;
+ constexpr size_t size_CRC32 = 4;
+ constexpr size_t size_ISIZE = 4;
+
+ auto const check_not_truncated = [&] { return headerLen + size_CRC32 + size_ISIZE <= srcLen; };
+
+ auto const skip_n = [&](size_t n) {
+ headerLen += n;
+ return check_not_truncated();
+ };
+
+ auto const skip_zero_terminated = [&] {
+ while (headerLen < srcLen && srcBuf[headerLen++]) {
+ }
+ return check_not_truncated();
+ };
+
+ if (flags & FEXTRA) {
+ if (!skip_n(size_XLEN)) {
+ return false;
+ }
+ auto const xlen = size_t(srcBuf[headerLen - 2]) | //
+ size_t(srcBuf[headerLen - 1] << 8);
+ if (!skip_n(xlen)) {
+ return false;
+ }
+ }
+
+ if ((flags & FNAME) && !skip_zero_terminated()) {
+ return false;
+ }
+
+ if ((flags & FCOMMENT) && !skip_zero_terminated()) {
+ return false;
+ }
+
+ if ((flags & FHCRC) && !skip_n(size_CRC16)) {
+ return false;
+ }
+
+ if (!check_not_truncated()) {
+ return false;
+ }
+
+ srcCrc = ((0x0ff & srcBuf[srcLen - 5]) << 24)
+ | ((0x0ff & srcBuf[srcLen - 6]) << 16)
+ | ((0x0ff & srcBuf[srcLen - 7]) << 8)
+ | ((0x0ff & srcBuf[srcLen - 8]) << 0);
+ //printf("srcCrc:%lx\n", srcCrc);
+
+ srcSiz = ((0x0ff & srcBuf[srcLen - 1]) << 24)
+ | ((0x0ff & srcBuf[srcLen - 2]) << 16)
+ | ((0x0ff & srcBuf[srcLen - 3]) << 8)
+ | ((0x0ff & srcBuf[srcLen - 4]) << 0);
+ //printf("srcSiz:%lx/%ld\n", srcSiz, srcSiz);
+
+ //outputBufLen = srcSiz + srcSiz/100 + 14;
+
+ unsigned char *data = srcBuf + headerLen;
+ unsigned long dataLen = srcLen - (headerLen + 8);
+ //printf("%x %x\n", data[0], data[dataLen-1]);
+
+ d_stream.zalloc = (alloc_func)nullptr;
+ d_stream.zfree = (free_func)nullptr;
+ d_stream.opaque = (voidpf)nullptr;
+ d_stream.next_in = data;
+ d_stream.avail_in = dataLen;
+ d_stream.next_out = outputBuf;
+ d_stream.avail_out = OUT_SIZE;
+
+ int zerr = inflateInit2(&d_stream, -MAX_WBITS);
+ if ( zerr == Z_OK )
+ {
+ zerr = fetchMore();
+ } else {
+ printf("inflateInit2: Some kind of problem: %d\n", zerr);
+ }
+
+
+ return (zerr == Z_OK) || (zerr == Z_STREAM_END);
+}
+
+
+int GzipInputStream::fetchMore()
+{
+ // TODO assumes we aren't called till the buffer is empty
+ d_stream.next_out = outputBuf;
+ d_stream.avail_out = OUT_SIZE;
+ outputBufLen = 0;
+ outputBufPos = 0;
+
+ int zerr = inflate( &d_stream, Z_SYNC_FLUSH );
+ if ( zerr == Z_OK || zerr == Z_STREAM_END ) {
+ outputBufLen = OUT_SIZE - d_stream.avail_out;
+ if ( outputBufLen ) {
+ crc = crc32(crc, const_cast<const Bytef *>(outputBuf), outputBufLen);
+ }
+ //printf("crc:%lx\n", crc);
+// } else if ( zerr != Z_STREAM_END ) {
+// // TODO check to be sure this won't happen for partial end reads
+// printf("inflate: Some kind of problem: %d\n", zerr);
+ }
+
+ return zerr;
+}
+
+//#########################################################################
+//# G Z I P O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+GzipOutputStream::GzipOutputStream(OutputStream &destinationStream)
+ : BasicOutputStream(destinationStream)
+{
+
+ totalIn = 0;
+ totalOut = 0;
+ crc = crc32(0L, Z_NULL, 0);
+
+ //Gzip header
+ destination.put(0x1f);
+ destination.put(0x8b);
+
+ //Say it is compressed
+ destination.put(Z_DEFLATED);
+
+ //flags
+ destination.put(0);
+
+ //time
+ destination.put(0);
+ destination.put(0);
+ destination.put(0);
+ destination.put(0);
+
+ //xflags
+ destination.put(0);
+
+ //OS code - from zutil.h
+ //destination.put(OS_CODE);
+ //apparently, we should not explicitly include zutil.h
+ destination.put(0);
+
+}
+
+/**
+ *
+ */
+GzipOutputStream::~GzipOutputStream()
+{
+ close();
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void GzipOutputStream::close()
+{
+ if (closed)
+ return;
+
+ flush();
+
+ //# Send the CRC
+ uLong outlong = crc;
+ for (int n = 0; n < 4; n++)
+ {
+ destination.put(static_cast<char>(outlong & 0xff));
+ outlong >>= 8;
+ }
+ //# send the file length
+ outlong = totalIn & 0xffffffffL;
+ for (int n = 0; n < 4; n++)
+ {
+ destination.put(static_cast<char>(outlong & 0xff));
+ outlong >>= 8;
+ }
+
+ destination.close();
+ closed = true;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void GzipOutputStream::flush()
+{
+ if (closed || inputBuf.empty())
+ {
+ return;
+ }
+
+ uLong srclen = inputBuf.size();
+ Bytef *srcbuf = new (std::nothrow) Bytef [srclen];
+ if (!srcbuf)
+ {
+ return;
+ }
+
+ uLong destlen = srclen;
+ Bytef *destbuf = new (std::nothrow) Bytef [(destlen + (srclen/100) + 13)];
+ if (!destbuf)
+ {
+ delete[] srcbuf;
+ return;
+ }
+
+ std::vector<unsigned char>::iterator iter;
+ Bytef *p = srcbuf;
+ for (iter=inputBuf.begin() ; iter != inputBuf.end() ; ++iter)
+ *p++ = *iter;
+
+ crc = crc32(crc, const_cast<const Bytef *>(srcbuf), srclen);
+
+ int zerr = compress(destbuf, static_cast<uLongf *>(&destlen), srcbuf, srclen);
+ if (zerr != Z_OK)
+ {
+ printf("Some kind of problem\n");
+ }
+
+ totalOut += destlen;
+ //skip the redundant zlib header and checksum
+ for (uLong i=2; i<destlen-4 ; i++)
+ {
+ destination.put((int)destbuf[i]);
+ }
+
+ destination.flush();
+
+ inputBuf.clear();
+ delete[] srcbuf;
+ delete[] destbuf;
+}
+
+
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int GzipOutputStream::put(char ch)
+{
+ if (closed)
+ {
+ //probably throw an exception here
+ return -1;
+ }
+
+
+ //Add char to buffer
+ inputBuf.push_back(ch);
+ totalIn++;
+ return 1;
+}
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/io/stream/gzipstream.h b/src/io/stream/gzipstream.h
new file mode 100644
index 0000000..202b3a1
--- /dev/null
+++ b/src/io/stream/gzipstream.h
@@ -0,0 +1,120 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_GZIPSTREAM_H
+#define SEEN_INKSCAPE_IO_GZIPSTREAM_H
+/**
+ * @file
+ * Zlib-enabled input and output streams.
+ *
+ * This is a thin wrapper of libz calls, in order
+ * to provide a simple interface to our developers
+ * for gzip input and output.
+ */
+/*
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <vector>
+#include "inkscapestream.h"
+#include <zlib.h>
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# G Z I P I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for deflating a gzip-compressed InputStream source
+ *
+ */
+class GzipInputStream : public BasicInputStream
+{
+
+public:
+
+ GzipInputStream(InputStream &sourceStream);
+
+ ~GzipInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+private:
+
+ bool load();
+ int fetchMore();
+
+ bool loaded;
+
+ unsigned char *outputBuf;
+ unsigned char *srcBuf;
+
+ unsigned long crc;
+ unsigned long srcCrc;
+ unsigned long srcSiz;
+ unsigned long srcLen;
+ long outputBufPos;
+ long outputBufLen;
+
+ z_stream d_stream;
+}; // class GzipInputStream
+
+
+
+
+//#########################################################################
+//# G Z I P O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for gzip-compressing data going to the
+ * destination OutputStream
+ *
+ */
+class GzipOutputStream : public BasicOutputStream
+{
+
+public:
+
+ GzipOutputStream(OutputStream &destinationStream);
+
+ ~GzipOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+private:
+
+ std::vector<unsigned char> inputBuf;
+
+ long totalIn;
+ long totalOut;
+ unsigned long crc;
+
+}; // class GzipOutputStream
+
+
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif /* __INKSCAPE_IO_GZIPSTREAM_H__ */
diff --git a/src/io/stream/inkscapestream.cpp b/src/io/stream/inkscapestream.cpp
new file mode 100644
index 0000000..bc6dc1d
--- /dev/null
+++ b/src/io/stream/inkscapestream.cpp
@@ -0,0 +1,800 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Our base input/output stream classes. These are is directly
+ * inherited from iostreams, and includes any extra
+ * functionality that we might need.
+ *
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdlib>
+#include "inkscapestream.h"
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# U T I L I T Y
+//#########################################################################
+
+void pipeStream(InputStream &source, OutputStream &dest)
+{
+ for (;;)
+ {
+ int ch = source.get();
+ if (ch<0)
+ break;
+ dest.put(ch);
+ }
+ dest.flush();
+}
+
+//#########################################################################
+//# B A S I C I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+BasicInputStream::BasicInputStream(InputStream &sourceStream)
+ : source(sourceStream)
+{
+ closed = false;
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int BasicInputStream::available()
+{
+ if (closed)
+ return 0;
+ return source.available();
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void BasicInputStream::close()
+{
+ if (closed)
+ return;
+ source.close();
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int BasicInputStream::get()
+{
+ if (closed)
+ return -1;
+ return source.get();
+}
+
+
+
+//#########################################################################
+//# B A S I C O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+BasicOutputStream::BasicOutputStream(OutputStream &destinationStream)
+ : destination(destinationStream)
+{
+ closed = false;
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void BasicOutputStream::close()
+{
+ if (closed)
+ return;
+ destination.close();
+ closed = true;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void BasicOutputStream::flush()
+{
+ if (closed)
+ return;
+ destination.flush();
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int BasicOutputStream::put(char ch)
+{
+ if (closed)
+ return -1;
+ destination.put(ch);
+ return 1;
+}
+
+
+
+//#########################################################################
+//# B A S I C R E A D E R
+//#########################################################################
+/**
+ *
+ */
+BasicReader::BasicReader(Reader &sourceReader)
+{
+ source = &sourceReader;
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this reader without blocking by the next caller of a method for
+ * this reader.
+ */
+int BasicReader::available()
+{
+ if (source)
+ return source->available();
+ else
+ return 0;
+}
+
+
+/**
+ * Closes this reader and releases any system resources
+ * associated with the reader.
+ */
+void BasicReader::close()
+{
+ if (source)
+ source->close();
+}
+
+/**
+ * Reads the next byte of data from the reader.
+ */
+char BasicReader::get()
+{
+ if (source)
+ return source->get();
+ else
+ return (char)-1;
+}
+
+
+/**
+ * Reads a line of data from the reader.
+ */
+Glib::ustring BasicReader::readLine()
+{
+ Glib::ustring str;
+ while (available() > 0)
+ {
+ char ch = get();
+ if (ch == '\n')
+ break;
+ str.push_back(ch);
+ }
+ return str;
+}
+
+/**
+ * Reads a line of data from the reader.
+ */
+Glib::ustring BasicReader::readWord()
+{
+ Glib::ustring str;
+ while (available() > 0)
+ {
+ char ch = get();
+ if (!std::isprint(ch))
+ break;
+ str.push_back(ch);
+ }
+ return str;
+}
+
+
+static bool getLong(Glib::ustring &str, long *val)
+{
+ const char *begin = str.raw().c_str();
+ char *end;
+ long ival = strtol(begin, &end, 10);
+ if (str == end)
+ return false;
+ *val = ival;
+ return true;
+}
+
+static bool getULong(Glib::ustring &str, unsigned long *val)
+{
+ const char *begin = str.raw().c_str();
+ char *end;
+ unsigned long ival = strtoul(begin, &end, 10);
+ if (str == end)
+ return false;
+ *val = ival;
+ return true;
+}
+
+static bool getDouble(Glib::ustring &str, double *val)
+{
+ const char *begin = str.raw().c_str();
+ char *end;
+ double ival = strtod(begin, &end);
+ if (str == end)
+ return false;
+ *val = ival;
+ return true;
+}
+
+
+
+const Reader &BasicReader::readBool (bool& val )
+{
+ Glib::ustring buf = readWord();
+ if (buf == "true")
+ val = true;
+ else
+ val = false;
+ return *this;
+}
+
+const Reader &BasicReader::readShort (short& val )
+{
+ Glib::ustring buf = readWord();
+ long ival;
+ if (getLong(buf, &ival))
+ val = (short) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readUnsignedShort (unsigned short& val )
+{
+ Glib::ustring buf = readWord();
+ unsigned long ival;
+ if (getULong(buf, &ival))
+ val = (unsigned short) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readInt (int& val )
+{
+ Glib::ustring buf = readWord();
+ long ival;
+ if (getLong(buf, &ival))
+ val = (int) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readUnsignedInt (unsigned int& val )
+{
+ Glib::ustring buf = readWord();
+ unsigned long ival;
+ if (getULong(buf, &ival))
+ val = (unsigned int) ival;
+ return *this;
+}
+
+const Reader &BasicReader::readLong (long& val )
+{
+ Glib::ustring buf = readWord();
+ long ival;
+ if (getLong(buf, &ival))
+ val = ival;
+ return *this;
+}
+
+const Reader &BasicReader::readUnsignedLong (unsigned long& val )
+{
+ Glib::ustring buf = readWord();
+ unsigned long ival;
+ if (getULong(buf, &ival))
+ val = ival;
+ return *this;
+}
+
+const Reader &BasicReader::readFloat (float& val )
+{
+ Glib::ustring buf = readWord();
+ double ival;
+ if (getDouble(buf, &ival))
+ val = (float)ival;
+ return *this;
+}
+
+const Reader &BasicReader::readDouble (double& val )
+{
+ Glib::ustring buf = readWord();
+ double ival;
+ if (getDouble(buf, &ival))
+ val = ival;
+ return *this;
+}
+
+
+
+//#########################################################################
+//# I N P U T S T R E A M R E A D E R
+//#########################################################################
+
+
+InputStreamReader::InputStreamReader(InputStream &inputStreamSource)
+ : inputStream(inputStreamSource)
+{
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void InputStreamReader::close()
+{
+ inputStream.close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+int InputStreamReader::available()
+{
+ return inputStream.available();
+}
+
+/**
+ * Overloaded to receive its bytes from an InputStream
+ * rather than a Reader
+ */
+char InputStreamReader::get()
+{
+ char ch = inputStream.get();
+ return ch;
+}
+
+
+
+//#########################################################################
+//# S T D R E A D E R
+//#########################################################################
+
+
+/**
+ *
+ */
+StdReader::StdReader()
+{
+ inputStream = new StdInputStream();
+}
+
+/**
+ *
+ */
+StdReader::~StdReader()
+{
+ delete inputStream;
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void StdReader::close()
+{
+ inputStream->close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+int StdReader::available()
+{
+ return inputStream->available();
+}
+
+/**
+ * Overloaded to receive its bytes from an InputStream
+ * rather than a Reader
+ */
+char StdReader::get()
+{
+ char ch = inputStream->get();
+ return ch;
+}
+
+
+
+
+
+//#########################################################################
+//# B A S I C W R I T E R
+//#########################################################################
+
+/**
+ *
+ */
+BasicWriter::BasicWriter(Writer &destinationWriter)
+{
+ destination = &destinationWriter;
+}
+
+/**
+ * Closes this writer and releases any system resources
+ * associated with this writer.
+ */
+void BasicWriter::close()
+{
+ if (destination)
+ destination->close();
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void BasicWriter::flush()
+{
+ if (destination)
+ destination->flush();
+}
+
+/**
+ * Writes the specified byte to this output writer.
+ */
+void BasicWriter::put(char ch)
+{
+ if (destination)
+ destination->put(ch);
+}
+
+/**
+ * Provide printf()-like formatting
+ */
+Writer &BasicWriter::printf(char const *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gchar *buf = g_strdup_vprintf(fmt, args);
+ va_end(args);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+/**
+ * Writes the specified character to this output writer.
+ */
+Writer &BasicWriter::writeChar(char ch)
+{
+ put(ch);
+ return *this;
+}
+
+
+/**
+ * Writes the specified unicode string to this output writer.
+ */
+Writer &BasicWriter::writeUString(const Glib::ustring &str)
+{
+ writeStdString(str.raw());
+ return *this;
+}
+
+/**
+ * Writes the specified standard string to this output writer.
+ */
+Writer &BasicWriter::writeStdString(const std::string &str)
+{
+ for (char it : str) {
+ put(it);
+ }
+ return *this;
+}
+
+/**
+ * Writes the specified character string to this output writer.
+ */
+Writer &BasicWriter::writeString(const char *str)
+{
+ std::string tmp;
+ if (str)
+ tmp = str;
+ else
+ tmp = "null";
+ writeStdString(tmp);
+ return *this;
+}
+
+
+
+
+/**
+ *
+ */
+Writer &BasicWriter::writeBool (bool val )
+{
+ if (val)
+ writeString("true");
+ else
+ writeString("false");
+ return *this;
+}
+
+
+/**
+ *
+ */
+Writer &BasicWriter::writeShort (short val )
+{
+ gchar *buf = g_strdup_printf("%d", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+
+
+/**
+ *
+ */
+Writer &BasicWriter::writeUnsignedShort (unsigned short val )
+{
+ gchar *buf = g_strdup_printf("%u", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeInt (int val)
+{
+ gchar *buf = g_strdup_printf("%d", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeUnsignedInt (unsigned int val)
+{
+ gchar *buf = g_strdup_printf("%u", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeLong (long val)
+{
+ gchar *buf = g_strdup_printf("%ld", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeUnsignedLong(unsigned long val)
+{
+ gchar *buf = g_strdup_printf("%lu", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeFloat(float val)
+{
+#if 1
+ gchar *buf = g_strdup_printf("%8.3f", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+#else
+ std::string tmp = ftos(val, 'g', 8, 3, 0);
+ writeStdString(tmp);
+#endif
+ return *this;
+}
+
+/**
+ *
+ */
+Writer &BasicWriter::writeDouble(double val)
+{
+#if 1
+ gchar *buf = g_strdup_printf("%8.3f", val);
+ if (buf) {
+ writeString(buf);
+ g_free(buf);
+ }
+#else
+ std::string tmp = ftos(val, 'g', 8, 3, 0);
+ writeStdString(tmp);
+#endif
+ return *this;
+}
+
+
+Writer& operator<< (Writer &writer, char val)
+ { return writer.writeChar(val); }
+
+Writer& operator<< (Writer &writer, Glib::ustring &val)
+ { return writer.writeUString(val); }
+
+Writer& operator<< (Writer &writer, std::string &val)
+ { return writer.writeStdString(val); }
+
+Writer& operator<< (Writer &writer, char const *val)
+ { return writer.writeString(val); }
+
+Writer& operator<< (Writer &writer, bool val)
+ { return writer.writeBool(val); }
+
+Writer& operator<< (Writer &writer, short val)
+ { return writer.writeShort(val); }
+
+Writer& operator<< (Writer &writer, unsigned short val)
+ { return writer.writeUnsignedShort(val); }
+
+Writer& operator<< (Writer &writer, int val)
+ { return writer.writeInt(val); }
+
+Writer& operator<< (Writer &writer, unsigned int val)
+ { return writer.writeUnsignedInt(val); }
+
+Writer& operator<< (Writer &writer, long val)
+ { return writer.writeLong(val); }
+
+Writer& operator<< (Writer &writer, unsigned long val)
+ { return writer.writeUnsignedLong(val); }
+
+Writer& operator<< (Writer &writer, float val)
+ { return writer.writeFloat(val); }
+
+Writer& operator<< (Writer &writer, double val)
+ { return writer.writeDouble(val); }
+
+
+
+//#########################################################################
+//# O U T P U T S T R E A M W R I T E R
+//#########################################################################
+
+
+OutputStreamWriter::OutputStreamWriter(OutputStream &outputStreamDest)
+ : outputStream(outputStreamDest)
+{
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void OutputStreamWriter::close()
+{
+ flush();
+ outputStream.close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+void OutputStreamWriter::flush()
+{
+ outputStream.flush();
+}
+
+/**
+ * Overloaded to redirect the output chars from the next Writer
+ * in the chain to an OutputStream instead.
+ */
+void OutputStreamWriter::put(char ch)
+{
+ outputStream.put(ch);
+}
+
+//#########################################################################
+//# S T D W R I T E R
+//#########################################################################
+
+
+/**
+ *
+ */
+StdWriter::StdWriter()
+{
+ outputStream = new StdOutputStream();
+}
+
+
+/**
+ *
+ */
+StdWriter::~StdWriter()
+{
+ delete outputStream;
+}
+
+
+
+/**
+ * Close the underlying OutputStream
+ */
+void StdWriter::close()
+{
+ flush();
+ outputStream->close();
+}
+
+/**
+ * Flush the underlying OutputStream
+ */
+void StdWriter::flush()
+{
+ outputStream->flush();
+}
+
+/**
+ * Overloaded to redirect the output chars from the next Writer
+ * in the chain to an OutputStream instead.
+ */
+void StdWriter::put(char ch)
+{
+ outputStream->put(ch);
+}
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/inkscapestream.h b/src/io/stream/inkscapestream.h
new file mode 100644
index 0000000..b20899a
--- /dev/null
+++ b/src/io/stream/inkscapestream.h
@@ -0,0 +1,670 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_INKSCAPESTREAM_H
+#define SEEN_INKSCAPE_IO_INKSCAPESTREAM_H
+/*
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdio>
+#include <glibmm/ustring.h>
+
+#ifdef printf
+#undef printf
+#endif
+
+namespace Inkscape
+{
+namespace IO
+{
+
+class StreamException : public std::exception
+{
+public:
+ StreamException(Glib::ustring theReason) noexcept
+ : reason(std::move(theReason))
+ {}
+ ~StreamException() noexcept override
+ = default;
+ char const *what() const noexcept override
+ { return reason.c_str(); }
+
+private:
+ Glib::ustring reason;
+
+};
+
+//#########################################################################
+//# I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This interface is the base of all input stream classes. Users who wish
+ * to make an InputStream that is part of a chain should inherit from
+ * BasicInputStream. Inherit from this class to make a source endpoint,
+ * such as a URI or buffer.
+ *
+ */
+class InputStream
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ InputStream() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~InputStream() = default;
+
+ /**
+ * Return the number of bytes that are currently available
+ * to be read
+ */
+ virtual int available() = 0;
+
+ /**
+ * Do whatever it takes to 'close' this input stream
+ * The most likely implementation of this method will be
+ * for endpoints that use a resource for their data.
+ */
+ virtual void close() = 0;
+
+ /**
+ * Read one byte from this input stream. This is a blocking
+ * call. If no data is currently available, this call will
+ * not return until it exists. If the user does not want
+ * their code to block, then the usual solution is:
+ * if (available() > 0)
+ * myChar = get();
+ * This call returns -1 on end-of-file.
+ */
+ virtual int get() = 0;
+
+}; // class InputStream
+
+
+
+
+/**
+ * This is the class that most users should inherit, to provide
+ * their own streams.
+ *
+ */
+class BasicInputStream : public InputStream
+{
+
+public:
+
+ BasicInputStream(InputStream &sourceStream);
+
+ ~BasicInputStream() override = default;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+protected:
+
+ bool closed;
+
+ InputStream &source;
+
+private:
+
+
+}; // class BasicInputStream
+
+
+
+/**
+ * Convenience class for reading from standard input
+ */
+class StdInputStream : public InputStream
+{
+public:
+
+ int available() override
+ { return 0; }
+
+ void close() override
+ { /* do nothing */ }
+
+ int get() override
+ { return getchar(); }
+
+};
+
+
+
+
+
+
+//#########################################################################
+//# O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This interface is the base of all input stream classes. Users who wish
+ * to make an OutputStream that is part of a chain should inherit from
+ * BasicOutputStream. Inherit from this class to make a destination endpoint,
+ * such as a URI or buffer.
+ */
+class OutputStream
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ OutputStream() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~OutputStream() = default;
+
+ /**
+ * This call should
+ * 1. flush itself
+ * 2. close itself
+ * 3. close the destination stream
+ */
+ virtual void close() = 0;
+
+ /**
+ * This call should push any pending data it might have to
+ * the destination stream. It should NOT call flush() on
+ * the destination stream.
+ */
+ virtual void flush() = 0;
+
+ /**
+ * Send one byte to the destination stream.
+ */
+ virtual int put(char ch) = 0;
+
+
+}; // class OutputStream
+
+
+/**
+ * This is the class that most users should inherit, to provide
+ * their own output streams.
+ */
+class BasicOutputStream : public OutputStream
+{
+
+public:
+
+ BasicOutputStream(OutputStream &destinationStream);
+
+ ~BasicOutputStream() override = default;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+protected:
+
+ bool closed;
+
+ OutputStream &destination;
+
+
+}; // class BasicOutputStream
+
+
+
+/**
+ * Convenience class for writing to standard output
+ */
+class StdOutputStream : public OutputStream
+{
+public:
+
+ void close() override
+ { }
+
+ void flush() override
+ { }
+
+ int put(char ch) override
+ {return putchar(ch); }
+
+};
+
+
+
+
+//#########################################################################
+//# R E A D E R
+//#########################################################################
+
+
+/**
+ * This interface and its descendants are for unicode character-oriented input
+ *
+ */
+class Reader
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ Reader() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~Reader() = default;
+
+
+ virtual int available() = 0;
+
+ virtual void close() = 0;
+
+ virtual char get() = 0;
+
+ virtual Glib::ustring readLine() = 0;
+
+ virtual Glib::ustring readWord() = 0;
+
+ /* Input formatting */
+ virtual const Reader& readBool (bool& val ) = 0;
+ virtual const Reader& operator>> (bool& val ) = 0;
+
+ virtual const Reader& readShort (short &val) = 0;
+ virtual const Reader& operator>> (short &val) = 0;
+
+ virtual const Reader& readUnsignedShort (unsigned short &val) = 0;
+ virtual const Reader& operator>> (unsigned short &val) = 0;
+
+ virtual const Reader& readInt (int &val) = 0;
+ virtual const Reader& operator>> (int &val) = 0;
+
+ virtual const Reader& readUnsignedInt (unsigned int &val) = 0;
+ virtual const Reader& operator>> (unsigned int &val) = 0;
+
+ virtual const Reader& readLong (long &val) = 0;
+ virtual const Reader& operator>> (long &val) = 0;
+
+ virtual const Reader& readUnsignedLong (unsigned long &val) = 0;
+ virtual const Reader& operator>> (unsigned long &val) = 0;
+
+ virtual const Reader& readFloat (float &val) = 0;
+ virtual const Reader& operator>> (float &val) = 0;
+
+ virtual const Reader& readDouble (double &val) = 0;
+ virtual const Reader& operator>> (double &val) = 0;
+
+}; // interface Reader
+
+
+
+/**
+ * This class and its descendants are for unicode character-oriented input
+ *
+ */
+class BasicReader : public Reader
+{
+
+public:
+
+ BasicReader(Reader &sourceStream);
+
+ ~BasicReader() override = default;
+
+ int available() override;
+
+ void close() override;
+
+ char get() override;
+
+ Glib::ustring readLine() override;
+
+ Glib::ustring readWord() override;
+
+ /* Input formatting */
+ const Reader& readBool (bool& val ) override;
+ const Reader& operator>> (bool& val ) override
+ { return readBool(val); }
+
+ const Reader& readShort (short &val) override;
+ const Reader& operator>> (short &val) override
+ { return readShort(val); }
+
+ const Reader& readUnsignedShort (unsigned short &val) override;
+ const Reader& operator>> (unsigned short &val) override
+ { return readUnsignedShort(val); }
+
+ const Reader& readInt (int &val) override;
+ const Reader& operator>> (int &val) override
+ { return readInt(val); }
+
+ const Reader& readUnsignedInt (unsigned int &val) override;
+ const Reader& operator>> (unsigned int &val) override
+ { return readUnsignedInt(val); }
+
+ const Reader& readLong (long &val) override;
+ const Reader& operator>> (long &val) override
+ { return readLong(val); }
+
+ const Reader& readUnsignedLong (unsigned long &val) override;
+ const Reader& operator>> (unsigned long &val) override
+ { return readUnsignedLong(val); }
+
+ const Reader& readFloat (float &val) override;
+ const Reader& operator>> (float &val) override
+ { return readFloat(val); }
+
+ const Reader& readDouble (double &val) override;
+ const Reader& operator>> (double &val) override
+ { return readDouble(val); }
+
+
+protected:
+
+ Reader *source;
+
+ BasicReader()
+ { source = nullptr; }
+
+private:
+
+}; // class BasicReader
+
+
+
+/**
+ * Class for placing a Reader on an open InputStream
+ *
+ */
+class InputStreamReader : public BasicReader
+{
+public:
+
+ InputStreamReader(InputStream &inputStreamSource);
+
+ /*Overload these 3 for your implementation*/
+ int available() override;
+
+ void close() override;
+
+ char get() override;
+
+
+private:
+
+ InputStream &inputStream;
+
+
+};
+
+/**
+ * Convenience class for reading formatted from standard input
+ *
+ */
+class StdReader : public BasicReader
+{
+public:
+
+ StdReader();
+
+ ~StdReader() override;
+
+ /*Overload these 3 for your implementation*/
+ int available() override;
+
+ void close() override;
+
+ char get() override;
+
+
+private:
+
+ InputStream *inputStream;
+
+
+};
+
+
+
+
+
+//#########################################################################
+//# W R I T E R
+//#########################################################################
+
+/**
+ * This interface and its descendants are for unicode character-oriented output
+ *
+ */
+class Writer
+{
+
+public:
+
+ /**
+ * Constructor.
+ */
+ Writer() = default;
+
+ /**
+ * Destructor
+ */
+ virtual ~Writer() = default;
+
+ virtual void close() = 0;
+
+ virtual void flush() = 0;
+
+ virtual void put(char ch) = 0;
+
+ /* Formatted output */
+ virtual Writer& printf(char const *fmt, ...) G_GNUC_PRINTF(2,3) = 0;
+
+ virtual Writer& writeChar(char val) = 0;
+
+ virtual Writer& writeUString(const Glib::ustring &val) = 0;
+
+ virtual Writer& writeStdString(const std::string &val) = 0;
+
+ virtual Writer& writeString(const char *str) = 0;
+
+ virtual Writer& writeBool (bool val ) = 0;
+
+ virtual Writer& writeShort (short val ) = 0;
+
+ virtual Writer& writeUnsignedShort (unsigned short val ) = 0;
+
+ virtual Writer& writeInt (int val ) = 0;
+
+ virtual Writer& writeUnsignedInt (unsigned int val ) = 0;
+
+ virtual Writer& writeLong (long val ) = 0;
+
+ virtual Writer& writeUnsignedLong (unsigned long val ) = 0;
+
+ virtual Writer& writeFloat (float val ) = 0;
+
+ virtual Writer& writeDouble (double val ) = 0;
+
+
+
+}; // interface Writer
+
+
+/**
+ * This class and its descendants are for unicode character-oriented output
+ *
+ */
+class BasicWriter : public Writer
+{
+
+public:
+
+ BasicWriter(Writer &destinationWriter);
+
+ ~BasicWriter() override = default;
+
+ /*Overload these 3 for your implementation*/
+ void close() override;
+
+ void flush() override;
+
+ void put(char ch) override;
+
+
+
+ /* Formatted output */
+ Writer &printf(char const *fmt, ...) override G_GNUC_PRINTF(2,3);
+
+ Writer& writeChar(char val) override;
+
+ Writer& writeUString(const Glib::ustring &val) override;
+
+ Writer& writeStdString(const std::string &val) override;
+
+ Writer& writeString(const char *str) override;
+
+ Writer& writeBool (bool val ) override;
+
+ Writer& writeShort (short val ) override;
+
+ Writer& writeUnsignedShort (unsigned short val ) override;
+
+ Writer& writeInt (int val ) override;
+
+ Writer& writeUnsignedInt (unsigned int val ) override;
+
+ Writer& writeLong (long val ) override;
+
+ Writer& writeUnsignedLong (unsigned long val ) override;
+
+ Writer& writeFloat (float val ) override;
+
+ Writer& writeDouble (double val ) override;
+
+
+protected:
+
+ Writer *destination;
+
+ BasicWriter()
+ { destination = nullptr; }
+
+private:
+
+}; // class BasicWriter
+
+
+
+Writer& operator<< (Writer &writer, char val);
+
+Writer& operator<< (Writer &writer, Glib::ustring &val);
+
+Writer& operator<< (Writer &writer, std::string &val);
+
+Writer& operator<< (Writer &writer, char const *val);
+
+Writer& operator<< (Writer &writer, bool val);
+
+Writer& operator<< (Writer &writer, short val);
+
+Writer& operator<< (Writer &writer, unsigned short val);
+
+Writer& operator<< (Writer &writer, int val);
+
+Writer& operator<< (Writer &writer, unsigned int val);
+
+Writer& operator<< (Writer &writer, long val);
+
+Writer& operator<< (Writer &writer, unsigned long val);
+
+Writer& operator<< (Writer &writer, float val);
+
+Writer& operator<< (Writer &writer, double val);
+
+
+
+
+/**
+ * Class for placing a Writer on an open OutputStream
+ *
+ */
+class OutputStreamWriter : public BasicWriter
+{
+public:
+
+ OutputStreamWriter(OutputStream &outputStreamDest);
+
+ /*Overload these 3 for your implementation*/
+ void close() override;
+
+ void flush() override;
+
+ void put(char ch) override;
+
+
+private:
+
+ OutputStream &outputStream;
+
+
+};
+
+
+/**
+ * Convenience class for writing to standard output
+ */
+class StdWriter : public BasicWriter
+{
+public:
+ StdWriter();
+
+ ~StdWriter() override;
+
+
+ void close() override;
+
+
+ void flush() override;
+
+
+ void put(char ch) override;
+
+
+private:
+
+ OutputStream *outputStream;
+
+};
+
+//#########################################################################
+//# U T I L I T Y
+//#########################################################################
+
+void pipeStream(InputStream &source, OutputStream &dest);
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif // SEEN_INKSCAPE_IO_INKSCAPESTREAM_H
diff --git a/src/io/stream/stringstream.cpp b/src/io/stream/stringstream.cpp
new file mode 100644
index 0000000..0259869
--- /dev/null
+++ b/src/io/stream/stringstream.cpp
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Our base String stream classes. We implement these to
+ * be based on Glib::ustring
+ *
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "stringstream.h"
+
+namespace Inkscape
+{
+namespace IO
+{
+
+
+//#########################################################################
+//# S T R I N G I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+StringInputStream::StringInputStream(Glib::ustring &sourceString)
+ : buffer(sourceString)
+{
+ position = 0;
+}
+
+/**
+ *
+ */
+StringInputStream::~StringInputStream()
+= default;
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int StringInputStream::available()
+{
+ return buffer.size() - position;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void StringInputStream::close()
+{
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int StringInputStream::get()
+{
+ if (position >= (int)buffer.size())
+ return -1;
+ int ch = (int) buffer[position++];
+ return ch;
+}
+
+
+
+
+//#########################################################################
+//# S T R I N G O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+StringOutputStream::StringOutputStream()
+= default;
+
+/**
+ *
+ */
+StringOutputStream::~StringOutputStream()
+= default;
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void StringOutputStream::close()
+{
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void StringOutputStream::flush()
+{
+ //nothing to do
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int StringOutputStream::put(char ch)
+{
+ buffer.push_back(ch);
+ return 1;
+}
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/stringstream.h b/src/io/stream/stringstream.h
new file mode 100644
index 0000000..3afb9a8
--- /dev/null
+++ b/src/io/stream/stringstream.h
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef __INKSCAPE_IO_STRINGSTREAM_H__
+#define __INKSCAPE_IO_STRINGSTREAM_H__
+
+#include <glibmm/ustring.h>
+
+#include "inkscapestream.h"
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+
+//#########################################################################
+//# S T R I N G I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for reading character from a Glib::ustring
+ *
+ */
+class StringInputStream : public InputStream
+{
+
+public:
+
+ StringInputStream(Glib::ustring &sourceString);
+
+ ~StringInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+private:
+
+ Glib::ustring &buffer;
+
+ long position;
+
+}; // class StringInputStream
+
+
+
+
+//#########################################################################
+//# S T R I N G O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for sending a stream to a Glib::ustring
+ *
+ */
+class StringOutputStream : public OutputStream
+{
+
+public:
+
+ StringOutputStream();
+
+ ~StringOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+ virtual Glib::ustring &getString()
+ { return buffer; }
+
+ virtual void clear()
+ { buffer = ""; }
+
+private:
+
+ Glib::ustring buffer;
+
+
+}; // class StringOutputStream
+
+
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+
+#endif /* __INKSCAPE_IO_STRINGSTREAM_H__ */
diff --git a/src/io/stream/uristream.cpp b/src/io/stream/uristream.cpp
new file mode 100644
index 0000000..734b8fa
--- /dev/null
+++ b/src/io/stream/uristream.cpp
@@ -0,0 +1,171 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Our base String stream classes. We implement these to
+ * be based on Glib::ustring
+ *
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "uristream.h"
+#include "io/sys.h"
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# F I L E I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+FileInputStream::FileInputStream(FILE *source)
+ : inf(source)
+{
+ if (!inf) {
+ Glib::ustring err = "FileInputStream passed NULL";
+ throw StreamException(err);
+ }
+}
+
+/**
+ *
+ */
+FileInputStream::~FileInputStream()
+{
+ close();
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int FileInputStream::available()
+{
+ return 0;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void FileInputStream::close()
+{
+ if (!inf)
+ return;
+ fflush(inf);
+ fclose(inf);
+ inf=nullptr;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int FileInputStream::get()
+{
+ int retVal = -1;
+ if (!inf || feof(inf))
+ {
+ retVal = -1;
+ }
+ else
+ {
+ retVal = fgetc(inf);
+ }
+
+ return retVal;
+}
+
+
+
+
+//#########################################################################
+//# F I L E O U T P U T S T R E A M
+//#########################################################################
+
+FileOutputStream::FileOutputStream(FILE *fp)
+ : ownsFile(false)
+ , outf(fp)
+{
+ if (!outf) {
+ Glib::ustring err = "FileOutputStream given null file ";
+ throw StreamException(err);
+ }
+}
+
+/**
+ *
+ */
+FileOutputStream::~FileOutputStream()
+{
+ close();
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void FileOutputStream::close()
+{
+ if (!outf)
+ return;
+ fflush(outf);
+ if ( ownsFile )
+ fclose(outf);
+ outf=nullptr;
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void FileOutputStream::flush()
+{
+ if (!outf)
+ return;
+ fflush(outf);
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int FileOutputStream::put(char ch)
+{
+ unsigned char uch;
+
+ if (!outf)
+ return -1;
+ uch = (unsigned char)(ch & 0xff);
+ if (fputc(uch, outf) == EOF) {
+ Glib::ustring err = "ERROR writing to file ";
+ throw StreamException(err);
+ }
+
+ return 1;
+}
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/stream/uristream.h b/src/io/stream/uristream.h
new file mode 100644
index 0000000..f0544d8
--- /dev/null
+++ b/src/io/stream/uristream.h
@@ -0,0 +1,101 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_URISTREAM_H
+#define SEEN_INKSCAPE_IO_URISTREAM_H
+/**
+ * @file
+ * This should be the only way that we provide sources/sinks
+ * to any input/output stream.
+ */
+/*
+ * Authors:
+ * Bob Jamison <rjamison@titan.com>
+ *
+ * Copyright (C) 2004 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "object/uri.h"
+
+#include "inkscapestream.h"
+
+
+namespace Inkscape
+{
+
+class URI;
+
+namespace IO
+{
+
+//#########################################################################
+//# F I L E I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for receiving a stream of data from a file
+ */
+class FileInputStream : public InputStream
+{
+
+public:
+ FileInputStream(FILE *source);
+
+ ~FileInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+private:
+ FILE *inf; //for file: uris
+
+}; // class FileInputStream
+
+
+
+
+//#########################################################################
+//# F I L E O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for sending a stream to a destination file
+ */
+class FileOutputStream : public OutputStream
+{
+
+public:
+
+ FileOutputStream(FILE *fp);
+
+ ~FileOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+private:
+
+ bool ownsFile;
+
+ FILE *outf; //for file: uris
+
+}; // class FileOutputStream
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif // SEEN_INKSCAPE_IO_URISTREAM_H
+
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/io/stream/xsltstream.cpp b/src/io/stream/xsltstream.cpp
new file mode 100644
index 0000000..882db30
--- /dev/null
+++ b/src/io/stream/xsltstream.cpp
@@ -0,0 +1,248 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * XSL Transforming input and output classes
+ *
+ * Authors:
+ * Bob Jamison <ishmalius@gmail.com>
+ *
+ * Copyright (C) 2004-2008 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "xsltstream.h"
+#include "stringstream.h"
+#include <libxslt/transform.h>
+
+
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# X S L T S T Y L E S H E E T
+//#########################################################################
+
+/**
+ *
+ */
+XsltStyleSheet::XsltStyleSheet(InputStream &xsltSource)
+
+ : stylesheet(nullptr)
+{
+ if (!read(xsltSource)) {
+ throw StreamException("read failed");
+ }
+}
+
+/**
+ *
+ */
+XsltStyleSheet::XsltStyleSheet()
+ : stylesheet(nullptr)
+{
+}
+
+
+
+/**
+ *
+ */
+bool XsltStyleSheet::read(InputStream &xsltSource)
+{
+ StringOutputStream outs;
+ pipeStream(xsltSource, outs);
+ std::string strBuf = outs.getString().raw();
+ xmlDocPtr doc = xmlParseMemory(strBuf.c_str(), strBuf.size());
+ stylesheet = xsltParseStylesheetDoc(doc);
+ //following not necessary. handled by xsltFreeStylesheet(stylesheet);
+ //xmlFreeDoc(doc);
+ if (!stylesheet)
+ return false;
+ return true;
+}
+
+
+/**
+ *
+ */
+XsltStyleSheet::~XsltStyleSheet()
+{
+ if (stylesheet)
+ xsltFreeStylesheet(stylesheet);
+}
+
+
+
+//#########################################################################
+//# X S L T I N P U T S T R E A M
+//#########################################################################
+
+
+/**
+ *
+ */
+XsltInputStream::XsltInputStream(InputStream &xmlSource, XsltStyleSheet &sheet)
+ : BasicInputStream(xmlSource), stylesheet(sheet)
+{
+ //Load the data
+ StringOutputStream outs;
+ pipeStream(source, outs);
+ std::string strBuf = outs.getString().raw();
+
+ //Do the processing
+ const char *params[1];
+ params[0] = nullptr;
+ xmlDocPtr srcDoc = xmlParseMemory(strBuf.c_str(), strBuf.size());
+ xmlDocPtr resDoc = xsltApplyStylesheet(stylesheet.stylesheet, srcDoc, params);
+ xmlDocDumpFormatMemory(resDoc, &outbuf, &outsize, 1);
+ outpos = 0;
+
+ //Free our mem
+ xmlFreeDoc(resDoc);
+ xmlFreeDoc(srcDoc);
+}
+
+/**
+ *
+ */
+XsltInputStream::~XsltInputStream()
+{
+ xmlFree(outbuf);
+}
+
+/**
+ * Returns the number of bytes that can be read (or skipped over) from
+ * this input stream without blocking by the next caller of a method for
+ * this input stream.
+ */
+int XsltInputStream::available()
+{
+ return outsize - outpos;
+}
+
+
+/**
+ * Closes this input stream and releases any system resources
+ * associated with the stream.
+ */
+void XsltInputStream::close()
+{
+ closed = true;
+}
+
+/**
+ * Reads the next byte of data from the input stream. -1 if EOF
+ */
+int XsltInputStream::get()
+{
+ if (closed)
+ return -1;
+ if (outpos >= outsize)
+ return -1;
+ int ch = (int) outbuf[outpos++];
+ return ch;
+}
+
+
+
+
+
+
+//#########################################################################
+//# X S L T O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ *
+ */
+XsltOutputStream::XsltOutputStream(OutputStream &dest, XsltStyleSheet &sheet)
+ : BasicOutputStream(dest), stylesheet(sheet)
+{
+ flushed = false;
+}
+
+/**
+ *
+ */
+XsltOutputStream::~XsltOutputStream()
+{
+ //do not automatically close
+}
+
+/**
+ * Closes this output stream and releases any system resources
+ * associated with this stream.
+ */
+void XsltOutputStream::close()
+{
+ flush();
+ destination.close();
+}
+
+/**
+ * Flushes this output stream and forces any buffered output
+ * bytes to be written out.
+ */
+void XsltOutputStream::flush()
+{
+ if (flushed)
+ {
+ destination.flush();
+ return;
+ }
+
+ //Do the processing
+ xmlChar *resbuf;
+ int resSize;
+ const char *params[1];
+ params[0] = nullptr;
+ xmlDocPtr srcDoc = xmlParseMemory(outbuf.raw().c_str(), outbuf.size());
+ xmlDocPtr resDoc = xsltApplyStylesheet(stylesheet.stylesheet, srcDoc, params);
+ xmlDocDumpFormatMemory(resDoc, &resbuf, &resSize, 1);
+ /*
+ xmlErrorPtr err = xmlGetLastError();
+ if (err)
+ {
+ throw StreamException(err->message);
+ }
+ */
+
+ for (int i=0 ; i<resSize ; i++)
+ {
+ char ch = resbuf[i];
+ destination.put(ch);
+ }
+
+ //Free our mem
+ xmlFree(resbuf);
+ xmlFreeDoc(resDoc);
+ xmlFreeDoc(srcDoc);
+ destination.flush();
+ flushed = true;
+}
+
+/**
+ * Writes the specified byte to this output stream.
+ */
+int XsltOutputStream::put(char ch)
+{
+ outbuf.push_back(ch);
+ return 1;
+}
+
+
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+//#########################################################################
+//# E N D O F F I L E
+//#########################################################################
diff --git a/src/io/stream/xsltstream.h b/src/io/stream/xsltstream.h
new file mode 100644
index 0000000..7410ddc
--- /dev/null
+++ b/src/io/stream/xsltstream.h
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_IO_XSLTSTREAM_H
+#define SEEN_INKSCAPE_IO_XSLTSTREAM_H
+/**
+ * @file
+ * Xslt-enabled input and output streams.
+ */
+/*
+ * Authors:
+ * Bob Jamison <ishmalius@gmail.com>
+ *
+ * Copyright (C) 2004-2008 Inkscape.org
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "inkscapestream.h"
+
+#include <libxslt/xslt.h>
+#include <libxslt/xsltInternals.h>
+
+
+namespace Inkscape
+{
+namespace IO
+{
+
+//#########################################################################
+//# X S L T S T Y L E S H E E T
+//#########################################################################
+/**
+ * This is a container for reusing a loaded stylesheet
+ */
+class XsltStyleSheet
+{
+
+public:
+
+ /**
+ * Constructor with loading
+ */
+ XsltStyleSheet(InputStream &source);
+
+ /**
+ * Simple constructor, no loading
+ */
+ XsltStyleSheet();
+
+ /**
+ * Loader
+ */
+ bool read(InputStream &source);
+
+ /**
+ * Destructor
+ */
+ virtual ~XsltStyleSheet();
+
+ xsltStylesheetPtr stylesheet;
+
+
+}; // class XsltStyleSheet
+
+
+//#########################################################################
+//# X S L T I N P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for transforming stream input by a given stylesheet
+ */
+class XsltInputStream : public BasicInputStream
+{
+
+public:
+
+ XsltInputStream(InputStream &xmlSource, XsltStyleSheet &stylesheet);
+
+ ~XsltInputStream() override;
+
+ int available() override;
+
+ void close() override;
+
+ int get() override;
+
+
+private:
+
+ XsltStyleSheet &stylesheet;
+
+ xmlChar *outbuf;
+ int outsize;
+ int outpos;
+
+}; // class XsltInputStream
+
+
+
+
+//#########################################################################
+//# X S L T O U T P U T S T R E A M
+//#########################################################################
+
+/**
+ * This class is for transforming stream output by a given stylesheet
+ */
+class XsltOutputStream : public BasicOutputStream
+{
+
+public:
+
+ XsltOutputStream(OutputStream &destination, XsltStyleSheet &stylesheet);
+
+ ~XsltOutputStream() override;
+
+ void close() override;
+
+ void flush() override;
+
+ int put(char ch) override;
+
+private:
+
+ XsltStyleSheet &stylesheet;
+
+ Glib::ustring outbuf;
+
+ bool flushed;
+
+}; // class XsltOutputStream
+
+
+
+} // namespace IO
+} // namespace Inkscape
+
+
+#endif /* __INKSCAPE_IO_XSLTSTREAM_H__ */
diff --git a/src/io/sys.cpp b/src/io/sys.cpp
new file mode 100644
index 0000000..b06c638
--- /dev/null
+++ b/src/io/sys.cpp
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * System abstraction utility routines
+ *
+ * Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include <fstream>
+#ifdef _WIN32
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glibmm/ustring.h>
+
+#include "preferences.h"
+#include "sys.h"
+
+//#define INK_DUMP_FILENAME_CONV 1
+#undef INK_DUMP_FILENAME_CONV
+
+//#define INK_DUMP_FOPEN 1
+#undef INK_DUMP_FOPEN
+
+void dump_str(gchar const *str, gchar const *prefix);
+void dump_ustr(Glib::ustring const &ustr);
+
+extern guint update_in_progress;
+
+
+void Inkscape::IO::dump_fopen_call( char const *utf8name, char const *id )
+{
+#ifdef INK_DUMP_FOPEN
+ Glib::ustring str;
+ for ( int i = 0; utf8name[i]; i++ )
+ {
+ if ( utf8name[i] == '\\' )
+ {
+ str += "\\\\";
+ }
+ else if ( (utf8name[i] >= 0x20) && ((0x0ff & utf8name[i]) <= 0x7f) )
+ {
+ str += utf8name[i];
+ }
+ else
+ {
+ gchar tmp[32];
+ g_snprintf( tmp, sizeof(tmp), "\\x%02x", (0x0ff & utf8name[i]) );
+ str += tmp;
+ }
+ }
+ g_message( "fopen call %s for [%s]", id, str.data() );
+#else
+ (void)utf8name;
+ (void)id;
+#endif
+}
+
+FILE *Inkscape::IO::fopen_utf8name( char const *utf8name, char const *mode )
+{
+ FILE* fp = nullptr;
+
+ if (Glib::ustring( utf8name ) == Glib::ustring("-")) {
+ // user requests to use pipes
+
+ Glib::ustring how( mode );
+ if ( how.find("w") != Glib::ustring::npos ) {
+#ifdef _WIN32
+ setmode(fileno(stdout), O_BINARY);
+#endif
+ return stdout;
+ } else {
+ return stdin;
+ }
+ }
+
+ gchar *filename = g_filename_from_utf8( utf8name, -1, nullptr, nullptr, nullptr );
+ if ( filename )
+ {
+ // ensure we open the file in binary mode (not needed in POSIX but doesn't hurt either)
+ Glib::ustring how( mode );
+ if ( how.find("b") == Glib::ustring::npos )
+ {
+ how.append("b");
+ }
+ // when opening a file for writing: create parent directories if they don't exist already
+ if ( how.find("w") != Glib::ustring::npos )
+ {
+ gchar *dirname = g_path_get_dirname(utf8name);
+ if (g_mkdir_with_parents(dirname, 0777)) {
+ g_warning("Could not create directory '%s'", dirname);
+ }
+ g_free(dirname);
+ }
+ fp = g_fopen(filename, how.c_str());
+ g_free(filename);
+ filename = nullptr;
+ }
+ return fp;
+}
+
+
+int Inkscape::IO::mkdir_utf8name( char const *utf8name )
+{
+ int retval = -1;
+ gchar *filename = g_filename_from_utf8( utf8name, -1, nullptr, nullptr, nullptr );
+ if ( filename )
+ {
+ retval = g_mkdir(filename, S_IRWXU | S_IRGRP | S_IXGRP); // The mode argument is ignored on Windows.
+ g_free(filename);
+ filename = nullptr;
+ }
+ return retval;
+}
+
+bool Inkscape::IO::file_test( char const *utf8name, GFileTest test )
+{
+ bool exists = false;
+
+ // in case the file to check is a pipe it doesn't need to exist
+ if (g_strcmp0(utf8name, "-") == 0 && G_FILE_TEST_IS_REGULAR)
+ return true;
+
+ if ( utf8name ) {
+ gchar *filename = nullptr;
+ if (utf8name && !g_utf8_validate(utf8name, -1, nullptr)) {
+ /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable.
+ If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to
+ use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false)
+ to beginning of this function. */
+ filename = g_strdup(utf8name);
+ // Looks like g_get_home_dir isn't safe.
+ //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!");
+ } else {
+ filename = g_filename_from_utf8 ( utf8name, -1, nullptr, nullptr, nullptr );
+ }
+ if ( filename ) {
+ exists = g_file_test (filename, test);
+ g_free(filename);
+ filename = nullptr;
+ } else {
+ g_warning( "Unable to convert filename in IO:file_test" );
+ }
+ }
+
+ return exists;
+}
+
+bool Inkscape::IO::file_is_writable( char const *utf8name)
+{
+ bool success = true;
+
+ if ( utf8name) {
+ gchar *filename = nullptr;
+ if (utf8name && !g_utf8_validate(utf8name, -1, nullptr)) {
+ /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable.
+ If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to
+ use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false)
+ to beginning of this function. */
+ filename = g_strdup(utf8name);
+ // Looks like g_get_home_dir isn't safe.
+ //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!");
+ } else {
+ filename = g_filename_from_utf8 ( utf8name, -1, nullptr, nullptr, nullptr );
+ }
+ if ( filename ) {
+ GStatBuf st;
+ if (g_file_test (filename, G_FILE_TEST_EXISTS)){
+ if (g_lstat (filename, &st) == 0) {
+ success = ((st.st_mode & S_IWRITE) != 0);
+ }
+ }
+ g_free(filename);
+ filename = nullptr;
+ } else {
+ g_warning( "Unable to convert filename in IO:file_test" );
+ }
+ }
+
+ return success;
+}
+
+/**Checks if directory of file exists, useful
+ * because inkscape doesn't create directories.*/
+bool Inkscape::IO::file_directory_exists( char const *utf8name ){
+ bool exists = true;
+
+ if ( utf8name) {
+ gchar *filename = nullptr;
+ if (utf8name && !g_utf8_validate(utf8name, -1, nullptr)) {
+ /* FIXME: Trying to guess whether or not a filename is already in utf8 is unreliable.
+ If any callers pass non-utf8 data (e.g. using g_get_home_dir), then change caller to
+ use simple g_file_test. Then add g_return_val_if_fail(g_utf_validate(...), false)
+ to beginning of this function. */
+ filename = g_strdup(utf8name);
+ // Looks like g_get_home_dir isn't safe.
+ //g_warning("invalid UTF-8 detected internally. HUNT IT DOWN AND KILL IT!!!");
+ } else {
+ filename = g_filename_from_utf8 ( utf8name, -1, nullptr, nullptr, nullptr );
+ }
+ if ( filename ) {
+ gchar *dirname = g_path_get_dirname(filename);
+ exists = Inkscape::IO::file_test( dirname, G_FILE_TEST_EXISTS);
+ g_free(filename);
+ g_free(dirname);
+ filename = nullptr;
+ dirname = nullptr;
+ } else {
+ g_warning( "Unable to convert filename in IO:file_test" );
+ }
+ }
+
+ return exists;
+
+}
+
+/** Wrapper around g_dir_open, but taking a utf8name as first argument. */
+GDir *
+Inkscape::IO::dir_open(gchar const *const utf8name, guint const flags, GError **const error)
+{
+ gchar *const opsys_name = g_filename_from_utf8(utf8name, -1, nullptr, nullptr, error);
+ if (opsys_name) {
+ GDir *ret = g_dir_open(opsys_name, flags, error);
+ g_free(opsys_name);
+ return ret;
+ } else {
+ return nullptr;
+ }
+}
+
+/**
+ * Like g_dir_read_name, but returns a utf8name (which must be freed, unlike g_dir_read_name).
+ *
+ * N.B. Skips over any dir entries that fail to convert to utf8.
+ */
+gchar *
+Inkscape::IO::dir_read_utf8name(GDir *dir)
+{
+ for (;;) {
+ gchar const *const opsys_name = g_dir_read_name(dir);
+ if (!opsys_name) {
+ return nullptr;
+ }
+ gchar *utf8_name = g_filename_to_utf8(opsys_name, -1, nullptr, nullptr, nullptr);
+ if (utf8_name) {
+ return utf8_name;
+ }
+ }
+}
+
+
+gchar* Inkscape::IO::locale_to_utf8_fallback( const gchar *opsysstring,
+ gssize len,
+ gsize *bytes_read,
+ gsize *bytes_written,
+ GError **error )
+{
+ gchar *result = nullptr;
+ if ( opsysstring ) {
+ gchar *newFileName = g_locale_to_utf8( opsysstring, len, bytes_read, bytes_written, error );
+ if ( newFileName ) {
+ if ( !g_utf8_validate(newFileName, -1, nullptr) ) {
+ g_warning( "input filename did not yield UTF-8" );
+ g_free( newFileName );
+ } else {
+ result = newFileName;
+ }
+ newFileName = nullptr;
+ } else if ( g_utf8_validate(opsysstring, -1, nullptr) ) {
+ // This *might* be a case that we want
+ // g_warning( "input failed filename->utf8, fell back to original" );
+ // TODO handle cases when len >= 0
+ result = g_strdup( opsysstring );
+ } else {
+ gchar const *charset = nullptr;
+ g_get_charset(&charset);
+ g_warning( "input filename conversion failed for file with locale charset '%s'", charset );
+ }
+ }
+ return result;
+}
+
+void
+Inkscape::IO::spawn_async_with_pipes( const std::string& working_directory,
+ const std::vector<std::string>& argv,
+ Glib::SpawnFlags flags,
+ const sigc::slot<void ()>& child_setup,
+ Glib::Pid* child_pid,
+ int* standard_input,
+ int* standard_output,
+ int* standard_error)
+{
+ Glib::spawn_async_with_pipes(working_directory,
+ argv,
+ flags,
+ child_setup,
+ child_pid,
+ standard_input,
+ standard_output,
+ standard_error);
+}
+
+
+gchar* Inkscape::IO::sanitizeString( gchar const * str )
+{
+ gchar *result = nullptr;
+ if ( str ) {
+ if ( g_utf8_validate(str, -1, nullptr) ) {
+ result = g_strdup(str);
+ } else {
+ guchar scratch[8];
+ Glib::ustring buf;
+ guchar const *ptr = (guchar const*)str;
+ while ( *ptr )
+ {
+ if ( *ptr == '\\' )
+ {
+ buf.append("\\\\");
+ } else if ( *ptr < 0x80 ) {
+ buf += (char)(*ptr);
+ } else {
+ g_snprintf((gchar*)scratch, sizeof(scratch), "\\x%02x", *ptr);
+ buf.append((const char*)scratch);
+ }
+ ptr++;
+ }
+ result = g_strdup(buf.c_str());
+ }
+ }
+ return result;
+}
+
+/*
+ * Returns the file extension of a path/filename
+ */
+Glib::ustring Inkscape::IO::get_file_extension(Glib::ustring path)
+{
+ Glib::ustring::size_type loc = path.find_last_of(".");
+ return loc < path.size() ? path.substr(loc) : "";
+}
+
+/*
+ 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 :
diff --git a/src/io/sys.h b/src/io/sys.h
new file mode 100644
index 0000000..f2a7e1e
--- /dev/null
+++ b/src/io/sys.h
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_SYS_H
+#define SEEN_SYS_H
+
+/*
+ * System abstraction utility routines
+ *
+ * Authors:
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstdio>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <glib.h>
+#include <glibmm/spawn.h>
+#include <string>
+#include <vector>
+
+/*#####################
+## U T I L I T Y
+#####################*/
+
+namespace Inkscape {
+namespace IO {
+
+void dump_fopen_call( char const *utf8name, char const *id );
+
+FILE *fopen_utf8name( char const *utf8name, char const *mode );
+
+int mkdir_utf8name( char const *utf8name );
+
+bool file_test( char const *utf8name, GFileTest test );
+
+bool file_directory_exists( char const *utf8name );
+
+bool file_is_writable( char const *utf8name);
+
+GDir *dir_open(gchar const *utf8name, guint flags, GError **error);
+
+gchar *dir_read_utf8name(GDir *dir);
+
+gchar* locale_to_utf8_fallback( const gchar *opsysstring,
+ gssize len,
+ gsize *bytes_read,
+ gsize *bytes_written,
+ GError **error );
+
+gchar* sanitizeString( gchar const * str );
+
+void spawn_async_with_pipes (const std::string& working_directory,
+ const std::vector<std::string>& argv,
+ Glib::SpawnFlags flags,
+ const sigc::slot<void ()>& child_setup,
+ Glib::Pid* child_pid,
+ int* standard_input,
+ int* standard_output,
+ int* standard_error);
+
+Glib::ustring get_file_extension(Glib::ustring path);
+
+}
+}
+
+
+#endif // SEEN_SYS_H