diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 11:50:49 +0000 |
commit | c853ffb5b2f75f5a889ed2e3ef89b818a736e87a (patch) | |
tree | 7d13a0883bb7936b84d6ecdd7bc332b41ed04bee /src/io | |
parent | Initial commit. (diff) | |
download | inkscape-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')
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 |