diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:29:01 +0000 |
commit | 35a96bde514a8897f6f0fcc41c5833bf63df2e2a (patch) | |
tree | 657d15a03cc46bd099fc2c6546a7a4ad43815d9f /src/extension | |
parent | Initial commit. (diff) | |
download | inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.tar.xz inkscape-35a96bde514a8897f6f0fcc41c5833bf63df2e2a.zip |
Adding upstream version 1.0.2.upstream/1.0.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/extension')
254 files changed, 78526 insertions, 0 deletions
diff --git a/src/extension/CMakeLists.txt b/src/extension/CMakeLists.txt new file mode 100644 index 0000000..5708c2e --- /dev/null +++ b/src/extension/CMakeLists.txt @@ -0,0 +1,263 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +set(extension_SRC + db.cpp + dependency.cpp + effect.cpp + error-file.cpp + execution-env.cpp + extension.cpp + init.cpp + input.cpp + output.cpp + patheffect.cpp + print.cpp + system.cpp + timer.cpp + loader.cpp + + implementation/implementation.cpp + implementation/xslt.cpp + implementation/script.cpp + + internal/bluredge.cpp + internal/cairo-ps-out.cpp + internal/cairo-render-context.cpp + internal/cairo-renderer.cpp + internal/cairo-renderer-pdf-out.cpp + internal/emf-inout.cpp + internal/emf-print.cpp + internal/gdkpixbuf-input.cpp + internal/gimpgrad.cpp + internal/grid.cpp + internal/image-resolution.cpp + internal/latex-pstricks.cpp + internal/latex-pstricks-out.cpp + internal/metafile-inout.cpp + internal/metafile-print.cpp + internal/odf.cpp + internal/latex-text-renderer.cpp + internal/pov-out.cpp + internal/svg.cpp + internal/svgz.cpp + internal/text_reassemble.c + internal/wmf-inout.cpp + internal/wmf-print.cpp + + internal/filter/filter-all.cpp + internal/filter/filter-file.cpp + internal/filter/filter.cpp + + internal/pdfinput/pdf-input.cpp + internal/pdfinput/pdf-parser.cpp + internal/pdfinput/svg-builder.cpp + + prefdialog/prefdialog.cpp + prefdialog/parameter.cpp + prefdialog/parameter-bool.cpp + prefdialog/parameter-color.cpp + prefdialog/parameter-float.cpp + prefdialog/parameter-int.cpp + prefdialog/parameter-notebook.cpp + prefdialog/parameter-optiongroup.cpp + prefdialog/parameter-path.cpp + prefdialog/parameter-string.cpp + prefdialog/widget.cpp + prefdialog/widget-box.cpp + prefdialog/widget-image.cpp + prefdialog/widget-label.cpp + prefdialog/widget-separator.cpp + prefdialog/widget-spacer.cpp + + # ------ + # Header + db.h + dependency.h + effect.h + error-file.h + execution-env.h + extension.h + init.h + input.h + output.h + patheffect.h + print.h + system.h + timer.h + loader.h + + implementation/implementation.h + implementation/script.h + implementation/xslt.h + + internal/bluredge.h + internal/cairo-ps-out.h + internal/cairo-render-context.h + internal/cairo-renderer-pdf-out.h + internal/cairo-renderer.h + internal/clear-n_.h + internal/emf-inout.h + internal/emf-print.h + internal/filter/bevels.h + internal/filter/blurs.h + internal/filter/bumps.h + internal/filter/color.h + internal/filter/distort.h + internal/filter/filter.h + internal/filter/image.h + internal/filter/morphology.h + internal/filter/overlays.h + internal/filter/paint.h + internal/filter/protrusions.h + internal/filter/shadows.h + internal/filter/textures.h + internal/filter/transparency.h + internal/gdkpixbuf-input.h + internal/gimpgrad.h + internal/grid.h + internal/image-resolution.h + internal/latex-pstricks-out.h + internal/latex-pstricks.h + internal/latex-text-renderer.h + internal/metafile-inout.h + internal/metafile-print.h + internal/odf.h + internal/pdfinput/pdf-input.h + internal/pdfinput/pdf-parser.h + internal/pdfinput/svg-builder.h + internal/pov-out.h + internal/svg.h + internal/svgz.h + internal/text_reassemble.h + internal/wmf-inout.h + internal/wmf-print.h + + prefdialog/prefdialog.h + prefdialog/parameter.h + prefdialog/parameter-bool.h + prefdialog/parameter-color.h + prefdialog/parameter-float.h + prefdialog/parameter-int.h + prefdialog/parameter-notebook.h + prefdialog/parameter-optiongroup.h + prefdialog/parameter-path.h + prefdialog/parameter-string.h + prefdialog/widget.h + prefdialog/widget-box.h + prefdialog/widget-image.h + prefdialog/widget-label.h + prefdialog/widget-separator.h + prefdialog/widget-spacer.h +) + +if(WIN32) + list(APPEND extension_SRC + ) +endif() + +if(WITH_LIBCDR) + list(APPEND extension_SRC + internal/cdr-input.cpp + internal/cdr-input.h + ) +endif() + +if(WITH_LIBVISIO) + list(APPEND extension_SRC + internal/vsd-input.cpp + internal/vsd-input.h + ) +endif() + +if(WITH_LIBWPG) + list(APPEND extension_SRC + internal/wpg-input.cpp + internal/wpg-input.h + ) +endif() + +if(WITH_MAGICK) + list(APPEND extension_SRC + internal/bitmap/adaptiveThreshold.cpp + internal/bitmap/adaptiveThreshold.h + internal/bitmap/addNoise.cpp + internal/bitmap/addNoise.h + internal/bitmap/blur.cpp + internal/bitmap/blur.h + internal/bitmap/channel.cpp + internal/bitmap/channel.h + internal/bitmap/charcoal.cpp + internal/bitmap/charcoal.h + internal/bitmap/colorize.cpp + internal/bitmap/colorize.h + internal/bitmap/contrast.cpp + internal/bitmap/contrast.h + internal/bitmap/crop.cpp + internal/bitmap/crop.h + internal/bitmap/cycleColormap.cpp + internal/bitmap/cycleColormap.h + internal/bitmap/despeckle.cpp + internal/bitmap/despeckle.h + internal/bitmap/edge.cpp + internal/bitmap/edge.h + internal/bitmap/emboss.cpp + internal/bitmap/emboss.h + internal/bitmap/enhance.cpp + internal/bitmap/enhance.h + internal/bitmap/equalize.cpp + internal/bitmap/equalize.h + internal/bitmap/gaussianBlur.cpp + internal/bitmap/gaussianBlur.h + internal/bitmap/imagemagick.cpp + internal/bitmap/imagemagick.h + internal/bitmap/implode.cpp + internal/bitmap/implode.h + internal/bitmap/level.cpp + internal/bitmap/level.h + internal/bitmap/levelChannel.cpp + internal/bitmap/levelChannel.h + internal/bitmap/medianFilter.cpp + internal/bitmap/medianFilter.h + internal/bitmap/modulate.cpp + internal/bitmap/modulate.h + internal/bitmap/negate.cpp + internal/bitmap/negate.h + internal/bitmap/normalize.cpp + internal/bitmap/normalize.h + internal/bitmap/oilPaint.cpp + internal/bitmap/oilPaint.h + internal/bitmap/opacity.cpp + internal/bitmap/opacity.h + internal/bitmap/raise.cpp + internal/bitmap/raise.h + internal/bitmap/reduceNoise.cpp + internal/bitmap/reduceNoise.h + internal/bitmap/sample.cpp + internal/bitmap/sample.h + internal/bitmap/shade.cpp + internal/bitmap/shade.h + internal/bitmap/sharpen.cpp + internal/bitmap/sharpen.h + internal/bitmap/solarize.cpp + internal/bitmap/solarize.h + internal/bitmap/spread.cpp + internal/bitmap/spread.h + internal/bitmap/swirl.cpp + internal/bitmap/swirl.h + internal/bitmap/threshold.cpp + internal/bitmap/threshold.h + internal/bitmap/unsharpmask.cpp + internal/bitmap/unsharpmask.h + internal/bitmap/wave.cpp + internal/bitmap/wave.h + ) +endif() + +if(WITH_DBUS) + add_subdirectory(dbus) +endif() + +# add_inkscape_lib(extension_LIB "${extension_SRC}") +add_inkscape_source("${extension_SRC}") + +add_subdirectory( plugins ) diff --git a/src/extension/db.cpp b/src/extension/db.cpp new file mode 100644 index 0000000..2c1ab95 --- /dev/null +++ b/src/extension/db.cpp @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Functions to keep a listing of all modules in the system. Has its + * own file mostly for abstraction reasons, but is pretty simple + * otherwise. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Lauris Kaplinski <lauris@kaplinski.com> + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "db.h" +#include "input.h" +#include "output.h" +#include "effect.h" + +/* Globals */ + +/* Namespaces */ + +namespace Inkscape { +namespace Extension { + +/** This is the actual database object. There is only one of these */ +DB db; + +/* Types */ + +DB::DB (void) = default; + + +struct ModuleInputCmp { + bool operator()(Input* module1, Input* module2) const { + + // Ensure SVG files are at top + int n1 = 0; + int n2 = 0; + // 12345678901234567890123456789012 + if (strncmp(module1->get_id(),"org.inkscape.input.svg", 22) == 0) n1 = 1; + if (strncmp(module2->get_id(),"org.inkscape.input.svg", 22) == 0) n2 = 1; + if (strncmp(module1->get_id(),"org.inkscape.input.svgz", 23) == 0) n1 = 2; + if (strncmp(module2->get_id(),"org.inkscape.input.svgz", 23) == 0) n2 = 2; + + if (n1 != 0 && n2 != 0) return (n1 < n2); + if (n1 != 0) return true; + if (n2 != 0) return false; + + // GDK filetypenames begin with lower case letters and thus are sorted at the end. + // Special case "sK1" which starts with a lower case letter to keep it out of GDK region. + if (strncmp(module1->get_id(),"org.inkscape.input.sk1", 22) == 0) { + return ( strcmp("SK1", module2->get_filetypename()) <= 0 ); + } + if (strncmp(module2->get_id(),"org.inkscape.input.sk1", 22) == 0) { + return ( strcmp(module1->get_filetypename(), "SK1" ) <= 0 ); + } + + return ( strcmp(module1->get_filetypename(), module2->get_filetypename()) <= 0 ); + } +}; + + +struct ModuleOutputCmp { + bool operator()(Output* module1, Output* module2) const { + + // Ensure SVG files are at top + int n1 = 0; + int n2 = 0; + // 12345678901234567890123456789012 + if (strncmp(module1->get_id(),"org.inkscape.output.svg.inkscape", 32) == 0) n1 = 1; + if (strncmp(module2->get_id(),"org.inkscape.output.svg.inkscape", 32) == 0) n2 = 1; + if (strncmp(module1->get_id(),"org.inkscape.output.svg.plain", 29) == 0) n1 = 2; + if (strncmp(module2->get_id(),"org.inkscape.output.svg.plain", 29) == 0) n2 = 2; + if (strncmp(module1->get_id(),"org.inkscape.output.svgz.inkscape", 33) == 0) n1 = 3; + if (strncmp(module2->get_id(),"org.inkscape.output.svgz.inkscape", 33) == 0) n2 = 3; + if (strncmp(module1->get_id(),"org.inkscape.output.svgz.plain", 30) == 0) n1 = 4; + if (strncmp(module2->get_id(),"org.inkscape.output.svgz.plain", 30) == 0) n2 = 4; + if (strncmp(module1->get_id(),"org.inkscape.output.scour", 25) == 0) n1 = 5; + if (strncmp(module2->get_id(),"org.inkscape.output.scour", 25) == 0) n2 = 5; + if (strncmp(module1->get_id(),"org.inkscape.output.ZIP", 23) == 0) n1 = 6; + if (strncmp(module2->get_id(),"org.inkscape.output.ZIP", 23) == 0) n2 = 6; + if (strncmp(module1->get_id(),"org.inkscape.output.LAYERS", 26) == 0) n1 = 7; + if (strncmp(module2->get_id(),"org.inkscape.output.LAYERS", 26) == 0) n2 = 7; + + if (n1 != 0 && n2 != 0) return (n1 < n2); + if (n1 != 0) return true; + if (n2 != 0) return false; + + // Special case "sK1" which starts with a lower case letter. + if (strncmp(module1->get_id(),"org.inkscape.output.sk1", 23) == 0) { + return ( strcmp("SK1", module2->get_filetypename()) <= 0 ); + } + if (strncmp(module2->get_id(),"org.inkscape.output.sk1", 23) == 0) { + return ( strcmp(module1->get_filetypename(), "SK1" ) <= 0 ); + } + + return ( strcmp(module1->get_filetypename(), module2->get_filetypename()) <= 0 ); + } +}; + + +/** + \brief Add a module to the module database + \param module The module to be registered. +*/ +void +DB::register_ext (Extension *module) +{ + g_return_if_fail(module != nullptr); + g_return_if_fail(module->get_id() != nullptr); + + // only add to list if it's a never-before-seen module + bool add_to_list = + ( moduledict.find(module->get_id()) == moduledict.end()); + + //printf("Registering: '%s' '%s' add:%d\n", module->get_id(), module->get_name(), add_to_list); + moduledict[module->get_id()] = module; + + if (add_to_list) { + modulelist.push_back( module ); + } +} + +/** + \brief This function removes a module from the database + \param module The module to be removed. +*/ +void +DB::unregister_ext (Extension * module) +{ + g_return_if_fail(module != nullptr); + g_return_if_fail(module->get_id() != nullptr); + + // printf("Extension DB: removing %s\n", module->get_id()); + moduledict.erase(moduledict.find(module->get_id())); + // only remove if it's not there any more + if ( moduledict.find(module->get_id()) != moduledict.end()) + modulelist.remove(module); +} + +/** + \return A reference to the Inkscape::Extension::Extension specified by the input key. + \brief This function looks up a Inkscape::Extension::Extension by using its unique + id. It then returns a reference to that module. + \param key The unique ID of the module + + Retrieves a module by name; if non-NULL, it refs the returned + module; the caller is responsible for releasing that reference + when it is no longer needed. +*/ +Extension * +DB::get (const gchar *key) +{ + if (key == nullptr) return nullptr; + + Extension *mod = moduledict[key]; + if ( !mod || mod->deactivated() ) + return nullptr; + + return mod; +} + +/** + \return none + \brief A function to execute another function with every entry + in the database as a parameter. + \param in_func The function to execute for every module + \param in_data A data pointer that is also passed to in_func + + Enumerates the modules currently in the database, calling a given + callback for each one. +*/ +void +DB::foreach (void (*in_func)(Extension * in_plug, gpointer in_data), gpointer in_data) +{ + std::list <Extension *>::iterator cur; + + for (cur = modulelist.begin(); cur != modulelist.end(); ++cur) { + // printf("foreach: %s\n", (*cur)->get_id()); + in_func((*cur), in_data); + } +} + +/** + \return none + \brief The function to look at each module and see if it is + an input module, then add it to the list. + \param in_plug Module to be examined + \param data The list to be attached to + + The first thing that is checked is if this module is an input + module. If it is, then it is added to the list which is passed + in through \c data. +*/ +void +DB::input_internal (Extension * in_plug, gpointer data) +{ + if (dynamic_cast<Input *>(in_plug)) { + InputList * ilist; + Input * imod; + + imod = dynamic_cast<Input *>(in_plug); + ilist = reinterpret_cast<InputList *>(data); + + ilist->push_back(imod); + // printf("Added to input list: %s\n", imod->get_id()); + } +} + +/** + \return none + \brief The function to look at each module and see if it is + an output module, then add it to the list. + \param in_plug Module to be examined + \param data The list to be attached to + + The first thing that is checked is if this module is an output + module. If it is, then it is added to the list which is passed + in through \c data. +*/ +void +DB::output_internal (Extension * in_plug, gpointer data) +{ + if (dynamic_cast<Output *>(in_plug)) { + OutputList * olist; + Output * omod; + + omod = dynamic_cast<Output *>(in_plug); + olist = reinterpret_cast<OutputList *>(data); + + olist->push_back(omod); + // printf("Added to output list: %s\n", omod->get_id()); + } + + return; +} + +/** + \return none + \brief The function to look at each module and see if it is + an effect module, then add it to the list. + \param in_plug Module to be examined + \param data The list to be attached to + + The first thing that is checked is if this module is an effect + module. If it is, then it is added to the list which is passed + in through \c data. +*/ +void +DB::effect_internal (Extension * in_plug, gpointer data) +{ + if (dynamic_cast<Effect *>(in_plug)) { + EffectList * elist; + Effect * emod; + + emod = dynamic_cast<Effect *>(in_plug); + elist = reinterpret_cast<EffectList *>(data); + + elist->push_back(emod); + // printf("Added to effect list: %s\n", emod->get_id()); + } + + return; +} + +/** + \brief Creates a list of all the Input extensions + \param ou_list The list that is used to put all the extensions in + + Calls the database \c foreach function with \c input_internal. +*/ +DB::InputList & +DB::get_input_list (DB::InputList &ou_list) +{ + foreach(input_internal, (gpointer)&ou_list); + ou_list.sort( ModuleInputCmp() ); + return ou_list; +} + +/** + \brief Creates a list of all the Output extensions + \param ou_list The list that is used to put all the extensions in + + Calls the database \c foreach function with \c output_internal. +*/ +DB::OutputList & +DB::get_output_list (DB::OutputList &ou_list) +{ + foreach(output_internal, (gpointer)&ou_list); + ou_list.sort( ModuleOutputCmp() ); + return ou_list; +} + +/** + \brief Creates a list of all the Effect extensions + \param ou_list The list that is used to put all the extensions in + + Calls the database \c foreach function with \c effect_internal. +*/ +DB::EffectList & +DB::get_effect_list (DB::EffectList &ou_list) +{ + foreach(effect_internal, (gpointer)&ou_list); + return ou_list; +} + +} } /* namespace Extension, Inkscape */ diff --git a/src/extension/db.h b/src/extension/db.h new file mode 100644 index 0000000..4efc74f --- /dev/null +++ b/src/extension/db.h @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Functions to keep a listing of all modules in the system. Has its + * own file mostly for abstraction reasons, but is pretty simple + * otherwise. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Lauris Kaplinski <lauris@kaplinski.com> + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_MODULES_DB_H +#define SEEN_MODULES_DB_H + +#include <map> +#include <list> +#include <cstring> + +#include <glib.h> + + +namespace Inkscape { +namespace Extension { + +class Input; +class Output; +class Effect; +class Extension; + +class DB { +private: + /** A string comparison function to be used in the moduledict + to find the different extensions in the hash map. */ + struct ltstr { + bool operator()(const char* s1, const char* s2) const { + if ( (s1 == nullptr) && (s2 != nullptr) ) { + return true; + } else if (s1 == nullptr || s2 == nullptr) { + return false; + } else { + return strcmp(s1, s2) < 0; + } + } + }; + /** This is the actual database. It has all of the modules in it, + indexed by their ids. It's a hash table for faster lookups */ + std::map <const char *, Extension *, ltstr> moduledict; + /** Maintain an ordered list of modules for generating the extension + lists via "foreach" */ + std::list <Extension *> modulelist; + + static void foreach_internal (gpointer in_key, gpointer in_value, gpointer in_data); + +public: + DB (); + Extension * get (const gchar *key); + void register_ext (Extension *module); + void unregister_ext (Extension *module); + void foreach (void (*in_func)(Extension * in_plug, gpointer in_data), gpointer in_data); + +private: + static void input_internal (Extension * in_plug, gpointer data); + static void output_internal (Extension * in_plug, gpointer data); + static void effect_internal (Extension * in_plug, gpointer data); + +public: + typedef std::list<Output *> OutputList; + typedef std::list<Input *> InputList; + typedef std::list<Effect *> EffectList; + + InputList &get_input_list (InputList &ou_list); + OutputList &get_output_list (OutputList &ou_list); + EffectList &get_effect_list (EffectList &ou_list); +}; /* class DB */ + +extern DB db; + +} } /* namespace Extension, Inkscape */ + +#endif // SEEN_MODULES_DB_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/extension/dbus/CMakeLists.txt b/src/extension/dbus/CMakeLists.txt new file mode 100644 index 0000000..f189180 --- /dev/null +++ b/src/extension/dbus/CMakeLists.txt @@ -0,0 +1,30 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +if(WITH_DBUS) +include_directories(${CMAKE_BINARY_DIR}/src/extension/dbus) +set(dbus_SRC "") + list(APPEND dbus_SRC + application-interface.cpp + dbus-init.cpp + document-interface.cpp + ) + add_custom_target(inkscape_dbus + DEPENDS ${CMAKE_BINARY_DIR}/src/extension/dbus/application-server-glue.h ${CMAKE_BINARY_DIR}/src/extension/dbus/document-server-glue.h ${CMAKE_BINARY_DIR}/src/extension/dbus/document-client-glue.h + ) + add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/src/extension/dbus/application-server-glue.h ${CMAKE_BINARY_DIR}/src/extension/dbus/document-server-glue.h ${CMAKE_BINARY_DIR}/src/extension/dbus/document-client-glue.h + DEPENDS ${CMAKE_SOURCE_DIR}/src/extension/dbus/application-interface.xml ${CMAKE_SOURCE_DIR}/src/extension/dbus/document-interface.xml + COMMAND dbus-binding-tool --mode=glib-server --output=${CMAKE_BINARY_DIR}/src/extension/dbus/application-server-glue.h --prefix=application_interface ${CMAKE_SOURCE_DIR}/src/extension/dbus/application-interface.xml + COMMAND dbus-binding-tool --mode=glib-server --output=${CMAKE_BINARY_DIR}/src/extension/dbus/document-server-glue.h --prefix=document_interface ${CMAKE_SOURCE_DIR}/src/extension/dbus/document-interface.xml + COMMAND dbus-binding-tool --mode=glib-client --output=${CMAKE_BINARY_DIR}/src/extension/dbus/document-client-glue.h --prefix=document_interface ${CMAKE_SOURCE_DIR}/src/extension/dbus/document-interface.xml + ) + set_source_files_properties( + ${CMAKE_BINARY_DIR}/src/extension/dbus/application-server-glue.h + PROPERTIES GENERATED TRUE) + set_source_files_properties( + ${CMAKE_BINARY_DIR}/src/extension/dbus/document-server-glue.h + PROPERTIES GENERATED TRUE) + set_source_files_properties( + ${CMAKE_BINARY_DIR}/src/extension/dbus/document-client-glue.h + PROPERTIES GENERATED TRUE) +add_inkscape_source("${dbus_SRC}") +endif() diff --git a/src/extension/dbus/Notes.txt b/src/extension/dbus/Notes.txt new file mode 100644 index 0000000..25c3f35 --- /dev/null +++ b/src/extension/dbus/Notes.txt @@ -0,0 +1,79 @@ +INTRO: +For people that are interested in improving the DBus API here is a +intro to how everything is laid out. + +First read the documentation for a general idea of how the different interfaces +fit together and how Dbus is used in this application. + +Here are short descriptions of the relevant files: + +document-interface.cpp: This has most of the "meat" of the interface, this is where +most functions are implemented. + +application-interface.cpp: This is where the application interface is implemented. + +(document/application)-interface.xml: These files are the master record of the interfaces. +All of the documentation is generated from these files as is a lot of glue code. +Any changes MUST be reflected here. + +dbus-init.cpp: This is where the interface is exposed when Inkscape starts up. +Here is where the names given to the various interfaces are set. The application interface is constant but the document interfaces are generated on the fly. + +org.inkscape.service.in: This sets where DBus looks for the Inkscape executable +if it is not running when someone tries to connect. + +pytester.py: A python script that tests a lot of dbus functions. + +doc/builddocs.sh: builds documentation out of the XML files and some others. + +config.xsl, dbus-introspect-docs.dtd, spec-to-docbook.xsl, docbook.css: I borrowed +these files, they set how the documentation looks, I have no idea how to edit them. + +doc/inkscapeDbusRef.xml: This is the top level file for laying out the documentation, +it also includes the introduction. + +doc/inkscapeDbusTerms.xml: This contains the terms sections of the documentation. +Also the overview and all the tutorials. + +*.ref.xml: These are intermediate files, do not edit. + +wrapper/inkscape-dbus-wrapper.c: This is actually completely separate from inkscape. +It has a wrapper for each function in the document interface and includes the +client generated bindings. It is used to create a shared object that will allow +people to use the interface without even knowing anything about Dbus. + +BUGS: + *Inkscape crashes if widow is closed while code is running, + need better error handling. + + *Pause updates needs work. + + *Default style for new shapes is occasionally strange. + + *The following methods are broken: + -document_interface_selection_move_to_layer + + *The following are not implemented: + -document_interface_layer_get_all + -document_interface_selection_box + -document_interface_get_node_coordinates + + *The following do not behave like the documentation: + -document_interface_transform + -document_interface_text + +EFFICIENCY: + *Need better way to retrieve objects by name. + Switch to GQuark codes for object retrieval? + + *Rethink how often activate_desktop needs to be called. + +FEATURES: + *Find out more about extension API. + *API compatibility for plugins? + +CLEANUP: + + + + diff --git a/src/extension/dbus/application-interface.cpp b/src/extension/dbus/application-interface.cpp new file mode 100644 index 0000000..1b4be69 --- /dev/null +++ b/src/extension/dbus/application-interface.cpp @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is where the implementation of the DBus based application API lives. + * All the methods in here are designed to be called remotly via DBus. + * document-interface.cpp has all of the actual manipulation methods. + * This interface is just for creating new document interfaces. + * + * Documentation for these methods is in application-interface.xml + * which is the "gold standard" as to how the interface should work. + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "application-interface.h" +#include <string.h> +#include "dbus-init.h" +#include "file.h" +#include "inkscape.h" + +G_DEFINE_TYPE(ApplicationInterface, application_interface, G_TYPE_OBJECT) + +static void +application_interface_finalize (GObject *object) +{ + G_OBJECT_CLASS (application_interface_parent_class)->finalize (object); +} + + +static void +application_interface_class_init (ApplicationInterfaceClass *klass) +{ + GObjectClass *object_class; + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = application_interface_finalize; +} + +static void +application_interface_init (ApplicationInterface *app_interface) +{ + dbus_g_error_domain_register (INKSCAPE_ERROR, + NULL, + INKSCAPE_TYPE_ERROR); +} + +static bool +ensure_desktop_valid(GError **error) +{ + if (!INKSCAPE.use_gui()) { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OTHER, "Application interface action requires a GUI"); + return false; + } + return true; +} + +static bool +ensure_desktop_not_present(GError **error) +{ + if (INKSCAPE.use_gui()) { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OTHER, "Application interface action requires non-GUI (command line) mode"); + return false; + } + return true; +} + +ApplicationInterface * +application_interface_new (void) +{ + return (ApplicationInterface*)g_object_new (TYPE_APPLICATION_INTERFACE, NULL); +} + +/* + * Error stuff... + * + * To add a new error type, edit here and in the .h InkscapeError enum. + */ +GQuark +inkscape_error_quark (void) +{ + static GQuark quark = 0; + if (!quark) + quark = g_quark_from_static_string ("inkscape_error"); + + return quark; +} + +#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } + +GType inkscape_error_get_type(void) +{ + static GType etype = 0; + + if (etype == 0) { + static const GEnumValue values[] = + { + + ENUM_ENTRY(INKSCAPE_ERROR_SELECTION, "Incompatible_Selection"), + ENUM_ENTRY(INKSCAPE_ERROR_OBJECT, "Incompatible_Object"), + ENUM_ENTRY(INKSCAPE_ERROR_VERB, "Failed_Verb"), + ENUM_ENTRY(INKSCAPE_ERROR_OTHER, "Generic_Error"), + { 0, 0, 0 } + }; + + etype = g_enum_register_static("InkscapeError", values); + } + + return etype; +} + +/**************************************************************************** + DESKTOP FUNCTIONS +****************************************************************************/ + +gchar* +application_interface_desktop_new (ApplicationInterface *app_interface, + GError **error) +{ + g_return_val_if_fail(ensure_desktop_valid(error), NULL); + return (gchar*)Inkscape::Extension::Dbus::init_desktop(); +} + +gchar** +application_interface_get_desktop_list (ApplicationInterface *app_interface) +{ + return NULL; +} + +gchar* +application_interface_get_active_desktop (ApplicationInterface *app_interface, + GError **error) +{ + return NULL; +} + +gboolean +application_interface_set_active_desktop (ApplicationInterface *app_interface, + gchar* document_name, + GError **error) +{ + return TRUE; +} + +gboolean +application_interface_desktop_close_all (ApplicationInterface *app_interface, + GError **error) +{ + return TRUE; +} + +gboolean +application_interface_exit (ApplicationInterface *app_interface, GError **error) +{ + sp_file_exit(); + return TRUE; +} + +/**************************************************************************** + DOCUMENT FUNCTIONS +****************************************************************************/ + +gchar* application_interface_document_new (ApplicationInterface *app_interface, + GError **error) +{ + g_return_val_if_fail(ensure_desktop_not_present(error), NULL); + return (gchar*)Inkscape::Extension::Dbus::init_document(); +} + +gchar* +application_interface_get_active_document(ApplicationInterface *app_interface, + GError **error) +{ + gchar *result = (gchar*)Inkscape::Extension::Dbus::init_active_document(); + if (!result) { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OTHER, "No active document"); + } + return result; +} + +gchar** +application_interface_get_document_list (ApplicationInterface *app_interface) +{ + return NULL; +} + +gboolean +application_interface_document_close_all (ApplicationInterface *app_interface, + GError **error) +{ + return TRUE; +} + +/* INTERESTING FUNCTIONS + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + g_assert(desktop != NULL); + + SPDocument *doc = desktop->getDocument(); + g_assert(doc != NULL); + + Inkscape::XML::Node *repr = doc->getReprRoot(); + g_assert(repr != NULL); +*/ + diff --git a/src/extension/dbus/application-interface.h b/src/extension/dbus/application-interface.h new file mode 100644 index 0000000..9e97585 --- /dev/null +++ b/src/extension/dbus/application-interface.h @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is where the implementation of the DBus based application API lives. + * All the methods in here are designed to be called remotly via DBus. + * document-interface.cpp has all of the actual manipulation methods. + * This interface is just for creating new document interfaces. + * + * Documentation for these methods is in application-interface.xml + * which is the "gold standard" as to how the interface should work. + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_APPLICATION_INTERFACE_H_ +#define INKSCAPE_EXTENSION_APPLICATION_INTERFACE_H_ + +#include <glib.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-bindings.h> +#include <dbus/dbus-glib-lowlevel.h> + +#define DBUS_APPLICATION_INTERFACE_PATH "/org/inkscape/application" + +#define TYPE_APPLICATION_INTERFACE (application_interface_get_type ()) +#define APPLICATION_INTERFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_APPLICATION_INTERFACE, ApplicationInterface)) +#define APPLICATION_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_APPLICATION_INTERFACE, ApplicationInterfaceClass)) +#define IS_APPLICATION_INTERFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_APPLICATION_INTERFACE)) +#define IS_APPLICATION_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_APPLICATION_INTERFACE)) +#define APPLICATION_INTERFACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_APPLICATION_INTERFACE, ApplicationInterfaceClass)) + +G_BEGIN_DECLS + +typedef struct _ApplicationInterface ApplicationInterface; +typedef struct _ApplicationInterfaceClass ApplicationInterfaceClass; + +struct _ApplicationInterface { + GObject parent; +}; + +struct _ApplicationInterfaceClass { + GObjectClass parent; +}; + + +enum InkscapeError +{ + INKSCAPE_ERROR_SELECTION, + INKSCAPE_ERROR_OBJECT, + INKSCAPE_ERROR_VERB, + INKSCAPE_ERROR_OTHER +}; + + + +#define INKSCAPE_ERROR (inkscape_error_quark ()) +#define INKSCAPE_TYPE_ERROR (inkscape_error_get_type ()) + +GQuark inkscape_error_quark (void); +GType inkscape_error_get_type (void); + +/**************************************************************************** + DESKTOP FUNCTIONS +****************************************************************************/ + +gchar* +application_interface_desktop_new (ApplicationInterface *app_interface, + GError **error); + +gchar** +application_interface_get_desktop_list (ApplicationInterface *app_interface); + +gchar* +application_interface_get_active_desktop (ApplicationInterface *app_interface, + GError **error); + +gboolean +application_interface_set_active_desktop (ApplicationInterface *app_interface, + gchar* document_name, + GError **error); + +gboolean +application_interface_desktop_close_all (ApplicationInterface *app_interface, + GError **error); + +gboolean +application_interface_exit (ApplicationInterface *app_interface, GError **error); + +/**************************************************************************** + DOCUMENT FUNCTIONS +****************************************************************************/ + +gchar* +application_interface_document_new (ApplicationInterface *app_interface, + GError **error); + +gchar* +application_interface_get_active_document(ApplicationInterface *app_interface, + GError **error); + +gchar** +application_interface_get_document_list (ApplicationInterface *app_interface); + +gboolean +application_interface_document_close_all (ApplicationInterface *app_interface, + GError **error); + + +/**************************************************************************** + SETUP +****************************************************************************/ + +ApplicationInterface *application_interface_new (void); +GType application_interface_get_type (void); + + +G_END_DECLS + +#endif // INKSCAPE_EXTENSION_APPLICATION_INTERFACE_H_ diff --git a/src/extension/dbus/application-interface.xml b/src/extension/dbus/application-interface.xml new file mode 100644 index 0000000..05513cb --- /dev/null +++ b/src/extension/dbus/application-interface.xml @@ -0,0 +1,99 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- SPDX-License-Identifier: GPL-2.0-or-later --> +<!-- + * This is the master description of the DBus application interface. + * The interface is mostly just for creating new document instances. + * + * This file is used to generate both glue code and documentation. + * The methods are in the same order as the .cpp/.h and the sections are labeled. + * + * Any change to method prototypes in application-interface.cpp MUST be reflected here. + * + * This file is the proverbial gold standard for the application interface. + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. +--> +<node name="/org/inkscape/application" + xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" +> + <interface name="org.inkscape.application"> + + <!-- DESKTOP FUNCTIONS --> + <method name="desktop_new"> + <arg type="s" name="desktop_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>This string can be used to connect to the new interface that was created.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Create a new document interface and return its location. Only call this when Inkscape is running in GUI mode.</doc:para> + </doc:description> + </doc:doc> + </method> + <method name="get_desktop_list"> + <arg type="as" name="document_list" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> + <doc:doc> + <doc:summary>A list of interfaces being provided by Inkscape.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>List all the interfaces that it is possible to connect to. TODO: not implemented.</doc:para> + </doc:description> + </doc:doc> + </method> + <method name="desktop_close_all"> + <doc:doc> + <doc:description> + <doc:para>Close all document interfaces without saving. TODO: not implemented.</doc:para> + </doc:description> + </doc:doc> + </method> + <method name="exit"> + <doc:doc> + <doc:description> + <doc:para>Exit Inkscape without saving. Fairly straightforward.</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- DOCUMENT FUNCTIONS --> + <method name="document_new"> + <arg type="s" name="document_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>This string can be used to connect to the new interface that was created.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Originally, there were going to be two interfaces. A desktop and a document. Desktops would be used when the user wanted to see the result of their code and documents would be used when less overhead was desired. Unfortunately as more and more of the code can to rely on the desktop and it's associated support code (including selections and verbs) the document interface was looking more and more limited. Ultimately I decided to just go with the desktop interface since I didn't have a compelling reason for keeping the other one and having two similar interfaces could be very confusing. The desktop interface inherited the document name because I believe it's more familiar to people.</doc:para> + <doc:para>Perhaps it would be best to have an option as to whether or not to create a window and fail with a good error message when they call a function that requires one. Or have a second interface for different use cases but have it be completely different, rather than a subset of the first if there are use cases that support it.</doc:para> + <doc:para>UPDATE: 3rd July 2013, Eric Greveson: After having done some initial work to attempt to decouple Inkscape "verbs" from desktops, it is now possible to run a limited subset of actions in command-line mode (with a selection model and document, but no desktop). I believe that the "single document interface" approach, with some functions that may require a GUI, is the better path, and so document interfaces without a desktop are now possible. Most functions still require the desktop to work, though, with the notable exception of selection methods and Boolean operations.</doc:para> + <doc:para>As a result, this function should ONLY be called when using Inkscape in command-line mode. Use "desktop_new" instead if running in GUI mode.</doc:para> + </doc:description> + </doc:doc> + </method> + <method name="get_active_document"> + <arg type="s" name="document_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>This string can be used to connect to the current active document.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Get the location of the current active document (e.g. when running in console mode, when desktops are not available).</doc:para> + </doc:description> + </doc:doc> + </method> + </interface> +</node> diff --git a/src/extension/dbus/builddocs.sh b/src/extension/dbus/builddocs.sh new file mode 100644 index 0000000..dbe3d6e --- /dev/null +++ b/src/extension/dbus/builddocs.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0-or-later +xsltproc doc/spec-to-docbook.xsl application-interface.xml > doc/org.inkscape.application.ref.xml && +xsltproc doc/spec-to-docbook.xsl document-interface.xml > doc/org.inkscape.document.ref.xml && +xsltproc doc/spec-to-docbook.xsl proposed-interface.xml > doc/org.inkscape.proposed.ref.xml && +xmlto --skip-validation xhtml-nochunks -o doc -m doc/config.xsl doc/inkscapeDbusRef.xml && +firefox doc/inkscapeDbusRef.html + diff --git a/src/extension/dbus/dbus-init.cpp b/src/extension/dbus/dbus-init.cpp new file mode 100644 index 0000000..c83caac --- /dev/null +++ b/src/extension/dbus/dbus-init.cpp @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is where Inkscape connects to the DBus when it starts and + * registers the main interface. + * + * Also where new interfaces are registered when a new document is created. + * (Not called directly by application-interface but called indirectly.) + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <dbus/dbus-glib.h> +// this is required so that giomm headers won't barf +#undef DBUS_MESSAGE_TYPE_INVALID +#include "dbus-init.h" + +#include "application-interface.h" +#include "application-server-glue.h" + +#include "document-interface.h" +#include "document-server-glue.h" + +#include "inkscape.h" +#include "document.h" +#include "desktop.h" +#include "file.h" +#include "verbs.h" +#include "helper/action.h" + +#include <algorithm> +#include <iostream> +#include <sstream> + + +namespace +{ + // This stores the bus name to use for this app instance. By default, it + // will be set to org.inkscape. However, users may provide other names by + // setting command-line parameters when starting Inkscape, so that more + // than one instance of Inkscape may be used by external scripts. + gchar *instance_bus_name = NULL; +} + +namespace Inkscape { +namespace Extension { +namespace Dbus { + +/* PRIVATE get a connection to the session bus */ +DBusGConnection * +dbus_get_connection() { + GError *error = NULL; + DBusGConnection *connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (error) { + fprintf(stderr, "Failed to get connection"); + return NULL; + } + else + return connection; +} + +/* PRIVATE create a proxy object for a bus.*/ +DBusGProxy * +dbus_get_proxy(DBusGConnection *connection) { + return dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); +} + +/* PRIVATE register an object on a bus */ +static gpointer +dbus_register_object (DBusGConnection *connection, + DBusGProxy *proxy, + GType object_type, + const DBusGObjectInfo *info, + const gchar *path) +{ + GObject *object = (GObject*)g_object_new (object_type, NULL); + dbus_g_object_type_install_info (object_type, info); + dbus_g_connection_register_g_object (connection, path, object); + return object; +} + +/* + * PRIVATE register a document interface for the document in the given ActionContext, if none exists. + * Return the DBus path to the interface (something like /org/inkscape/document_0). + * Note that while a DocumentInterface could be used either for a document with no desktop, or a + * document with a desktop, this function is only used for creating interfaces in the former case. + * Desktop-associated DocumentInterfaces are named /org/inkscape/desktop_0, etc. + * FIXME: This state of affairs probably needs tidying up at some point in the future. + */ +static gchar * +dbus_register_document(Inkscape::ActionContext const & target) +{ + SPDocument *doc = target.getDocument(); + g_assert(doc != NULL); + + // Document name is not suitable for DBus name, as it might contain invalid chars + std::string name("/org/inkscape/document_"); + std::stringstream ss; + ss << doc->serial(); + name.append(ss.str()); + + DBusGConnection *connection = dbus_get_connection(); + DBusGProxy *proxy = dbus_get_proxy(connection); + + // Has the document already been registered? + if (!dbus_g_connection_lookup_g_object(connection, name.c_str())) { + // No - register it + DocumentInterface *doc_interface = (DocumentInterface*) dbus_register_object (connection, + proxy, + TYPE_DOCUMENT_INTERFACE, + &dbus_glib_document_interface_object_info, + name.c_str()); + + // Set the document info for this interface + doc_interface->target = target; + } + return strdup(name.c_str()); +} + +/* Initialize a Dbus service */ +void +init (void) +{ + if (instance_bus_name == NULL) { + // Set the bus name to the default + instance_bus_name = strdup("org.inkscape"); + } + + guint result; + GError *error = NULL; + DBusGConnection *connection; + DBusGProxy *proxy; + connection = dbus_get_connection(); + proxy = dbus_get_proxy(connection); + org_freedesktop_DBus_request_name (proxy, + instance_bus_name, + DBUS_NAME_FLAG_DO_NOT_QUEUE, &result, &error); + //create interface for application + dbus_register_object (connection, + proxy, + TYPE_APPLICATION_INTERFACE, + &dbus_glib_application_interface_object_info, + DBUS_APPLICATION_INTERFACE_PATH); +} + +gchar * +init_document (void) +{ + // This is for command-line use only + g_assert(!INKSCAPE.use_gui()); + + // Create a blank document and get its selection model etc in an ActionContext + SPDocument *doc = SPDocument::createNewDoc(NULL, 1, TRUE); + INKSCAPE.add_document(doc); + return dbus_register_document(INKSCAPE.action_context_for_document(doc)); +} + +gchar * +init_active_document() +{ + SPDocument *doc = INKSCAPE.active_document(); + if (!doc) { + return NULL; + } + + return dbus_register_document(INKSCAPE.active_action_context()); +} + +gchar * +dbus_init_desktop_interface (SPDesktop * dt) +{ + DBusGConnection *connection; + DBusGProxy *proxy; + + std::string name("/org/inkscape/desktop_"); + std::stringstream out; + out << dt->dkey; + name.append(out.str()); + + //printf("DKEY: %d\n, NUMBER %d\n NAME: %s\n", dt->dkey, dt->number, name.c_str()); + + connection = dbus_get_connection(); + proxy = dbus_get_proxy(connection); + + if (!dbus_g_connection_lookup_g_object(connection, name.c_str())) { + DocumentInterface *doc_interface = (DocumentInterface*) dbus_register_object (connection, + proxy, + TYPE_DOCUMENT_INTERFACE, + &dbus_glib_document_interface_object_info, + name.c_str()); + + // Set the document info for this interface + doc_interface->target = Inkscape::ActionContext(dt); + doc_interface->updates = TRUE; + dt->dbus_document_interface=doc_interface; + } + return strdup(name.c_str()); +} + +gchar * +init_desktop (void) { + //this function will create a new desktop and call + //dbus_init_desktop_interface. + SPDesktop * dt = sp_file_new_default(); + + std::string name("/org/inkscape/desktop_"); + std::stringstream out; + out << dt->dkey; + name.append(out.str()); + return strdup(name.c_str()); +} + +void +dbus_set_bus_name(gchar const * bus_name) +{ + g_assert(bus_name != NULL); + g_assert(instance_bus_name == NULL); + instance_bus_name = strdup(bus_name); +} + +gchar * +dbus_get_bus_name() +{ + g_assert(instance_bus_name != NULL); + return instance_bus_name; +} + +} } } /* namespace Inkscape::Extension::Dbus */ diff --git a/src/extension/dbus/dbus-init.h b/src/extension/dbus/dbus-init.h new file mode 100644 index 0000000..54a2819 --- /dev/null +++ b/src/extension/dbus/dbus-init.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Soren Berg <glimmer07@gmail.com> + * + * Copyright (C) 2009 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_DBUS_INIT_H__ +#define INKSCAPE_EXTENSION_DBUS_INIT_H__ + +class SPDesktop; + +namespace Inkscape { +namespace Extension { +namespace Dbus { + +/** \brief Dbus stuff. For registering objects on the bus. */ + +void init (void); + +gchar * init_document (void); + +gchar * init_active_document (); + +gchar * init_desktop (void); + +gchar * dbus_init_desktop_interface (SPDesktop * dt); + +/** Set the bus name to use. Default is "org.inkscape". + This function should only be called once, before init(), if a non-default + bus name is required. */ +void dbus_set_bus_name(gchar const * bus_name); + +/** Get the bus name for this instance. Default is "org.inkscape". + This function should only be called after init(). + The returned gchar * is owned by this module and should not be freed. */ +gchar * dbus_get_bus_name(); + +} } } /* namespace Dbus, Extension, Inkscape */ + +#endif /* INKSCAPE_EXTENSION_DBUS_INIT_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/extension/dbus/doc/config.xsl b/src/extension/dbus/doc/config.xsl new file mode 100644 index 0000000..09cf8de --- /dev/null +++ b/src/extension/dbus/doc/config.xsl @@ -0,0 +1,8 @@ +<?xml version='1.0'?> +<!-- SPDX-License-Identifier: GPL-2.0-or-later --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + version="1.0"> + <xsl:param name="html.stylesheet" select="'docbook.css'"/> +</xsl:stylesheet> + diff --git a/src/extension/dbus/doc/dbus-introspect-docs.dtd b/src/extension/dbus/doc/dbus-introspect-docs.dtd new file mode 100644 index 0000000..3355d4d --- /dev/null +++ b/src/extension/dbus/doc/dbus-introspect-docs.dtd @@ -0,0 +1,34 @@ +<!-- DTD for D-Bus Introspection Documentation --> +<!-- SPDX-License-Identifier: GPL-2.0-or-later --> + +<!ELEMENT doc (summary?,description?,errors?,permission?,since?,deprecated,seealso?)> + +<!ELEMENT summary (#PCDATA|ref)*> +<!ELEMENT description (#PCDATA|para|example)*> +<!ELEMENT errors (error)*> +<!ELEMENT permission (#PCDATA|ref|para)*> +<!ELEMENT since EMPTY> +<!ATTLIST since version CDATA #REQUIRED> +<!ELEMENT deprecated (#PCDATA|ref)> +<!ATTLIST deprecated version CDATA #REQUIRED> +<!ATTLIST deprecated instead CDATA #REQUIRED> +<!ELEMENT seealso (ref+)> + +<!ELEMENT error (#PCDATA|para)*> +<!ATTLIST error name CDATA #REQUIRED> +<!ELEMENT para (#PCDATA|example|code|list|ref)*> +<!ELEMENT example (#PCDATA|para|code|ref)*> +<!ATTLIST language (c|glib|python|shell) #REQUIRED> +<!ATTLIST title CDATA #IMPLIED> +<!ELEMENT list (item*)> +<!ATTLIST list type (bullet|number) #REQUIRED> +<!ELEMENT item (term|definition)*> +<!ELEMENT term (#PCDATA|ref)*> +<!ELEMENT definition (#PCDATA|para)*> + +<!ELEMENT code (#PCDATA)> +<!ATTLIST code lang CDATA #IMPLIED> +<!ELEMENT ref CDATA> +<!ATTLIST ref type (parameter|arg|signal|method|interface) #REQUIRED> +<!ATTLIST ref to CDATA #REQUIRED> + diff --git a/src/extension/dbus/doc/docbook.css b/src/extension/dbus/doc/docbook.css new file mode 100644 index 0000000..0a1b7ee --- /dev/null +++ b/src/extension/dbus/doc/docbook.css @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +body +{ + font-family: sans-serif; +} +h1.title +{ +} +.permission +{ + color: #ee0000; + text-decoration: underline; +} +.synopsis, .classsynopsis +{ + background: #eeeeee; + border: solid 1px #aaaaaa; + padding: 0.5em; +} +.programlisting +{ + background: #eeeeff; + border: solid 1px #aaaaff; + padding: 0.5em; +} +.variablelist +{ + padding: 4px; + margin-left: 3em; +} +.variablelist td:first-child +{ + vertical-align: top; +} +td.shortcuts +{ + color: #770000; + font-size: 80%; +} +div.refnamediv +{ + margin-top: 2em; +} +div.toc +{ + border: 2em; +} +a +{ + text-decoration: none; +} +a:hover +{ + text-decoration: underline; + color: #FF0000; +} + +div.table table +{ + border-collapse: collapse; + border-spacing: 0px; + border-style: solid; + border-color: #777777; + border-width: 1px; +} + +div.table table td, div.table table th +{ + border-style: solid; + border-color: #777777; + border-width: 1px; + padding: 3px; + vertical-align: top; +} + +div.table table th +{ + background-color: #eeeeee; +} + diff --git a/src/extension/dbus/doc/inkscapeDbusRef.xml b/src/extension/dbus/doc/inkscapeDbusRef.xml new file mode 100644 index 0000000..6b5d578 --- /dev/null +++ b/src/extension/dbus/doc/inkscapeDbusRef.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ +<!ENTITY dbus-Application SYSTEM "org.inkscape.application.ref.xml"> +<!ENTITY dbus-Document SYSTEM "org.inkscape.document.ref.xml"> +<!ENTITY dbus-Proposed SYSTEM "org.inkscape.proposed.ref.xml"> +<!ENTITY Terms SYSTEM "inkscapeDbusTerms.xml"> + +]> + +<book id="index"> + <bookinfo> + <title>Inkscape Dbus Documentation</title> + <releaseinfo>Version 0.0</releaseinfo> + <date>7 July, 2009</date> + <authorgroup> + <author> + <firstname>Soren</firstname> + <surname>Berg</surname> + <affiliation> + <address> + <email>glimmer07@gmail.com</email> + </address> + </affiliation> + </author> + </authorgroup> + </bookinfo> + + <preface> + <title>Introduction</title> + <para> +This is the documentation for scripting Inkscape using Dbus. This framework was developed to let users quickly and easily write scripts to create or manipulate images in a variety of languages. Once the API has stabilized there will also be a C library that encapsulates the Dbus functionality. + </para> + <para> +The guiding principles behind the design of this API were: + </para> + <para> +<emphasis>Easy to use:</emphasis> Use of insider terms was limited where possible, and many functions have been simplified to provide a easy entry point for beginning users. Ideally one should not need any experience with Inkscape or even vector graphics to begin using the interface. At the same time, functions that can call arbitrary verbs or manipulate nodes and their attributes directly give knowledgeable users some flexibility. + </para> + <para> +<emphasis>Interactive:</emphasis> Since Dbus ties in with the main loop, users can mix scripting and mouse driven actions seamlessly. This allows for some unique uses but more importantly makes it easier for people to learn the API since they can play around with it in a scripting console, or even a simple python shell. + </para> + <para> +<emphasis>Responsive:</emphasis> Since one of the advantages of scripting is the ability to repeat actions many times with great precision it is obvious that the system would have to be fairly fast. The amount of overhead has been limited where possible and functions have been tested for speed. A system to pause updates and only refresh the display after a large number of operations have been completed, ensures that even very complicated renders will not take too long. + </para> + </preface> + + <part> + <title>Concepts</title> + + &Terms; + + </part> + + <part> + <title>Reference</title> + + <reference id="dbus-reference"> + <title>D-Bus API Reference</title> + + <partintro> + <para> + Inkscape provides a D-Bus API for programs to interactively script vector graphics. + </para> + <para> + This API is not yet stable and is likely to change in the future. + </para> + </partintro> + + &dbus-Application; + &dbus-Document; + &dbus-Proposed; + + </reference> + </part> + + <index> + <title>Index</title> + </index> + +</book> + diff --git a/src/extension/dbus/doc/inkscapeDbusTerms.xml b/src/extension/dbus/doc/inkscapeDbusTerms.xml new file mode 100644 index 0000000..45f2d63 --- /dev/null +++ b/src/extension/dbus/doc/inkscapeDbusTerms.xml @@ -0,0 +1,142 @@ +<chapter id="connecting"> + <title>Connecting to the API</title> + + <sect1> + <title>Overview</title> + <para> +There are really two Dbus interfaces provided by Inkscape. One is the application interface, which is constant, and allows one to control the Inkscape application as a whole and create new documents or windows. The second is the document interface. A document interface is automatically generated for every open window, and the commands sent to that interface will affect that particular window. + </para> + <para> +So the basic way of connecting goes like this: Connect to the session bus. Connect to the application interface. Request a new document. Connect to the newly created document interface using the name returned in the last step. Manipulate the document however you want (load files, create shapes, save, etc.) After the connection example there is a shortcut that will shorten this process somewhat in certain circumstances. + </para> + </sect1> + <sect1> + <title>Connection example</title> + <para> +Here is a basic example of connecting to the Bus and getting a new document. (In python for now because it's easy to read.) + </para> + <informalexample language="python" title="simple example"> + <programlisting> +import dbus + +#get the session bus. +bus = dbus.SessionBus() + +#get the object for the application. +inkapp = bus.get_object('org.inkscape', + '/org/inkscape/application') + +#request a new desktop. +desk2 = inkapp.desktop_new(dbus_interface='org.inkscape.application') + +#get the object for that desktop. +inkdoc1 = bus.get_object('org.inkscape', desk2) + +#tell it what interface it is using so we don't have to type it for every method. +doc1 = dbus.Interface(inkdoc1, dbus_interface="org.inkscape.document") + +#use! +doc1.rectangle (0,0,100,100) + </programlisting> + </informalexample> + </sect1> + + <sect1> + <title>Shortcut</title> + <para> +Here is a quicker way if you don't need multiple documents open at once. Since Inkscape starts automatically, and it always creates a blank document we can just connect to that. + </para> + <warning><para> +WARNING: This may not always work, it also might connect you to a document that is in use if Inkscape was already running. Only recommended for testing/experimenting. + </para></warning> + <informalexample language="python" title="simple example"> + <programlisting> +import dbus + +#get the session bus. +bus = dbus.SessionBus() + +#get object +inkdoc1 = bus.get_object('org.inkscape', '/org/inkscape/desktop_0') + +#get interface +doc1 = dbus.Interface(inkdoc1, dbus_interface="org.inkscape.document") + +#ta-da +doc1.rectangle (0,0,100,100) + </programlisting> + </informalexample> + </sect1> + +</chapter> + +<chapter id="terms"> + <title>Terminology</title> + + <anchor id="Coordinate System"/> + <sect1> + <title>Coordinate System</title> + <para> +The coordinate system used by this API may be different than what you are used to (although it is standard in the computer graphics industry.) Simply put the origin (0,0) is in the upper left hand corner of the document. X increases to the right and Y increases downwards. Therefore everything with positive coordinates is in the document. + </para> + <para> +For example: (100,100) would be just below and to the right of the top left corner of the document. + </para> + </sect1> + + <anchor id="Selections"/> + <sect1> + <title>Selections</title> + <para> +Selections are extremely useful ways of managing groups of objects and applying effects to all of them at once. Since much of Inkscapes core functionality is built around manipulating selections they are the key to much of this APIs utility. Manipulate the list of selected objects with <link linkend="document.selection_set">selection_set()</link>, <link linkend="document.selection_add">selection_add()</link>, and <link linkend="document.selection_box">selection_box()</link> and then call whatever selection function you need. + </para> + </sect1> + + <anchor id="Groups"/> + <sect1> + <title>Groups</title> + <para> +Groups are collections of objects that are treated as a single object. Groups have their own id and can be passed to any function that accepts an object, though some will not have any effect (groups ignore style for instance.) Groups can be transformed and occupy a single level in their layer. Objects within a group can still be modified using their ids, but this will not have any affect on the other group members. Functions like move_to may not work as expected if used on an object that is part of a group that has a transformation applied. + </para> + </sect1> + + <anchor id="Layers and Levels"/> + <sect1> + <title>Layers and Levels</title> + <para> +The basic idea is that things on top cover up things beneath them. The potentially confusing part is that Inkscape implements this in two ways: layers and levels. Levels are what order objects are in within a single layer. So the highest level object is still below all of the objects in the layer above it. <link linkend="document.layer_change_level">layer_change_level()</link> changes the order of layers and <link linkend="document.selection_change_level">selection_change_level()</link> changes the order of objects within a layer. + </para> + <para> +Changing the level of a selection also deserves some explanation. The <link linkend="document.selection_change_level">selection_change_level()</link> function can work in two ways. It can be absolute, "to_top" and "to_bottom" work like you'd expect, sending the entire selection to the top or bottom of that layer. But it can also be relative. "raise" and "lower" only work if there is another shape overlapping above or beneath the selection at the moment. Also if you have two objects selected and they are both occluded by a third, raising the selection once will only raise the first object in the selection above the third object. In other words selections don't move as a group. + </para> + </sect1> + + <anchor id="Style Strings"/> + <sect1> + <title>Style Strings</title> + <para> +Style strings look something like this: "fill:#ff0000;fill-opacity:1;stroke:#0000ff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none". It is a string of key value pairs that determines the style of a particular object. Style strings affect most objects. They can be set all at once or specific key value pairs can be added one by one. Style strings can also be merged, with the new string replacing key/value pairs that it contains and leaving the rest as they were. One could also think of it as the new string taking any attributes it does not have and adopting them from the old string. + </para> + </sect1> + + <anchor id="Paths"/> + <sect1> + <title>Paths</title> + <para> +A path is a string representing a series of points, and how the line curves between the points. It looks something like this: "m 351.42857,296.64789 a 54.285713,87.14286 0 1 1 -108.57143,0 54.285713,87.14286 0 1 1 108.57143,0 z" and is usually found as an attribute of a shape with the label "d". All shapes except rectangles have this "d" attribute. + </para> + <para> +Just because a shape has a path does not mean it IS a path however. A path object has no attributes except the path and a style. Calling <link linkend="document.object_to_path">object_to_path()</link> will convert any object to a path, stripping away any other attributes except id and style which stay the same. This will not change the visual appearance but you will no longer be able to use shape handles or affect it by changing any attributes except for "style", "d", and "transform". Some functions may require paths. + </para> + </sect1> + + <anchor id="Nodes"/> + <sect1> + <title>Nodes</title> + <para> +To be written. + </para> + </sect1> + +</chapter> + diff --git a/src/extension/dbus/doc/spec-to-docbook.xsl b/src/extension/dbus/doc/spec-to-docbook.xsl new file mode 100644 index 0000000..5282167 --- /dev/null +++ b/src/extension/dbus/doc/spec-to-docbook.xsl @@ -0,0 +1,545 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +<?xml version='1.0'?> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" + exclude-result-prefixes="doc"> +<!-- + Convert D-Bus Glib xml into DocBook refentries + Copyright (C) 2007 William Jon McCann + Released under GNU GPL v2+, read the file 'COPYING' for more information. +--> +<xsl:output method="xml" indent="yes" encoding="UTF-8"/> + +<xsl:template match="/"> + +<xsl:variable name="interface" select="//interface/@name"/> +<xsl:variable name="basename"> + <xsl:call-template name="interface-basename"> + <xsl:with-param name="str" select="$interface"/> + </xsl:call-template> +</xsl:variable> + +<refentry><xsl:attribute name="id"><xsl:value-of select="$basename"/></xsl:attribute> + <refmeta> + <refentrytitle role="top_of_page"><xsl:value-of select="//interface/@name"/></refentrytitle> + </refmeta> + + <refnamediv> + <refname><xsl:value-of select="//interface/@name"/></refname> + <refpurpose><xsl:value-of select="$basename"/> interface</refpurpose> + </refnamediv> + + <refsynopsisdiv role="synopsis"> + <title role="synopsis.title">Methods</title> + <synopsis> + <xsl:call-template name="methods-synopsis"> + <xsl:with-param name="basename" select="$basename"/> + </xsl:call-template> + </synopsis> + </refsynopsisdiv> + + <xsl:choose> + <xsl:when test="count(///signal) > 0"> + <refsect1 role="signal_proto"> + <title role="signal_proto.title">Signals</title> + <synopsis> + <xsl:call-template name="signals-synopsis"> + <xsl:with-param name="basename" select="$basename"/> + </xsl:call-template> + </synopsis> + </refsect1> + </xsl:when> + </xsl:choose> + + <refsect1 role="impl_interfaces"> + <title role="impl_interfaces.title">Implemented Interfaces</title> + <para> + Objects implementing <xsl:value-of select="$interface"/> also implements + org.freedesktop.DBus.Introspectable, + org.freedesktop.DBus.Properties + </para> + </refsect1> + + <xsl:choose> + <xsl:when test="count(///property) > 0"> + <refsect1 role="properties"> + <title role="properties.title">Properties</title> + <synopsis> + <xsl:call-template name="properties-synopsis"> + <xsl:with-param name="basename" select="$basename"/> + </xsl:call-template> + </synopsis> + </refsect1> + </xsl:when> + </xsl:choose> + + <refsect1 role="desc"> + <title role="desc.title">Description</title> + <para> + <xsl:apply-templates select="//interface/doc:doc"/> + </para> + </refsect1> + + <refsect1 role="details"> + <title role="details.title">Details</title> + <xsl:call-template name="method-details"> + <xsl:with-param name="basename" select="$basename"/> + </xsl:call-template> + </refsect1> + + <xsl:choose> + <xsl:when test="count(///signal) > 0"> + <refsect1 role="signals"> + <title role="signals.title">Signal Details</title> + <xsl:call-template name="signal-details"> + <xsl:with-param name="basename" select="$basename"/> + </xsl:call-template> + </refsect1> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="count(///property) > 0"> + <refsect1 role="property_details"> + <title role="property_details.title">Property Details</title> + <xsl:call-template name="property-details"> + <xsl:with-param name="basename" select="$basename"/> + </xsl:call-template> + </refsect1> + </xsl:when> + </xsl:choose> + +</refentry> +</xsl:template> + + +<xsl:template name="property-doc"> + <xsl:apply-templates select="doc:doc/doc:description"/> + + <variablelist role="params"> + <xsl:for-each select="arg"> +<varlistentry><term><parameter><xsl:value-of select="@name"/></parameter>:</term> +<listitem><simpara><xsl:value-of select="doc:doc/doc:summary"/></simpara></listitem> +</varlistentry> + </xsl:for-each> + </variablelist> + + <xsl:apply-templates select="doc:doc/doc:since"/> + <xsl:apply-templates select="doc:doc/doc:deprecated"/> + <xsl:apply-templates select="doc:doc/doc:permission"/> + <xsl:apply-templates select="doc:doc/doc:seealso"/> +</xsl:template> + + +<xsl:template name="property-details"> + <xsl:param name="basename"/> + <xsl:variable name="longest"> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="@name"/> + </xsl:call-template> + </xsl:variable> + <xsl:for-each select="///property"> + <refsect2> + <title><anchor role="function"><xsl:attribute name="id"><xsl:value-of select="$basename"/>:<xsl:value-of select="@name"/></xsl:attribute></anchor>The "<xsl:value-of select="@name"/>" property</title> +<indexterm><primary><xsl:value-of select="@name"/></primary><secondary><xsl:value-of select="$basename"/></secondary></indexterm> +<programlisting>'<xsl:value-of select="@name"/>'<xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="2"/></xsl:call-template> +<xsl:call-template name="property-args"><xsl:with-param name="indent" select="string-length(@name) + 2"/></xsl:call-template></programlisting> + </refsect2> + + <xsl:call-template name="property-doc"/> + + </xsl:for-each> +</xsl:template> + +<xsl:template name="signal-doc"> + <xsl:apply-templates select="doc:doc/doc:description"/> + + <variablelist role="params"> + <xsl:for-each select="arg"> +<varlistentry><term><parameter><xsl:value-of select="@name"/></parameter>:</term> +<listitem><simpara><xsl:value-of select="doc:doc/doc:summary"/></simpara></listitem> +</varlistentry> + </xsl:for-each> + </variablelist> + + <xsl:apply-templates select="doc:doc/doc:since"/> + <xsl:apply-templates select="doc:doc/doc:deprecated"/> + <xsl:apply-templates select="doc:doc/doc:permission"/> + <xsl:apply-templates select="doc:doc/doc:seealso"/> +</xsl:template> + +<xsl:template name="signal-details"> + <xsl:param name="basename"/> + <xsl:variable name="longest"> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="@name"/> + </xsl:call-template> + </xsl:variable> + <xsl:for-each select="///signal"> + <refsect2> + <title><anchor role="function"><xsl:attribute name="id"><xsl:value-of select="$basename"/>::<xsl:value-of select="@name"/></xsl:attribute></anchor>The <xsl:value-of select="@name"/> signal</title> +<indexterm><primary><xsl:value-of select="@name"/></primary><secondary><xsl:value-of select="$basename"/></secondary></indexterm> +<programlisting><xsl:value-of select="@name"/> (<xsl:call-template name="signal-args"><xsl:with-param name="indent" select="string-length(@name) + 2"/><xsl:with-param name="prefix" select="."/></xsl:call-template>)</programlisting> + </refsect2> + + <xsl:call-template name="signal-doc"/> + + </xsl:for-each> +</xsl:template> + +<xsl:template match="doc:code"> +<programlisting> +<xsl:apply-templates /> +</programlisting> +</xsl:template> + +<xsl:template match="doc:tt"> + <literal> + <xsl:apply-templates /> + </literal> +</xsl:template> + +<xsl:template match="doc:i"> + <emphasis> + <xsl:apply-templates /> + </emphasis> +</xsl:template> + +<xsl:template match="doc:b"> + <emphasis role="bold"> + <xsl:apply-templates /> + </emphasis> +</xsl:template> + +<xsl:template match="doc:ulink"> + <ulink> + <xsl:attribute name="url"><xsl:value-of select="@url"/></xsl:attribute> + <xsl:value-of select="."/> + </ulink> +</xsl:template> + +<xsl:template match="doc:summary"> + <xsl:apply-templates /> +</xsl:template> + +<xsl:template match="doc:example"> +<informalexample> +<xsl:apply-templates /> +</informalexample> +</xsl:template> + +<xsl:template name="listitems-do-term"> + <xsl:param name="str"/> + <xsl:choose> + <xsl:when test="string-length($str) > 0"> + <emphasis role="bold"><xsl:value-of select="$str"/>: </emphasis> + </xsl:when> + </xsl:choose> +</xsl:template> + +<xsl:template name="do-listitems"> + <xsl:for-each select="doc:item"> + <listitem> + <xsl:call-template name="listitems-do-term"><xsl:with-param name="str" select="doc:term"/></xsl:call-template> + <xsl:apply-templates select="doc:definition"/> + </listitem> + </xsl:for-each> +</xsl:template> + +<xsl:template match="doc:list"> + <para> + <xsl:choose> + <xsl:when test="contains(@type,'number')"> + <orderedlist> + <xsl:call-template name="do-listitems"/> + </orderedlist> + </xsl:when> + <xsl:otherwise> + <itemizedlist> + <xsl:call-template name="do-listitems"/> + </itemizedlist> + </xsl:otherwise> + </xsl:choose> + </para> +</xsl:template> + +<xsl:template match="doc:para"> +<para> +<xsl:apply-templates /> +</para> +</xsl:template> + +<xsl:template match="doc:description"> +<xsl:apply-templates /> +</xsl:template> + +<xsl:template match="doc:since"> +<para role="since">Since <xsl:value-of select="@version"/> +</para> +</xsl:template> + +<xsl:template match="doc:deprecated"> + <xsl:variable name="name" select="../../@name"/> + <xsl:variable name="parent"> + <xsl:call-template name="interface-basename"> + <xsl:with-param name="str" select="../../../@name"/>/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="type" select="name(../..)"/> + + <para role="deprecated"> + <warning><para><literal><xsl:value-of select="$name"/></literal> is deprecated since version <xsl:value-of select="@version"/> and should not be used in newly-written code. Use + + <xsl:variable name="to"> + <xsl:choose> + <xsl:when test="contains($type,'property')"> + <xsl:value-of select="$parent"/>:<xsl:value-of select="@instead"/> + </xsl:when> + <xsl:when test="contains($type,'signal')"> + <xsl:value-of select="$parent"/>::<xsl:value-of select="@instead"/> + </xsl:when> + <xsl:when test="contains($type,'method')"> + <xsl:value-of select="$parent"/>.<xsl:value-of select="@instead"/> + </xsl:when> + <xsl:when test="contains($type,'interface')"> + <xsl:value-of select="@instead"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@instead"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:call-template name="create-link"> + <xsl:with-param name="type" select="$type"/> + <xsl:with-param name="to" select="$to"/> + <xsl:with-param name="val" select="@instead"/> + </xsl:call-template> +instead.</para></warning> +</para> +</xsl:template> + +<xsl:template match="doc:permission"> +<para role="permission"> +<xsl:apply-templates /> +</para> +</xsl:template> + +<xsl:template match="doc:errors"> +<para role="errors"> +<xsl:apply-templates /> +</para> +</xsl:template> + +<xsl:template match="doc:seealso"> +<para> +See also: +<xsl:apply-templates /> + +</para> +</xsl:template> + +<xsl:template name="create-link"> + <xsl:param name="type"/> + <xsl:param name="to"/> + <xsl:param name="val"/> + + <xsl:choose> + <xsl:when test="contains($type,'property')"> + <link><xsl:attribute name="linkend"><xsl:value-of select="$to"/></xsl:attribute><literal><xsl:value-of select="$val"/></literal></link> + </xsl:when> + <xsl:when test="contains($type,'signal')"> + <link><xsl:attribute name="linkend"><xsl:value-of select="$to"/></xsl:attribute><literal><xsl:value-of select="$val"/></literal></link> + </xsl:when> + <xsl:when test="contains($type,'method')"> + <link><xsl:attribute name="linkend"><xsl:value-of select="$to"/></xsl:attribute><function><xsl:value-of select="$val"/></function></link> + </xsl:when> + <xsl:when test="contains($type,'interface')"> + <link><xsl:attribute name="linkend"><xsl:value-of select="$to"/></xsl:attribute><xsl:value-of select="$val"/></link> + </xsl:when> + </xsl:choose> +</xsl:template> + +<xsl:template match="doc:ref"> + <xsl:call-template name="create-link"> + <xsl:with-param name="type" select="@type"/> + <xsl:with-param name="to" select="@to"/> + <xsl:with-param name="val" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template name="method-doc"> + <xsl:apply-templates select="doc:doc/doc:description"/> + + <variablelist role="params"> + <xsl:for-each select="arg"> +<varlistentry><term><parameter><xsl:value-of select="@name"/></parameter>:</term> +<listitem><simpara><xsl:apply-templates select="doc:doc/doc:summary"/></simpara></listitem> +</varlistentry> + </xsl:for-each> + </variablelist> + + <xsl:apply-templates select="doc:doc/doc:since"/> + <xsl:apply-templates select="doc:doc/doc:deprecated"/> + + <xsl:choose> + <xsl:when test="count(doc:doc/doc:errors) > 0"> + <refsect3> + <title>Errors</title> + <variablelist role="errors"> + <xsl:for-each select="doc:doc/doc:errors/doc:error"> + <varlistentry> + <term><parameter><xsl:value-of select="@name"/></parameter>:</term> + <listitem><simpara><xsl:apply-templates select="."/></simpara></listitem> + </varlistentry> + </xsl:for-each> + </variablelist> + </refsect3> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="count(doc:doc/doc:permission) > 0"> + <refsect3> + <title>Permissions</title> + <xsl:apply-templates select="doc:doc/doc:permission"/> + </refsect3> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates select="doc:doc/doc:seealso"/> +</xsl:template> + +<xsl:template name="method-details"> + <xsl:param name="basename"/> + <xsl:variable name="longest"> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="@name"/> + </xsl:call-template> + </xsl:variable> + <xsl:for-each select="///method"> + <refsect2> + <title><anchor role="function"><xsl:attribute name="id"><xsl:value-of select="$basename"/>.<xsl:value-of select="@name"/></xsl:attribute></anchor><xsl:value-of select="@name"/> ()</title> +<indexterm><primary><xsl:value-of select="@name"/></primary><secondary><xsl:value-of select="$basename"/></secondary></indexterm> +<programlisting><xsl:value-of select="@name"/> (<xsl:call-template name="method-args"><xsl:with-param name="indent" select="string-length(@name) + 2"/><xsl:with-param name="prefix" select="."/></xsl:call-template>)</programlisting> + </refsect2> + + <xsl:call-template name="method-doc"/> + + </xsl:for-each> +</xsl:template> + + +<xsl:template name="properties-synopsis"> + <xsl:param name="basename"/> + <xsl:variable name="longest"> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="///property/@name"/> + </xsl:call-template> + </xsl:variable> + <xsl:for-each select="///property"> +<link><xsl:attribute name="linkend"><xsl:value-of select="$basename"/>:<xsl:value-of select="@name"/></xsl:attribute>'<xsl:value-of select="@name"/>'</link><xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="$longest - string-length(@name) + 1"/></xsl:call-template> <xsl:call-template name="property-args"><xsl:with-param name="indent" select="$longest + 2"/></xsl:call-template> +</xsl:for-each> +</xsl:template> + + +<xsl:template name="signals-synopsis"> + <xsl:param name="basename"/> + <xsl:variable name="longest"> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="///signal/@name"/> + </xsl:call-template> + </xsl:variable> + <xsl:for-each select="///signal"> +<link><xsl:attribute name="linkend"><xsl:value-of select="$basename"/>::<xsl:value-of select="@name"/></xsl:attribute><xsl:value-of select="@name"/></link><xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="$longest - string-length(@name) + 1"/></xsl:call-template>(<xsl:call-template name="signal-args"><xsl:with-param name="indent" select="$longest + 2"/><xsl:with-param name="prefix" select="///signal"/></xsl:call-template>) +</xsl:for-each> +</xsl:template> + + +<xsl:template name="methods-synopsis"> + <xsl:param name="basename"/> + <xsl:variable name="longest"> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="///method/@name"/> + </xsl:call-template> + </xsl:variable> + <xsl:for-each select="///method"> +<link><xsl:attribute name="linkend"><xsl:value-of select="$basename"/>.<xsl:value-of select="@name"/></xsl:attribute><xsl:value-of select="@name"/></link><xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="$longest - string-length(@name) + 1"/></xsl:call-template>(<xsl:call-template name="method-args"><xsl:with-param name="indent" select="$longest + 2"/><xsl:with-param name="prefix" select="///method"/></xsl:call-template>) +</xsl:for-each> +</xsl:template> + + +<xsl:template name="method-args"><xsl:param name="indent"/><xsl:param name="prefix"/><xsl:variable name="longest"><xsl:call-template name="find-longest"><xsl:with-param name="set" select="$prefix/arg/@type"/></xsl:call-template></xsl:variable><xsl:for-each select="arg"><xsl:value-of select="@direction"/> +<xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="4 - string-length(@direction)"/></xsl:call-template>'<xsl:value-of select="@type"/>'<xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="$longest - string-length(@type) + 1"/></xsl:call-template> +<xsl:value-of select="@name"/><xsl:if test="not(position() = last())">, +<xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="$indent"/></xsl:call-template></xsl:if> +</xsl:for-each> +</xsl:template> + + +<xsl:template name="signal-args"><xsl:param name="indent"/><xsl:param name="prefix"/><xsl:variable name="longest"><xsl:call-template name="find-longest"><xsl:with-param name="set" select="$prefix/arg/@type"/></xsl:call-template></xsl:variable><xsl:for-each select="arg">'<xsl:value-of select="@type"/>'<xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="$longest - string-length(@type) + 1"/></xsl:call-template> +<xsl:value-of select="@name"/><xsl:if test="not(position() = last())">, +<xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="$indent"/></xsl:call-template></xsl:if> +</xsl:for-each> +</xsl:template> + + +<xsl:template name="property-args"><xsl:param name="indent"/> +<xsl:value-of select="@access"/><xsl:call-template name="pad-spaces"><xsl:with-param name="width" select="9 - string-length(@access) + 1"/></xsl:call-template>'<xsl:value-of select="@type"/>' +</xsl:template> + + +<xsl:template name="pad-spaces"> + <xsl:param name="width"/> + <xsl:variable name="spaces" select="' '" ></xsl:variable> + <xsl:value-of select="substring($spaces,1,$width)"/> +</xsl:template> + + +<xsl:template name="find-longest"> + <xsl:param name="set"/> + <xsl:param name="index" select="1"/> + <xsl:param name="longest" select="0"/> + + <xsl:choose> + <xsl:when test="$index > count($set)"> + <!--finished looking--> + <xsl:value-of select="$longest"/> + </xsl:when> + <xsl:when test="string-length($set[$index])>$longest"> + <!--found new longest--> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="$set"/> + <xsl:with-param name="index" select="$index + 1"/> + <xsl:with-param name="longest" select="string-length($set[$index])"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!--this isn't any longer--> + <xsl:call-template name="find-longest"> + <xsl:with-param name="set" select="$set"/> + <xsl:with-param name="index" select="$index + 1"/> + <xsl:with-param name="longest" select="$longest"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + +<xsl:template name="interface-basename"> + <xsl:param name="str"/> + <xsl:choose> + <xsl:when test="contains($str,'.')"> + <xsl:call-template name="interface-basename"> + <xsl:with-param name="str" select="substring-after($str,'.')"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$str"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> + diff --git a/src/extension/dbus/document-interface.cpp b/src/extension/dbus/document-interface.cpp new file mode 100644 index 0000000..2384c50 --- /dev/null +++ b/src/extension/dbus/document-interface.cpp @@ -0,0 +1,1484 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is where the implementation of the DBus based document API lives. + * All the methods in here (except in the helper section) are + * designed to be called remotely via DBus. application-interface.cpp + * has the methods used to connect to the bus and get a document instance. + * + * Documentation for these methods is in document-interface.xml + * which is the "gold standard" as to how the interface should work. + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glib.h> +#include <string.h> + +#include <dbus/dbus-glib.h> + +//#include "2geom/svg-path-parser.h" //get_node_coordinates +#include "inkscape-application.h" // create_window() + +#include "application-interface.h" +#include "desktop-style.h" //sp_desktop_get_style +#include "desktop.h" +#include "document-interface.h" +#include "document-undo.h" +#include "document.h" // getReprDoc() +#include "file.h" //IO +#include "inkscape.h" //inkscape_find_desktop_by_dkey, activate desktops +#include "layer-fns.h" //LPOS_BELOW +#include "layer-model.h" +#include "print.h" //IO +#include "selection-chemistry.h"// lots of selection functions +#include "selection.h" //selection struct +#include "style.h" //style_write +#include "text-editing.h" +#include "verbs.h" + +#include "helper/action-context.h" +#include "helper/action.h" //sp_action_perform + +#include "display/canvas-text.h" //text +#include "display/sp-canvas.h" //text + +#include "extension/output.h" //IO +#include "extension/system.h" //IO + +#include "live_effects/parameter/text.h" //text + +#include "object/sp-ellipse.h" +#include "object/sp-object.h" +#include "object/sp-root.h" + +#include "util/units.h" + +#include "xml/repr.h" //sp_repr_document_new + +#if 0 +#include <libxml/tree.h> +#include <libxml/parser.h> +#include <libxml/xpath.h> +#include <libxml/xpathInternals.h> +#endif + + enum + { + OBJECT_MOVED_SIGNAL, + LAST_SIGNAL + }; + + static guint signals[LAST_SIGNAL] = { 0 }; + + +/**************************************************************************** + HELPER / SHORTCUT FUNCTIONS +****************************************************************************/ + +/* + * This function or the one below it translates the user input for an object + * into Inkscapes internal representation. It is called by almost every + * method so it should be as fast as possible. + * + * (eg turns "rect2234" to an SPObject or Inkscape::XML::Node) + * + * If the internal representation changes (No more 'id' attributes) this is the + * place to adjust things. + */ +Inkscape::XML::Node * +get_repr_by_name (SPDocument *doc, gchar *name, GError **error) +{ + /* ALTERNATIVE (is this faster if only repr is needed?) + Inkscape::XML::Node *node = sp_repr_lookup_name((doc->root)->repr, name); + */ + SPObject * obj = doc->getObjectById(name); + if (!obj) + { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OBJECT, "Object '%s' not found in document.", name); + return NULL; + } + return obj->getRepr(); +} + +/* + * See comment for get_repr_by_name, above. + */ +SPObject * +get_object_by_name (SPDocument *doc, gchar *name, GError **error) +{ + SPObject * obj = doc->getObjectById(name); + if (!obj) + { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OBJECT, "Object '%s' not found in document.", name); + return NULL; + } + return obj; +} + +/* + * Tests for NULL strings and throws an appropriate error. + * Every method that takes a string parameter (other than the + * name of an object, that's tested separately) should call this. + */ +gboolean +dbus_check_string (gchar *string, GError ** error, const gchar * errorstr) +{ + if (string == NULL) + { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OTHER, "%s", errorstr); + return FALSE; + } + return TRUE; +} + +/* + * This is used to return object values to the user + */ +const gchar * +get_name_from_object (SPObject * obj) +{ + return obj->getRepr()->attribute("id"); +} + +/* + * Some verbs (cut, paste) only work on the active layer. + * This makes sure that the document that is about to receive a command is active. + */ +void +desktop_ensure_active (SPDesktop* desk) { + if (desk != SP_ACTIVE_DESKTOP) + INKSCAPE.activate_desktop (desk); + return; +} + +gdouble +selection_get_center_x (Inkscape::Selection *sel){ + Geom::OptRect box = sel->documentBounds(SPItem::GEOMETRIC_BBOX); + return box ? box->midpoint()[Geom::X] : 0; +} + +gdouble +selection_get_center_y (Inkscape::Selection *sel){ + Geom::OptRect box = sel->documentBounds(SPItem::GEOMETRIC_BBOX); + return box ? box->midpoint()[Geom::Y] : 0; +} + +/* + * This function is used along with selection_restore to + * take advantage of functionality provided by a selection + * for a single object. + * + * It saves the current selection and sets the selection to + * the object specified. Any selection verb can be used on the + * object and then selection_restore is called, restoring the + * original selection. + * + * This should be mostly transparent to the user who need never + * know we never bothered to implement it separately. Although + * they might see the selection box flicker if used in a loop. + */ +std::vector<SPObject*> +selection_swap(Inkscape::Selection *sel, gchar *name, GError **error) +{ + std::vector<SPObject*> oldsel = std::vector<SPObject*>(sel->objects().begin(), sel->objects().end()); + + sel->set(get_object_by_name(sel->layers()->getDocument(), name, error)); + return oldsel; +} + +/* + * See selection_swap, above + */ +void +selection_restore(Inkscape::Selection *sel, std::vector<SPObject*> oldsel) +{ + // ... setList used to work here + sel->clear(); + sel->add(oldsel.begin(), oldsel.end()); +} + +/* + * Shortcut for creating a Node. + */ +Inkscape::XML::Node * +dbus_create_node (SPDocument *doc, const gchar *type) +{ + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + return xml_doc->createElement(type); +} + +/* + * Called by the shape creation functions. Gets the default style for the doc + * or sets it arbitrarily if none. + * + * There is probably a better way to do this (use the shape tools default styles) + * but I'm not sure how. + */ +gchar *finish_create_shape (DocumentInterface *doc_interface, GError ** /*error*/, Inkscape::XML::Node *newNode, gchar *desc) +{ + SPCSSAttr *style = NULL; + if (doc_interface->target.getDesktop()) { + style = sp_desktop_get_style(doc_interface->target.getDesktop(), TRUE); + } + if (style) { + Glib::ustring str; + sp_repr_css_write_string(style, str); + newNode->setAttributeOrRemoveIfEmpty("style", str); + } + else { + newNode->setAttribute("style", "fill:#0000ff;fill-opacity:1;stroke:#c900b9;stroke-width:0;stroke-miterlimit:0;stroke-opacity:1;stroke-dasharray:none", true); + } + + doc_interface->target.getSelection()->layers()->currentLayer()->appendChildRepr(newNode); + doc_interface->target.getSelection()->layers()->currentLayer()->updateRepr(); + + if (doc_interface->updates) { + Inkscape::DocumentUndo::done(doc_interface->target.getDocument(), 0, (gchar *)desc); + } + + return strdup(newNode->attribute("id")); +} + +/* + * This is the code used internally to call all the verbs. + * + * It handles error reporting and update pausing (which needs some work.) + * This is a good place to improve efficiency as it is called a lot. + * + * document_interface_call_verb is similar but is called by the user. + */ +gboolean +dbus_call_verb (DocumentInterface *doc_interface, int verbid, GError **error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + if ( desk ) { + desktop_ensure_active (desk); + } + Inkscape::Verb *verb = Inkscape::Verb::get( verbid ); + if ( verb ) { + SPAction *action = verb->get_action(doc_interface->target); + if ( action ) { + sp_action_perform( action, NULL ); + if (doc_interface->updates) + Inkscape::DocumentUndo::done(doc_interface->target.getDocument(), verb->get_code(), verb->get_tip()); + return TRUE; + } + } + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_VERB, "Verb failed to execute"); + return FALSE; +} + +/* + * Check that the desktop is not NULL. If it is NULL, set the error to a useful message. + */ +bool +ensure_desktop_valid(SPDesktop* desk, GError **error) +{ + if (desk) { + return true; + } + + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OTHER, "Document interface action requires a GUI"); + return false; +} + +/**************************************************************************** + DOCUMENT INTERFACE CLASS STUFF +****************************************************************************/ + +G_DEFINE_TYPE(DocumentInterface, document_interface, G_TYPE_OBJECT) + +static void +document_interface_finalize (GObject *object) +{ + G_OBJECT_CLASS (document_interface_parent_class)->finalize (object); +} + + +static void +document_interface_class_init (DocumentInterfaceClass *klass) +{ + GObjectClass *object_class; + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = document_interface_finalize; + signals[OBJECT_MOVED_SIGNAL] = + g_signal_new ("object_moved", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__STRING, + G_TYPE_NONE, 1, G_TYPE_STRING); +} + +static void +document_interface_init (DocumentInterface *doc_interface) +{ + doc_interface->target = Inkscape::ActionContext(); +} + + +DocumentInterface * +document_interface_new (void) +{ + return (DocumentInterface*)g_object_new (TYPE_DOCUMENT_INTERFACE, NULL); +} + + + +/**************************************************************************** + MISC FUNCTIONS +****************************************************************************/ + +gboolean document_interface_delete_all(DocumentInterface *doc_interface, GError ** /*error*/) +{ + sp_edit_clear_all(doc_interface->target.getSelection()); + return TRUE; +} + +gboolean +document_interface_call_verb (DocumentInterface *doc_interface, gchar *verbid, GError **error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + if ( desk ) { + desktop_ensure_active (desk); + } + Inkscape::Verb *verb = Inkscape::Verb::getbyid( verbid ); + if ( verb ) { + SPAction *action = verb->get_action(doc_interface->target); + if ( action ) { + sp_action_perform( action, NULL ); + if (doc_interface->updates) { + Inkscape::DocumentUndo::done(doc_interface->target.getDocument(), verb->get_code(), verb->get_tip()); + } + return TRUE; + } + } + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_VERB, "Verb '%s' failed to execute or was not found.", verbid); + return FALSE; +} + + +/**************************************************************************** + CREATION FUNCTIONS +****************************************************************************/ + +gchar* +document_interface_rectangle (DocumentInterface *doc_interface, int x, int y, + int width, int height, GError **error) +{ + + + Inkscape::XML::Node *newNode = dbus_create_node(doc_interface->target.getDocument(), "svg:rect"); + sp_repr_set_int(newNode, "x", x); //could also use newNode->setAttribute() + sp_repr_set_int(newNode, "y", y); + sp_repr_set_int(newNode, "width", width); + sp_repr_set_int(newNode, "height", height); + return finish_create_shape (doc_interface, error, newNode, (gchar *)"create rectangle"); +} + +gchar* +document_interface_ellipse_center (DocumentInterface *doc_interface, int cx, int cy, + int rx, int ry, GError **error) +{ + Inkscape::XML::Node *newNode = dbus_create_node(doc_interface->target.getDocument(), "svg:path"); + newNode->setAttribute("sodipodi:type", "arc"); + sp_repr_set_int(newNode, "sodipodi:cx", cx); + sp_repr_set_int(newNode, "sodipodi:cy", cy); + sp_repr_set_int(newNode, "sodipodi:rx", rx); + sp_repr_set_int(newNode, "sodipodi:ry", ry); + return finish_create_shape (doc_interface, error, newNode, (gchar *)"create circle"); +} + +gchar* +document_interface_polygon (DocumentInterface *doc_interface, int cx, int cy, + int radius, int rotation, int sides, + GError **error) +{ + gdouble rot = ((rotation / 180.0) * M_PI) - M_PI_2; + Inkscape::XML::Node *newNode = dbus_create_node(doc_interface->target.getDocument(), "svg:path"); + newNode->setAttribute("inkscape:flatsided", "true"); + newNode->setAttribute("sodipodi:type", "star"); + sp_repr_set_int(newNode, "sodipodi:cx", cx); + sp_repr_set_int(newNode, "sodipodi:cy", cy); + sp_repr_set_int(newNode, "sodipodi:r1", radius); + sp_repr_set_int(newNode, "sodipodi:r2", radius); + sp_repr_set_int(newNode, "sodipodi:sides", sides); + sp_repr_set_int(newNode, "inkscape:randomized", 0); + sp_repr_set_svg_double(newNode, "sodipodi:arg1", rot); + sp_repr_set_svg_double(newNode, "sodipodi:arg2", rot); + sp_repr_set_svg_double(newNode, "inkscape:rounded", 0); + + return finish_create_shape (doc_interface, error, newNode, (gchar *)"create polygon"); +} + +gchar* +document_interface_star (DocumentInterface *doc_interface, int cx, int cy, + int r1, int r2, int sides, gdouble rounded, + gdouble arg1, gdouble arg2, GError **error) +{ + Inkscape::XML::Node *newNode = dbus_create_node(doc_interface->target.getDocument(), "svg:path"); + newNode->setAttribute("inkscape:flatsided", "false"); + newNode->setAttribute("sodipodi:type", "star"); + sp_repr_set_int(newNode, "sodipodi:cx", cx); + sp_repr_set_int(newNode, "sodipodi:cy", cy); + sp_repr_set_int(newNode, "sodipodi:r1", r1); + sp_repr_set_int(newNode, "sodipodi:r2", r2); + sp_repr_set_int(newNode, "sodipodi:sides", sides); + sp_repr_set_int(newNode, "inkscape:randomized", 0); + sp_repr_set_svg_double(newNode, "sodipodi:arg1", arg1); + sp_repr_set_svg_double(newNode, "sodipodi:arg2", arg2); + sp_repr_set_svg_double(newNode, "inkscape:rounded", rounded); + + return finish_create_shape (doc_interface, error, newNode, (gchar *)"create star"); +} + +gchar* +document_interface_ellipse (DocumentInterface *doc_interface, int x, int y, + int width, int height, GError **error) +{ + int rx = width/2; + int ry = height/2; + return document_interface_ellipse_center (doc_interface, x+rx, y+ry, rx, ry, error); +} + +gchar* +document_interface_line (DocumentInterface *doc_interface, int x, int y, + int x2, int y2, GError **error) +{ + Inkscape::XML::Node *newNode = dbus_create_node(doc_interface->target.getDocument(), "svg:path"); + std::stringstream out; + // Not sure why this works. + out << "m " << x << "," << y << " " << x2 - x << "," << y2 - y; + newNode->setAttribute("d", out.str()); + return finish_create_shape (doc_interface, error, newNode, (gchar *)"create line"); +} + +gchar* +document_interface_spiral (DocumentInterface *doc_interface, int cx, int cy, + int r, int revolutions, GError **error) +{ + Inkscape::XML::Node *newNode = dbus_create_node(doc_interface->target.getDocument(), "svg:path"); + newNode->setAttribute("sodipodi:type", "spiral"); + sp_repr_set_int(newNode, "sodipodi:cx", cx); + sp_repr_set_int(newNode, "sodipodi:cy", cy); + sp_repr_set_int(newNode, "sodipodi:radius", r); + sp_repr_set_int(newNode, "sodipodi:revolution", revolutions); + sp_repr_set_int(newNode, "sodipodi:t0", 0); + sp_repr_set_int(newNode, "sodipodi:argument", 0); + sp_repr_set_int(newNode, "sodipodi:expansion", 1); + gchar * retval = finish_create_shape (doc_interface, error, newNode, (gchar *)"create spiral"); + //Makes sure there is no fill for spirals by default. + gchar* newString = g_strconcat(newNode->attribute("style"), ";fill:none", NULL); + newNode->setAttribute("style", newString); + g_free(newString); + return retval; +} + +gchar* +document_interface_text (DocumentInterface *doc_interface, int x, int y, gchar *text, GError **error) +{ + + Inkscape::XML::Node *text_node = dbus_create_node(doc_interface->target.getDocument(), "svg:text"); + sp_repr_set_int(text_node, "x", x); + sp_repr_set_int(text_node, "y", y); + //just a workaround so i can get an spitem from the name + gchar *name = finish_create_shape (doc_interface, error, text_node, (gchar *)"create text"); + + SPItem* text_obj=(SPItem* )get_object_by_name(doc_interface->target.getDocument(), name, error); + sp_te_set_repr_text_multiline(text_obj, text); + + return name; +} + +gchar * +document_interface_image (DocumentInterface *doc_interface, int x, int y, gchar *filename, GError **error) +{ + gchar * uri = g_filename_to_uri (filename, FALSE, error); + if (!uri) + return FALSE; + + Inkscape::XML::Node *newNode = dbus_create_node(doc_interface->target.getDocument(), "svg:image"); + sp_repr_set_int(newNode, "x", x); + sp_repr_set_int(newNode, "y", y); + newNode->setAttribute("xlink:href", uri); + + doc_interface->target.getSelection()->layers()->currentLayer()->appendChildRepr(newNode); + doc_interface->target.getSelection()->layers()->currentLayer()->updateRepr(); + + if (doc_interface->updates) + Inkscape::DocumentUndo::done(doc_interface->target.getDocument(), 0, "Imported bitmap."); + + //g_free(uri); + return strdup(newNode->attribute("id")); +} + +gchar *document_interface_node(DocumentInterface *doc_interface, gchar *type, GError ** /*error*/) +{ + SPDocument * doc = doc_interface->target.getDocument(); + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + + Inkscape::XML::Node *newNode = xml_doc->createElement(type); + + doc_interface->target.getSelection()->layers()->currentLayer()->appendChildRepr(newNode); + doc_interface->target.getSelection()->layers()->currentLayer()->updateRepr(); + + if (doc_interface->updates) { + Inkscape::DocumentUndo::done(doc, 0, (gchar *)"created empty node"); + } + + return strdup(newNode->attribute("id")); +} + +/**************************************************************************** + ENVIRONMENT FUNCTIONS +****************************************************************************/ +gdouble +document_interface_document_get_width (DocumentInterface *doc_interface) +{ + return doc_interface->target.getDocument()->getWidth().value("px"); +} + +gdouble +document_interface_document_get_height (DocumentInterface *doc_interface) +{ + return doc_interface->target.getDocument()->getHeight().value("px"); +} + +gchar *document_interface_document_get_css(DocumentInterface *doc_interface, GError ** error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(desk, error), NULL); + SPCSSAttr *current = desk->current; + Glib::ustring str; + sp_repr_css_write_string(current, str); + return (str.empty() ? NULL : g_strdup (str.c_str())); +} + +gboolean document_interface_document_merge_css(DocumentInterface *doc_interface, + gchar *stylestring, GError ** error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(desk, error), FALSE); + SPCSSAttr * style = sp_repr_css_attr_new(); + sp_repr_css_attr_add_from_string(style, stylestring); + sp_desktop_set_style(desk, style); + return TRUE; +} + +gboolean document_interface_document_set_css(DocumentInterface *doc_interface, + gchar *stylestring, GError ** error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(desk, error), FALSE); + SPCSSAttr * style = sp_repr_css_attr_new(); + sp_repr_css_attr_add_from_string (style, stylestring); + //Memory leak? + desk->current = style; + return TRUE; +} + +gboolean +document_interface_document_resize_to_fit_selection (DocumentInterface *doc_interface, + GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_FIT_CANVAS_TO_SELECTION, error); +} + +gboolean +document_interface_document_set_display_area (DocumentInterface *doc_interface, + double x0, + double y0, + double x1, + double y1, + double border, + GError **error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(desk, error), FALSE); + desk->set_display_area (Geom::Rect( Geom::Point(x0,y0), Geom::Point(x1,y1)), border, false ); + return TRUE; +} + + +GArray * +document_interface_document_get_display_area (DocumentInterface *doc_interface) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + if (!desk) { + return NULL; + } + Geom::Rect const d = desk->get_display_area(); + + GArray * dArr = g_array_new (TRUE, TRUE, sizeof(double)); + + double x0 = d.min()[Geom::X]; + double y0 = d.min()[Geom::Y]; + double x1 = d.max()[Geom::X]; + double y1 = d.max()[Geom::Y]; + g_array_append_val (dArr, x0); // + g_array_append_val (dArr, y0); + g_array_append_val (dArr, x1); + g_array_append_val (dArr, y1); + return dArr; + +} + + +/**************************************************************************** + OBJECT FUNCTIONS +****************************************************************************/ + +gboolean +document_interface_set_attribute (DocumentInterface *doc_interface, char *shape, + char *attribute, char *newval, GError **error) +{ + Inkscape::XML::Node *newNode = get_repr_by_name(doc_interface->target.getDocument(), shape, error); + + /* ALTERNATIVE (is this faster?) + Inkscape::XML::Node *newnode = sp_repr_lookup_name((doc->root)->repr, name); + */ + if (!dbus_check_string(newval, error, "New value string was empty.")) + return FALSE; + + if (!newNode) + return FALSE; + + newNode->setAttribute(attribute, newval, true); + return TRUE; +} + +gboolean +document_interface_set_int_attribute (DocumentInterface *doc_interface, + char *shape, char *attribute, + int newval, GError **error) +{ + Inkscape::XML::Node *newNode = get_repr_by_name (doc_interface->target.getDocument(), shape, error); + if (!newNode) + return FALSE; + + sp_repr_set_int (newNode, attribute, newval); + return TRUE; +} + + +gboolean +document_interface_set_double_attribute (DocumentInterface *doc_interface, + char *shape, char *attribute, + double newval, GError **error) +{ + Inkscape::XML::Node *newNode = get_repr_by_name (doc_interface->target.getDocument(), shape, error); + + if (!dbus_check_string (attribute, error, "New value string was empty.")) + return FALSE; + if (!newNode) + return FALSE; + + sp_repr_set_svg_double (newNode, attribute, newval); + return TRUE; +} + +gchar * +document_interface_get_attribute (DocumentInterface *doc_interface, char *shape, + char *attribute, GError **error) +{ + Inkscape::XML::Node *newNode = get_repr_by_name(doc_interface->target.getDocument(), shape, error); + + if (!dbus_check_string (attribute, error, "Attribute name empty.")) + return NULL; + if (!newNode) + return NULL; + + return g_strdup(newNode->attribute(attribute)); +} + +gboolean +document_interface_move (DocumentInterface *doc_interface, gchar *name, gdouble x, + gdouble y, GError **error) +{ + std::vector<SPObject*> oldsel = selection_swap(doc_interface->target.getSelection(), name, error); + if (oldsel.empty()) + return FALSE; + doc_interface->target.getSelection()->move(x, 0 - y); + selection_restore(doc_interface->target.getSelection(), oldsel); + return TRUE; +} + +gboolean +document_interface_move_to (DocumentInterface *doc_interface, gchar *name, gdouble x, + gdouble y, GError **error) +{ + std::vector<SPObject*> oldsel = selection_swap(doc_interface->target.getSelection(), name, error); + if (oldsel.empty()) + return FALSE; + Inkscape::Selection * sel = doc_interface->target.getSelection(); + doc_interface->target.getSelection()->move(x - selection_get_center_x(sel), + 0 - (y - selection_get_center_y(sel))); + selection_restore(doc_interface->target.getSelection(), oldsel); + return TRUE; +} + +gboolean +document_interface_object_to_path (DocumentInterface *doc_interface, + char *shape, GError **error) +{ + std::vector<SPObject*> oldsel = selection_swap(doc_interface->target.getSelection(), shape, error); + if (oldsel.empty()) + return FALSE; + dbus_call_verb (doc_interface, SP_VERB_OBJECT_TO_CURVE, error); + selection_restore(doc_interface->target.getSelection(), oldsel); + return TRUE; +} + +gchar * +document_interface_get_path (DocumentInterface *doc_interface, char *pathname, GError **error) +{ + Inkscape::XML::Node *node = get_repr_by_name(doc_interface->target.getDocument(), pathname, error); + + if (!node) + return NULL; + + if (node->attribute("d") == NULL) + { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OBJECT, "Object is not a path."); + return NULL; + } + return strdup(node->attribute("d")); +} + +gboolean +document_interface_transform (DocumentInterface *doc_interface, gchar *shape, + gchar *transformstr, GError **error) +{ + //FIXME: This should merge transformations. + gchar trans[] = "transform"; + document_interface_set_attribute (doc_interface, shape, trans, transformstr, error); + return TRUE; +} + +gchar * +document_interface_get_css (DocumentInterface *doc_interface, gchar *shape, + GError **error) +{ + gchar style[] = "style"; + return document_interface_get_attribute (doc_interface, shape, style, error); +} + +gboolean +document_interface_modify_css (DocumentInterface *doc_interface, gchar *shape, + gchar *cssattrb, gchar *newval, GError **error) +{ + // Doesn't like non-variable strings for some reason. + gchar style[] = "style"; + Inkscape::XML::Node *node = get_repr_by_name(doc_interface->target.getDocument(), shape, error); + + if (!dbus_check_string (cssattrb, error, "Attribute string empty.")) + return FALSE; + if (!node) + return FALSE; + + SPCSSAttr * oldstyle = sp_repr_css_attr (node, style); + sp_repr_css_set_property(oldstyle, cssattrb, newval); + Glib::ustring str; + sp_repr_css_write_string (oldstyle, str); + node->setAttributeOrRemoveIfEmpty (style, str); + return TRUE; +} + +gboolean +document_interface_merge_css (DocumentInterface *doc_interface, gchar *shape, + gchar *stylestring, GError **error) +{ + gchar style[] = "style"; + + Inkscape::XML::Node *node = get_repr_by_name(doc_interface->target.getDocument(), shape, error); + + if (!dbus_check_string (stylestring, error, "Style string empty.")) + return FALSE; + if (!node) + return FALSE; + + SPCSSAttr * newstyle = sp_repr_css_attr_new(); + sp_repr_css_attr_add_from_string (newstyle, stylestring); + + SPCSSAttr * oldstyle = sp_repr_css_attr (node, style); + + sp_repr_css_merge(oldstyle, newstyle); + Glib::ustring str; + sp_repr_css_write_string (oldstyle, str); + node->setAttributeOrRemoveIfEmpty (style, str); + + return TRUE; +} + +gboolean +document_interface_set_color (DocumentInterface *doc_interface, gchar *shape, + int r, int g, int b, gboolean fill, GError **error) +{ + gchar style[15]; + if (r<0 || r>255 || g<0 || g>255 || b<0 || b>255) + { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OTHER, "Given (%d,%d,%d). All values must be between 0-255 inclusive.", r, g, b); + return FALSE; + } + + if (fill) + snprintf(style, 15, "fill:#%.2x%.2x%.2x", r, g, b); + else + snprintf(style, 15, "stroke:#%.2x%.2x%.2x", r, g, b); + + if (strcmp(shape, "document") == 0) + return document_interface_document_merge_css (doc_interface, style, error); + + return document_interface_merge_css (doc_interface, shape, style, error); +} + +gboolean +document_interface_move_to_layer (DocumentInterface *doc_interface, gchar *shape, + gchar *layerstr, GError **error) +{ + std::vector<SPObject*> oldsel = selection_swap(doc_interface->target.getSelection(), shape, error); + if (oldsel.empty()) + return FALSE; + + document_interface_selection_move_to_layer(doc_interface, layerstr, error); + selection_restore(doc_interface->target.getSelection(), oldsel); + return TRUE; +} + +GArray *document_interface_get_node_coordinates(DocumentInterface * /*doc_interface*/, gchar * /*shape*/) +{ + //FIXME: Needs lot's of work. +/* + Inkscape::XML::Node *shapenode = get_repr_by_name (doc_interface->target.getDocument(), shape, error); + if (shapenode == NULL || shapenode->attribute("d") == NULL) { + return FALSE; + } + char * path = strdup(shapenode->attribute("d")); + printf("PATH: %s\n", path); + + Geom::parse_svg_path (path); + return NULL; + */ + return NULL; +} + + +gboolean +document_interface_set_text (DocumentInterface *doc_interface, gchar *name, gchar *text, GError **error) +{ + + SPItem* text_obj=(SPItem* )get_object_by_name(doc_interface->target.getDocument(), name, error); + //TODO verify object type + if (!text_obj) + return FALSE; + sp_te_set_repr_text_multiline(text_obj, text); + return TRUE; + +} + + + +gboolean +document_interface_text_apply_style (DocumentInterface *doc_interface, gchar *name, + int start_pos, int end_pos, gchar *style, gchar *styleval, + GError **error) +{ + + SPItem* text_obj=(SPItem* )get_object_by_name(doc_interface->target.getDocument(), name, error); + + //void sp_te_apply_style(SPItem *text, Inkscape::Text::Layout::iterator const &start, Inkscape::Text::Layout::iterator const &end, SPCSSAttr const *css) + //TODO verify object type + if (!text_obj) + return FALSE; + Inkscape::Text::Layout const *layout = te_get_layout(text_obj); + Inkscape::Text::Layout::iterator start = layout->charIndexToIterator (start_pos); + Inkscape::Text::Layout::iterator end = layout->charIndexToIterator (end_pos); + + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, style, styleval); + + sp_te_apply_style(text_obj, + start, + end, + css); + return TRUE; + +} + + +/**************************************************************************** + FILE I/O FUNCTIONS +****************************************************************************/ + +gboolean +document_interface_save (DocumentInterface *doc_interface, GError **error) +{ + SPDocument * doc = doc_interface->target.getDocument(); + printf("1: %s\n2: %s\n3: %s\n", doc->getDocumentURI(), doc->getDocumentBase(), doc->getDocumentName()); + if (doc->getDocumentURI()) + return document_interface_save_as (doc_interface, doc->getDocumentURI(), error); + return FALSE; +} + +gboolean document_interface_load(DocumentInterface *doc_interface, + gchar *filename, GError ** /*error*/) +{ + if (!filename) { + return false; + } + + SPDesktop *desk = doc_interface->target.getDesktop(); + if (desk) { + desktop_ensure_active(desk); + } + + Glib::RefPtr<Gio::File> file = Gio::File::create_for_path(filename); + + ConcreteInkscapeApplication<Gtk::Application>* app = &(ConcreteInkscapeApplication<Gtk::Application>::get_instance()); + + app->create_window(file); + + if (doc_interface->updates) { + Inkscape::DocumentUndo::done(doc_interface->target.getDocument(), SP_VERB_FILE_OPEN, "Opened File"); + } + return TRUE; +} + +gchar * +document_interface_import (DocumentInterface *doc_interface, + gchar *filename, GError **error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + if (desk) { + desktop_ensure_active(desk); + } + const Glib::ustring file(filename); + SPDocument * doc = doc_interface->target.getDocument(); + + SPObject *new_obj = NULL; + new_obj = file_import(doc, file, NULL); + return strdup(new_obj->getRepr()->attribute("id")); +} + +gboolean +document_interface_save_as (DocumentInterface *doc_interface, + const gchar *filename, GError **error) +{ + // FIXME: Isn't there a verb we can use for this instead? + SPDocument * doc = doc_interface->target.getDocument(); + + if (!doc || strlen(filename)<1) { //Safety check + return false; + } + + try { + Inkscape::Extension::save(NULL, doc, filename, + false, false, true, Inkscape::Extension::FILE_SAVE_METHOD_SAVE_AS); + } catch (...) { + // FIXME: catch ... is not usually a great idea, why is it needed here? + return false; + } + + return true; +} + +gboolean document_interface_mark_as_unmodified(DocumentInterface *doc_interface, GError ** /*error*/) +{ + SPDocument * doc = doc_interface->target.getDocument(); + if (doc) { + doc->setModifiedSinceSave(FALSE); + } + return TRUE; +} + +/* +gboolean +document_interface_print_to_file (DocumentInterface *doc_interface, GError **error) +{ + SPDocument * doc = doc_interface->target.getDocument(); + sp_print_document_to_file (doc, g_strdup("/home/soren/test.pdf")); + + return TRUE; +} +*/ +/**************************************************************************** + PROGRAM CONTROL FUNCTIONS +****************************************************************************/ + +gboolean +document_interface_close (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_FILE_CLOSE_VIEW, error); +} + +gboolean +document_interface_exit (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_FILE_QUIT, error); +} + +gboolean +document_interface_undo (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_EDIT_UNDO, error); +} + +gboolean +document_interface_redo (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_EDIT_REDO, error); +} + + + +/**************************************************************************** + UPDATE FUNCTIONS + FIXME: This would work better by adding a flag to SPDesktop to prevent + updating but that would be very intrusive so for now there is a workaround. + Need to make sure it plays well with verbs because they are used so much. +****************************************************************************/ + +void document_interface_pause_updates(DocumentInterface *doc_interface, GError ** error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_if_fail(ensure_desktop_valid(desk, error)); + doc_interface->updates = FALSE; + desk->canvas->_drawing_disabled = 1; +} + +void document_interface_resume_updates(DocumentInterface *doc_interface, GError ** error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_if_fail(ensure_desktop_valid(desk, error)); + doc_interface->updates = TRUE; + desk->canvas->_drawing_disabled = 0; + //FIXME: use better verb than rect. + Inkscape::DocumentUndo::done(doc_interface->target.getDocument(), SP_VERB_CONTEXT_RECT, "Multiple actions"); +} + +void document_interface_update(DocumentInterface *doc_interface, GError ** error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_if_fail(ensure_desktop_valid(desk, error)); + SPDocument *doc = doc_interface->target.getDocument(); + doc->getRoot()->uflags = TRUE; + doc->getRoot()->mflags = TRUE; + desk->enableInteraction(); + doc->ensureUpToDate(); + desk->disableInteraction(); + doc->getRoot()->uflags = FALSE; + doc->getRoot()->mflags = FALSE; + //Inkscape::DocumentUndo::done(doc, SP_VERB_CONTEXT_RECT, "Multiple actions"); +} + +/**************************************************************************** + SELECTION FUNCTIONS FIXME: use call_verb where appropriate (once update system is tested.) +****************************************************************************/ + +gboolean document_interface_selection_get(DocumentInterface *doc_interface, char ***out, GError ** /*error*/) +{ + Inkscape::Selection * sel = doc_interface->target.getSelection(); + auto oldsel = sel->objects(); + + int size = oldsel.size(); + + *out = g_new0 (char *, size + 1); + + int i = 0; + for (auto iter = oldsel.begin(); iter != oldsel.end(); ++iter) { + (*out)[i] = g_strdup((*iter)->getId()); + i++; + } + (*out)[i] = NULL; + + return TRUE; +} + +gboolean +document_interface_selection_add (DocumentInterface *doc_interface, char *name, GError **error) +{ + SPObject * obj = get_object_by_name(doc_interface->target.getDocument(), name, error); + if (!obj) + return FALSE; + + Inkscape::Selection *selection = doc_interface->target.getSelection(); + + selection->add(obj); + return TRUE; +} + +gboolean +document_interface_selection_add_list (DocumentInterface *doc_interface, + char **names, GError **error) +{ + int i; + for (i=0;names[i] != NULL;i++) { + document_interface_selection_add(doc_interface, names[i], error); + } + return TRUE; +} + +gboolean document_interface_selection_set(DocumentInterface *doc_interface, char *name, GError ** /*error*/) +{ + SPDocument * doc = doc_interface->target.getDocument(); + Inkscape::Selection *selection = doc_interface->target.getSelection(); + selection->set(doc->getObjectById(name)); + return TRUE; +} + +gboolean +document_interface_selection_set_list (DocumentInterface *doc_interface, + gchar **names, GError **error) +{ + doc_interface->target.getSelection()->clear(); + int i; + for (i=0;names[i] != NULL;i++) { + document_interface_selection_add(doc_interface, names[i], error); + } + return TRUE; +} + +gboolean document_interface_selection_rotate(DocumentInterface *doc_interface, int angle, GError ** /*error*/) +{ + Inkscape::Selection *selection = doc_interface->target.getSelection(); + selection->rotate(angle); + return TRUE; +} + +gboolean +document_interface_selection_delete (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_EDIT_DELETE, error); +} + +gboolean document_interface_selection_clear(DocumentInterface *doc_interface, GError ** /*error*/) +{ + doc_interface->target.getSelection()->clear(); + return TRUE; +} + +gboolean +document_interface_select_all (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_EDIT_SELECT_ALL, error); +} + +gboolean +document_interface_select_all_in_all_layers(DocumentInterface *doc_interface, + GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_EDIT_SELECT_ALL_IN_ALL_LAYERS, error); +} + +gboolean document_interface_selection_box(DocumentInterface * /*doc_interface*/, int /*x*/, int /*y*/, + int /*x2*/, int /*y2*/, gboolean /*replace*/, + GError ** /*error*/) +{ + //FIXME: implement. + return FALSE; +} + +gboolean +document_interface_selection_invert (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_EDIT_INVERT, error); +} + +gboolean +document_interface_selection_group (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_SELECTION_GROUP, error); +} +gboolean +document_interface_selection_ungroup (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_SELECTION_UNGROUP, error); +} + +gboolean +document_interface_selection_cut (DocumentInterface *doc_interface, GError **error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(desk, error), FALSE); + return dbus_call_verb (doc_interface, SP_VERB_EDIT_CUT, error); +} + +gboolean +document_interface_selection_copy (DocumentInterface *doc_interface, GError **error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(desk, error), FALSE); + return dbus_call_verb (doc_interface, SP_VERB_EDIT_COPY, error); +} + +gboolean +document_interface_selection_paste (DocumentInterface *doc_interface, GError **error) +{ + SPDesktop *desk = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(desk, error), FALSE); + return dbus_call_verb (doc_interface, SP_VERB_EDIT_PASTE, error); +} + +gboolean document_interface_selection_scale(DocumentInterface *doc_interface, gdouble grow, GError ** /*error*/) +{ + Inkscape::Selection *selection = doc_interface->target.getSelection(); + if (!selection) + { + return FALSE; + } + selection->scale(grow); + return TRUE; +} + +gboolean document_interface_selection_move(DocumentInterface *doc_interface, gdouble x, gdouble y, GError ** /*error*/) +{ + doc_interface->target.getSelection()->move(x, 0 - y); //switching coordinate systems. + return TRUE; +} + +gboolean document_interface_selection_move_to(DocumentInterface *doc_interface, gdouble x, gdouble y, GError ** /*error*/) +{ + Inkscape::Selection * sel = doc_interface->target.getSelection(); + + Geom::OptRect sel_bbox = sel->visualBounds(); + if (sel_bbox) { + Geom::Point m( x - selection_get_center_x(sel) , 0 - (y - selection_get_center_y(sel)) ); + sel->moveRelative(m, true); + } + return TRUE; +} + +//FIXME: does not paste in new layer. +// This needs to use lower level cut_impl and paste_impl (messy) +// See the built-in sp_selection_to_next_layer and duplicate. +gboolean +document_interface_selection_move_to_layer (DocumentInterface *doc_interface, + gchar *layerstr, GError **error) +{ + SPDesktop *dt = doc_interface->target.getDesktop(); + g_return_val_if_fail(ensure_desktop_valid(dt, error), FALSE); + + Inkscape::Selection *selection = doc_interface->target.getSelection(); + + // check if something is selected + if (selection->isEmpty()) + return FALSE; + + SPObject *next = get_object_by_name(doc_interface->target.getDocument(), layerstr, error); + + if (!next) + return FALSE; + + if (strcmp("layer", (next->getRepr())->attribute("inkscape:groupmode")) == 0) { + + dt->selection->cut(); + + doc_interface->target.getSelection()->layers()->setCurrentLayer(next); + + sp_selection_paste(dt, TRUE); + } + return TRUE; +} + +GArray * +document_interface_selection_get_center (DocumentInterface *doc_interface) +{ + Inkscape::Selection * sel = doc_interface->target.getSelection(); + + if (sel) + { + gdouble x = selection_get_center_x(sel); + gdouble y = selection_get_center_y(sel); + GArray * intArr = g_array_new (TRUE, TRUE, sizeof(double)); + + g_array_append_val (intArr, x); + g_array_append_val (intArr, y); + return intArr; + } + + return NULL; +} + +gboolean +document_interface_selection_to_path (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_OBJECT_TO_CURVE, error); +} + + +gboolean +document_interface_selection_combine (DocumentInterface *doc_interface, gchar *cmd, char ***newpaths, + GError **error) +{ + if (strcmp(cmd, "union") == 0) + dbus_call_verb (doc_interface, SP_VERB_SELECTION_UNION, error); + else if (strcmp(cmd, "intersection") == 0) + dbus_call_verb (doc_interface, SP_VERB_SELECTION_INTERSECT, error); + else if (strcmp(cmd, "difference") == 0) + dbus_call_verb (doc_interface, SP_VERB_SELECTION_DIFF, error); + else if (strcmp(cmd, "exclusion") == 0) + dbus_call_verb (doc_interface, SP_VERB_SELECTION_SYMDIFF, error); + else if (strcmp(cmd, "division") == 0) + dbus_call_verb (doc_interface, SP_VERB_SELECTION_CUT, error); + else { + g_set_error(error, INKSCAPE_ERROR, INKSCAPE_ERROR_OTHER, "Operation command not recognised"); + return FALSE; + } + + return document_interface_selection_get (doc_interface, newpaths, error); +} + +gboolean +document_interface_selection_change_level (DocumentInterface *doc_interface, gchar *cmd, + GError **error) +{ + if (strcmp(cmd, "raise") == 0) + return dbus_call_verb (doc_interface, SP_VERB_SELECTION_RAISE, error); + if (strcmp(cmd, "lower") == 0) + return dbus_call_verb (doc_interface, SP_VERB_SELECTION_LOWER, error); + if ((strcmp(cmd, "to_top") == 0) || (strcmp(cmd, "to_front") == 0)) + return dbus_call_verb (doc_interface, SP_VERB_SELECTION_TO_FRONT, error); + if ((strcmp(cmd, "to_bottom") == 0) || (strcmp(cmd, "to_back") == 0)) + return dbus_call_verb (doc_interface, SP_VERB_SELECTION_TO_BACK, error); + return TRUE; +} + +/**************************************************************************** + LAYER FUNCTIONS +****************************************************************************/ + +gchar *document_interface_layer_new(DocumentInterface *doc_interface, GError ** /*error*/) +{ + Inkscape::LayerModel * layers = doc_interface->target.getSelection()->layers(); + SPObject *new_layer = Inkscape::create_layer(layers->currentRoot(), layers->currentLayer(), Inkscape::LPOS_BELOW); + layers->setCurrentLayer(new_layer); + return g_strdup(get_name_from_object(new_layer)); +} + +gboolean +document_interface_layer_set (DocumentInterface *doc_interface, + gchar *layerstr, GError **error) +{ + SPObject * obj = get_object_by_name (doc_interface->target.getDocument(), layerstr, error); + + if (!obj) + return FALSE; + + doc_interface->target.getSelection()->layers()->setCurrentLayer (obj); + return TRUE; +} + +gchar **document_interface_layer_get_all(DocumentInterface * /*doc_interface*/) +{ + //FIXME: implement. + return NULL; +} + +gboolean +document_interface_layer_change_level (DocumentInterface *doc_interface, + gchar *cmd, GError **error) +{ + if (strcmp(cmd, "raise") == 0) + return dbus_call_verb (doc_interface, SP_VERB_LAYER_RAISE, error); + if (strcmp(cmd, "lower") == 0) + return dbus_call_verb (doc_interface, SP_VERB_LAYER_LOWER, error); + if ((strcmp(cmd, "to_top") == 0) || (strcmp(cmd, "to_front") == 0)) + return dbus_call_verb (doc_interface, SP_VERB_LAYER_TO_TOP, error); + if ((strcmp(cmd, "to_bottom") == 0) || (strcmp(cmd, "to_back") == 0)) + return dbus_call_verb (doc_interface, SP_VERB_LAYER_TO_BOTTOM, error); + return TRUE; +} + +gboolean +document_interface_layer_next (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_LAYER_NEXT, error); +} + +gboolean +document_interface_layer_previous (DocumentInterface *doc_interface, GError **error) +{ + return dbus_call_verb (doc_interface, SP_VERB_LAYER_PREV, error); +} + + +//////////////signals + + +DocumentInterface *fugly; +gboolean dbus_send_ping (SPDesktop* desk, SPItem *item) +{ + if (!item) return TRUE; + g_signal_emit (desk->dbus_document_interface, signals[OBJECT_MOVED_SIGNAL], 0, item->getId()); + return TRUE; +} + +//////////tree + + +gboolean +document_interface_get_children (DocumentInterface *doc_interface, char *name, char ***out, GError **error) +{ + SPItem* parent=(SPItem* )get_object_by_name(doc_interface->target.getDocument(), name, error); + std::vector<SPObject*> children = parent->childList(false); + + int size = children.size(); + + *out = g_new0 (char *, size + 1); + + int i = 0; + for (std::vector<SPObject*>::iterator iter = children.begin(), e = children.end(); iter != e; ++iter) { + (*out)[i] = g_strdup((*iter)->getId()); + i++; + } + (*out)[i] = NULL; + + return TRUE; +} + + +gchar* +document_interface_get_parent (DocumentInterface *doc_interface, char *name, GError **error) +{ + SPItem*node=(SPItem* )get_object_by_name(doc_interface->target.getDocument(), name, error); + + SPObject* parent=node->parent; + + return g_strdup(parent->getRepr()->attribute("id")); + +} + +#if 0 +//just pseudo code +gboolean +document_interface_get_xpath (DocumentInterface *doc_interface, char *xpath_expression, char ***out, GError **error){ + SPDocument * doc = doc_interface->target.getDocument(); + Inkscape::XML::Document *repr = doc->getReprDoc(); + + xmlXPathObjectPtr xpathObj; + xmlXPathContextPtr xpathCtx; + xpathCtx = xmlXPathNewContext(repr);//XmlDocPtr + xpathObj = xmlXPathEvalExpression(xmlCharStrdup(xpath_expression), xpathCtx); + + //xpathresult result = xpatheval(repr, xpath_selection); + //convert result to a string array we can return via dbus + return TRUE; +} +#endif +/* + 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/extension/dbus/document-interface.h b/src/extension/dbus/document-interface.h new file mode 100644 index 0000000..ed31513 --- /dev/null +++ b/src/extension/dbus/document-interface.h @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is where the implementation of the DBus based document API lives. + * All the methods in here (except in the helper section) are + * designed to be called remotly via DBus. application-interface.cpp + * has the methods used to connect to the bus and get a document instance. + * + * Documentation for these methods is in document-interface.xml + * which is the "gold standard" as to how the interface should work. + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_DOCUMENT_INTERFACE_H_ +#define INKSCAPE_EXTENSION_DOCUMENT_INTERFACE_H_ + +#include <glib.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-bindings.h> +#include <dbus/dbus-glib-lowlevel.h> + +// this is required so that giomm headers won't barf +#undef DBUS_MESSAGE_TYPE_INVALID +#undef DBUS_MESSAGE_TYPE_METHOD_CALL +#undef DBUS_MESSAGE_TYPE_METHOD_RETURN +#undef DBUS_MESSAGE_TYPE_ERROR +#undef DBUS_MESSAGE_TYPE_SIGNAL + +#include "helper/action-context.h" + +class SPDesktop; +class SPItem; + +#define TYPE_DOCUMENT_INTERFACE (document_interface_get_type ()) +#define DOCUMENT_INTERFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_DOCUMENT_INTERFACE, DocumentInterface)) +#define DOCUMENT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_DOCUMENT_INTERFACE, DocumentInterfaceClass)) +#define IS_DOCUMENT_INTERFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_DOCUMENT_INTERFACE)) +#define IS_DOCUMENT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_DOCUMENT_INTERFACE)) +#define DOCUMENT_INTERFACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_DOCUMENT_INTERFACE, DocumentInterfaceClass)) + +G_BEGIN_DECLS + +typedef struct _DocumentInterface DocumentInterface; +typedef struct _DocumentInterfaceClass DocumentInterfaceClass; + +struct _DocumentInterface { + GObject parent; + Inkscape::ActionContext target; ///< stores information about which document, selection, desktop etc this interface is linked to + gboolean updates; +}; + +struct _DocumentInterfaceClass { + GObjectClass parent; +}; + + + +struct DBUSPoint { + int x; + int y; +}; +/**************************************************************************** + MISC FUNCTIONS +****************************************************************************/ + +gboolean +document_interface_delete_all (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_call_verb (DocumentInterface *doc_interface, + gchar *verbid, GError **error); + +/**************************************************************************** + CREATION FUNCTIONS +****************************************************************************/ + +gchar* +document_interface_rectangle (DocumentInterface *doc_interface, int x, int y, + int width, int height, GError **error); + +gchar* +document_interface_ellipse (DocumentInterface *doc_interface, int x, int y, + int width, int height, GError **error); + +gchar* +document_interface_polygon (DocumentInterface *doc_interface, int cx, int cy, + int radius, int rotation, int sides, + GError **error); + +gchar* +document_interface_star (DocumentInterface *doc_interface, int cx, int cy, + int r1, int r2, int sides, gdouble rounded, + gdouble arg1, gdouble arg2, GError **error); + +gchar* +document_interface_spiral (DocumentInterface *doc_interface, int cx, int cy, + int r, int revolutions, GError **error); + +gchar* +document_interface_line (DocumentInterface *doc_interface, int x, int y, + int x2, int y2, GError **error); + +gchar* +document_interface_text (DocumentInterface *doc_interface, int x, int y, + gchar *text, GError **error); +gboolean +document_interface_set_text (DocumentInterface *doc_interface, gchar *name, + gchar *text, GError **error); +gboolean +document_interface_text_apply_style (DocumentInterface *doc_interface, gchar *name, + int start_pos, int end_pos, gchar *style, gchar *styleval, + GError **error); + +gchar * +document_interface_image (DocumentInterface *doc_interface, int x, int y, + gchar *filename, GError **error); + +gchar* +document_interface_node (DocumentInterface *doc_interface, gchar *svgtype, + GError **error); + + +/**************************************************************************** + ENVIRONMENT FUNCTIONS +****************************************************************************/ +gdouble +document_interface_document_get_width (DocumentInterface *doc_interface); + +gdouble +document_interface_document_get_height (DocumentInterface *doc_interface); + +gchar * +document_interface_document_get_css (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_document_merge_css (DocumentInterface *doc_interface, + gchar *stylestring, GError **error); + +gboolean +document_interface_document_set_css (DocumentInterface *doc_interface, + gchar *stylestring, GError **error); + +gboolean +document_interface_document_resize_to_fit_selection (DocumentInterface *doc_interface, + GError **error); +gboolean +document_interface_document_set_display_area (DocumentInterface *doc_interface, + double x0, + double y0, + double x1, + double y1, + double border, + GError **error); +GArray * +document_interface_document_get_display_area (DocumentInterface *doc_interface); + +/**************************************************************************** + OBJECT FUNCTIONS +****************************************************************************/ + +gboolean +document_interface_set_attribute (DocumentInterface *doc_interface, + char *shape, char *attribute, + char *newval, GError **error); + +gboolean +document_interface_set_int_attribute (DocumentInterface *doc_interface, + char *shape, char *attribute, + int newval, GError **error); + +gboolean +document_interface_set_double_attribute (DocumentInterface *doc_interface, + char *shape, char *attribute, + double newval, GError **error); + +gchar * +document_interface_get_attribute (DocumentInterface *doc_interface, + char *shape, char *attribute, GError **error); + +gboolean +document_interface_move (DocumentInterface *doc_interface, gchar *name, + gdouble x, gdouble y, GError **error); + +gboolean +document_interface_move_to (DocumentInterface *doc_interface, gchar *name, + gdouble x, gdouble y, GError **error); + +gboolean +document_interface_object_to_path (DocumentInterface *doc_interface, + char *shape, GError **error); + +gchar * +document_interface_get_path (DocumentInterface *doc_interface, + char *pathname, GError **error); + +gboolean +document_interface_transform (DocumentInterface *doc_interface, gchar *shape, + gchar *transformstr, GError **error); + +gchar * +document_interface_get_css (DocumentInterface *doc_interface, gchar *shape, + GError **error); + +gboolean +document_interface_modify_css (DocumentInterface *doc_interface, gchar *shape, + gchar *cssattrb, gchar *newval, GError **error); + +gboolean +document_interface_merge_css (DocumentInterface *doc_interface, gchar *shape, + gchar *stylestring, GError **error); + +gboolean +document_interface_set_color (DocumentInterface *doc_interface, gchar *shape, + int r, int g, int b, gboolean fill, GError **error); + +gboolean +document_interface_move_to_layer (DocumentInterface *doc_interface, gchar *shape, + gchar *layerstr, GError **error); + + +GArray * +document_interface_get_node_coordinates (DocumentInterface *doc_interface, gchar *shape); + +/**************************************************************************** + FILE I/O FUNCTIONS +****************************************************************************/ + +gboolean +document_interface_save (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_load (DocumentInterface *doc_interface, + gchar *filename, GError **error); + +gboolean +document_interface_save_as (DocumentInterface *doc_interface, + const gchar *filename, GError **error); + +gboolean +document_interface_mark_as_unmodified (DocumentInterface *doc_interface, GError **error); +/* +gboolean +document_interface_print_to_file (DocumentInterface *doc_interface, GError **error); +*/ + +/**************************************************************************** + PROGRAM CONTROL FUNCTIONS +****************************************************************************/ + +gboolean +document_interface_close (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_exit (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_undo (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_redo (DocumentInterface *doc_interface, GError **error); + + +/**************************************************************************** + UPDATE FUNCTIONS +****************************************************************************/ +void +document_interface_pause_updates (DocumentInterface *doc_interface, GError **error); + +void +document_interface_resume_updates (DocumentInterface *doc_interface, GError **error); + +void +document_interface_update (DocumentInterface *doc_interface, GError **error); + +/**************************************************************************** + SELECTION FUNCTIONS +****************************************************************************/ +gboolean +document_interface_selection_get (DocumentInterface *doc_interface, char ***out, GError **error); + +gboolean +document_interface_selection_add (DocumentInterface *doc_interface, + char *name, GError **error); + +gboolean +document_interface_selection_add_list (DocumentInterface *doc_interface, + char **names, GError **error); + +gboolean +document_interface_selection_set (DocumentInterface *doc_interface, + char *name, GError **error); + +gboolean +document_interface_selection_set_list (DocumentInterface *doc_interface, + gchar **names, GError **error); + +gboolean +document_interface_selection_rotate (DocumentInterface *doc_interface, + int angle, GError **error); + +gboolean +document_interface_selection_delete(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_clear(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_select_all(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_select_all_in_all_layers(DocumentInterface *doc_interface, + GError **error); + +gboolean +document_interface_selection_box (DocumentInterface *doc_interface, int x, int y, + int x2, int y2, gboolean replace, + GError **error); + +gboolean +document_interface_selection_invert (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_group(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_ungroup(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_cut(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_copy(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_paste(DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_scale (DocumentInterface *doc_interface, + gdouble grow, GError **error); + +gboolean +document_interface_selection_move (DocumentInterface *doc_interface, gdouble x, + gdouble y, GError **error); + +gboolean +document_interface_selection_move_to (DocumentInterface *doc_interface, gdouble x, + gdouble y, GError **error); + +gboolean +document_interface_selection_move_to_layer (DocumentInterface *doc_interface, + gchar *layerstr, GError **error); + +GArray * +document_interface_selection_get_center (DocumentInterface *doc_interface); + +gboolean +document_interface_selection_to_path (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_selection_combine (DocumentInterface *doc_interface, gchar *cmd, char ***newpaths, + GError **error); + +gboolean +document_interface_selection_change_level (DocumentInterface *doc_interface, gchar *cmd, + GError **error); + +/**************************************************************************** + LAYER FUNCTIONS +****************************************************************************/ + +gchar * +document_interface_layer_new (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_layer_set (DocumentInterface *doc_interface, + gchar *layerstr, GError **error); + +gchar ** +document_interface_layer_get_all (DocumentInterface *doc_interface); + +gboolean +document_interface_layer_change_level (DocumentInterface *doc_interface, + gchar *cmd, GError **error); + +gboolean +document_interface_layer_next (DocumentInterface *doc_interface, GError **error); + +gboolean +document_interface_layer_previous (DocumentInterface *doc_interface, GError **error); + + + + + + + + +DocumentInterface *document_interface_new (void); +GType document_interface_get_type (void); + +extern DocumentInterface *fugly; +gboolean dbus_send_ping (SPDesktop* desk, SPItem *item); + +gboolean +document_interface_get_children (DocumentInterface *doc_interface, char *name, char ***out, GError **error); + +gchar* +document_interface_get_parent (DocumentInterface *doc_interface, char *name, GError **error); + +gchar* +document_interface_import (DocumentInterface *doc_interface, + gchar *filename, GError **error); + +G_END_DECLS + +#endif // INKSCAPE_EXTENSION_DOCUMENT_INTERFACE_H_ diff --git a/src/extension/dbus/document-interface.xml b/src/extension/dbus/document-interface.xml new file mode 100644 index 0000000..4524cba --- /dev/null +++ b/src/extension/dbus/document-interface.xml @@ -0,0 +1,1530 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- SPDX-License-Identifier: GPL-2.0-or-later --> +<!-- + * This is the master description of the DBus document interface. + * + * This file is used to generate both glue code and documentation. + * The methods are in the same order as the .cpp/.h and the sections are labeled. + * + * Any change to method prototypes in document-interface.cpp MUST be reflected here. + * + * This file is the proverbial gold standard for the document interface. + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. +--> + + +<node name="/org/inkscape/document" + xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" +> +c + <interface name="org.inkscape.document"> + + <!-- MISC FUNCTIONS --> + + <method name="delete_all" > + </method> + + <method name="call_verb"> + <arg type="s" name="verbid" direction="in"> + <doc:doc> + <doc:summary>The string id of a verb. For example: "EditSelectAll".</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method allows you to call any Inkscape verb using it's associated string. Every button and menu item has an associated verb, so this allows access to some extra functionality if one is willing to do the prerequisite research. The list of verbs can be found at:</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- CREATION FUNCTIONS --> + + <method name="rectangle"> + <arg type="i" name="x" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the top left corner of the rectangle.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the top left corner of the rectangle.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="width" direction="in" > + <doc:doc> + <doc:summary>Width of the rectangle.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="height" direction="in" > + <doc:doc> + <doc:summary>Height of the rectangle.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new rectangle.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method creates a rectangle in the current layer using the current document style.</doc:para> + <doc:para>It is recommended that you save the return value if you will want to modify this particular shape later.</doc:para> + <doc:para>Additional variables include:</doc:para> + <doc:para>cx and cy: set these anywhere from zero to half the width or height respectively of the rectangle to give it rounded corners.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="ellipse"> + <arg type="i" name="x" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the top left corner of the ellipse.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the top left corner of the ellipse.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="width" direction="in" > + <doc:doc> + <doc:summary>Width of the ellipse.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="height" direction="in" > + <doc:doc> + <doc:summary>Height of the ellipse.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new ellipse.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method creates a ellipse in the current layer using the current document style.</doc:para> + <doc:para>It is recommended that you save the return value if you will want to modify this particular shape later.</doc:para> + <doc:para>Additional variables include:</doc:para> + <doc:para>"sodipodi:start" and "sodipodi:end": set these between 0 and Pi to create wedges or Pacman like shapes.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="polygon"> + <arg type="i" name="cx" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the center of the polygon.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="cy" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the center of the polygon.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="radius" direction="in" > + <doc:doc> + <doc:summary>Radius from the center to one of the points.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="rotation" direction="in" > + <doc:doc> + <doc:summary>Angle in degrees to rotate. 0 will have the first point pointing straight up.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="sides" direction="in" > + <doc:doc> + <doc:summary>Number of sides of the polygon.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new polygon.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method creates a polygon in the current layer using the current document style.</doc:para> + <doc:para>It is recommended that you save the return value if you will want to modify this particular shape later.</doc:para> + <doc:para>Note: this is actually a <doc:ref type="method" to="document.star">star</doc:ref> with "sodipodi:flatsided" set to true, which causes it to ignore the arg2 and r2 values.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="star"> + <arg type="i" name="cx" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the center of the star.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="cy" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the center of the star.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="r1" direction="in" > + <doc:doc> + <doc:summary>distance from the center for the first point.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="r2" direction="in" > + <doc:doc> + <doc:summary>distance from the center for the second point.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="arg1" direction="in" > + <doc:doc> + <doc:summary>Angle in radians for the first point. 0 is 90 degrees to the right of straight up.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="arg2" direction="in" > + <doc:doc> + <doc:summary>Angle in radians for the second point. 0 is 90 degrees to the right of straight up.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="sides" direction="in" > + <doc:doc> + <doc:summary>Number of times to repeat the points around the star.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="rounded" direction="in" > + <doc:doc> + <doc:summary>How rounded to make the star. 0 to 1 recommended for moderate to medium curves. 10 for extreme curves.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new star.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method creates a star in the current layer using the current document style.</doc:para> + <doc:para>It is recommended that you save the return value if you will want to modify this particular shape later.</doc:para> + <doc:para>Stars are quite complicated. Here is how they are represented: There are two points, represented by sodipodi:arg1 and sodipodi:arg2 for angle in radians and sodipodi:r1 and sodipodi:r2 for respective radius from the center point. The further one is a point of the star, the shorter one one of the valleys. This point and valley are repeated according to sodipodi:sides. sodipodi:rounded controls their control handles.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="spiral"> + <arg type="i" name="cx" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the center of the spiral.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="cy" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the center of the spiral.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="r" direction="in" > + <doc:doc> + <doc:summary>Radius of the spiral.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="revolutions" direction="in" > + <doc:doc> + <doc:summary>Number of revolutions.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new spiral.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method creates a spiral in the current layer using the current document style. However, fill is automatically set to "none". Stroke is unmodified.</doc:para> + <doc:para>It is recommended that you save the return value if you will want to modify this particular shape later.</doc:para> + <doc:para>Additional variables include:</doc:para> + <doc:para>"sodipodi:expansion": at 1 the spiral gets bigger at a constant rate. Less than one and the loops get tighter and tighter as it goes. More than one and they get looser and looser. This affects the number of revolutions so that it might not actually match the "sodipodi:revolutions" argument.</doc:para> + <doc:para>"sodipodi:t0": at 0 the entire spiral is drawn, at 0.5 it is only drawn %50 of the way (starting from the outside) etc.</doc:para> + <doc:para>"sodipodi:argument": Rotates the spiral. In radians.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="line"> + <arg type="i" name="x" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the first point.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the first point.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="x2" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the second point.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y2" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the second point.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new line.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method creates a line in the current layer using the current document style. It's a path, so the only attribute it will pay any attention to is "transform".</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="text"> + <arg type="i" name="x" direction="in" > + <doc:doc> + <doc:summary>The x coordinate to put the text at.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y" direction="in" > + <doc:doc> + <doc:summary>The y coordinate to put the text at.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="text" direction="in" > + <doc:doc> + <doc:summary>The text you want.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new text.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method creates some text in the current layer.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="image"> + <arg type="i" name="x" direction="in" > + <doc:doc> + <doc:summary>The x coordinate to put the image at.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y" direction="in" > + <doc:doc> + <doc:summary>The y coordinate to put the image at.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="text" direction="in" > + <doc:doc> + <doc:summary>The full path of the image you want.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new image.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method imports a non-vector image (such as a jpeg, png, etc.) and places it at the given coordinates. The resulting shape has no style or path but can be treated like a rectangle. With and height can be set explicitly (will deform image) or transform strings or <doc:ref type="method" to="document.selection_scale">selection_scale()</doc:ref> can scale it relatively.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="import"> + <arg type="s" name="pathname" direction="in" > + <doc:doc> + <doc:summary>The path to a valid svg file.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="object_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new image.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Imports the file at pathname. Similar to the image + method.</doc:para> + </doc:description> + </doc:doc> + </method> + + + <method name="node"> + <arg type="s" name="svgtype" direction="in" > + <doc:doc> + <doc:summary>The type of node, probably "svg:path"</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="node_name" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new node.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Make any kind of node you want. Mostly for making paths. (May need to allow updateRepr to be called for it to show up.)</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- ENVIRONMENT FUNCTIONS --> + + <method name="document_get_width"> + <arg type="d" name="val" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> + <doc:doc> + <doc:summary>Document width.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Retrieve the width of the current document. anything outside the boundary will not be printed or exported but will be saved.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="document_get_height"> + <arg type="d" name="val" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> + <doc:doc> + <doc:summary>Document height.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Retrieve the height of the current document. anything outside the boundary will not be printed or exported but will be saved.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="document_get_css"> + <arg type="s" name="css" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>CSS attribute string for the document.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Get the current style for the document. All new shapes will use this style if it exists.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Style Strings">Style Strings</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="document_set_css"> + <arg type="s" name="stylestring" direction="in" > + <doc:doc> + <doc:summary>A new CSS attribute string for the document.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Set the current style for the document. All new shapes will use this style if it exists.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Style Strings">Style Strings</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="document_merge_css"> + <arg type="s" name="stylestring" direction="in" > + <doc:doc> + <doc:summary>A new CSS attribute string for the document.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Merge this string with the current style for the document. All new shapes will use this style if it exists.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Style Strings">Style Strings</doc:ref>, <doc:ref type="method" to="document.merge_css">merge_css()</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="document_resize_to_fit_selection"> + <doc:doc> + <doc:description> + <doc:para>Resize the document to contain all of the currently selected objects.</doc:para> + <doc:para>This ensures that the image is not clipped when printing or exporting.</doc:para> + </doc:description> + </doc:doc> + </method> + + + + <method name="document_set_display_area"> + <arg type="d" name="x0" direction="in" > + <doc:doc> + <doc:summary></doc:summary> + </doc:doc> + </arg> + <arg type="d" name="y0" direction="in" > + <doc:doc> + <doc:summary></doc:summary> + </doc:doc> + </arg> + <arg type="d" name="x1" direction="in" > + <doc:doc> + <doc:summary></doc:summary> + </doc:doc> + </arg> + <arg type="d" name="y1" direction="in" > + <doc:doc> + <doc:summary></doc:summary> + </doc:doc> + </arg> + <arg type="d" name="border" direction="in" > + <doc:doc> + <doc:summary></doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Set display area.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="document_get_display_area"> + <doc:doc> + <doc:description> + <doc:para>Get display area.</doc:para> + </doc:description> + </doc:doc> + <arg type="ad" name="area" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> + <doc:doc> + <doc:summary>area</doc:summary> + </doc:doc> + </arg> + </method> + + + <!-- OBJECT FUNCTIONS --> + + <method name="set_attribute"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="attribute" direction="in" > + <doc:doc> + <doc:summary>The name of the attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="newval" direction="in" > + <doc:doc> + <doc:summary>The new value of the attribute. This will overwrite anything already set. To merge styles, see <doc:ref type="method" to="document.merge_css">merge_css()</doc:ref>. </doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Set any attribute, the available attributes depend on what kind of shape the object node represents. See <doc:ref type="method" to="document.rectangle">shape creation functions</doc:ref> for more details.</doc:para> + </doc:description> + </doc:doc> + </method> + + + <method name="set_text"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + + <arg type="s" name="text" direction="in" > + <doc:doc> + <doc:summary>The text you want.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>set text of text object.</doc:para> + </doc:description> + </doc:doc> + </method> + + + <method name="text_apply_style"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + + <arg type="i" name="start" direction="in" > + <doc:doc> + <doc:summary>start text pos.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="end" direction="in" > + <doc:doc> + <doc:summary>end text pos.</doc:summary> + </doc:doc> + </arg> + + <arg type="s" name="css_attrib" direction="in" > + <doc:doc> + <doc:summary>css attribute.</doc:summary> + </doc:doc> + </arg> + + <arg type="s" name="css_attrib_val" direction="in" > + <doc:doc> + <doc:summary>css attribute value.</doc:summary> + </doc:doc> + </arg> + + + <doc:doc> + <doc:description> + <doc:para>set styling of partial text object.</doc:para> + </doc:description> + </doc:doc> + </method> + + + <method name="set_int_attribute"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="attribute" direction="in" > + <doc:doc> + <doc:summary>The name of the attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="newval" direction="in" > + <doc:doc> + <doc:summary>The new value of the attribute. This will overwrite anything already set.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Set any attribute, the available attributes depend on what kind of shape the object node represents. See <doc:ref type="method" to="document.rectangle">shape creation functions</doc:ref> for more details.</doc:para> + <doc:para>This is a convenience function for <doc:ref type="method" to="document.set_attribute">set_attribute()</doc:ref>.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="set_double_attribute"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="attribute" direction="in" > + <doc:doc> + <doc:summary>The name of the attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="newval" direction="in" > + <doc:doc> + <doc:summary>The new value of the attribute. This will overwrite anything already set.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Set any attribute, the available attributes depend on what kind of shape the node represents. See <doc:ref type="method" to="document.rectangle">shape creation functions</doc:ref> for more details.</doc:para> + <doc:para>This is a convenience function for <doc:ref type="method" to="document.set_attribute">set_attribute()</doc:ref>.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="get_attribute"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="attribute" direction="in" > + <doc:doc> + <doc:summary>The name of the attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="val" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The current value of the attribute. String is a copy and must be freed.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Get the value of any attribute. Not all objects will have every attribute their type supports, some are optional. See <doc:ref type="method" to="document.rectangle">shape creation functions</doc:ref> for more details.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="move"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="x" direction="in" > + <doc:doc> + <doc:summary>Distance to move along the x axis.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="y" direction="in" > + <doc:doc> + <doc:summary>Distance to move along the y axis.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This will move a shape (or any object) relative to it's current location.</doc:para> + <doc:para>This may be accomplished with transformation attributes or by changing x and y attributes depending on the state of the object.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="move_to"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="x" direction="in" > + <doc:doc> + <doc:summary>the x coordinate of the desired location.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="y" direction="in" > + <doc:doc> + <doc:summary>the y coordinate of the desired location.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This will move a shape (or any object) to an absolute location. The point moved is the center of the bounding box, which is usually similar to the center of the shape.</doc:para> + <doc:para>Note that creating a rectangle or ellipse at 100,100 and calling move_to to move it to 100,100 will not produce the same results.</doc:para> + <doc:para>This may be accomplished with transformation attributes or by changing x and y attributes depending on the state of the object.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="object_to_path"> + <arg type="s" name="objectname" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Turns an object into a path. Most objects contain paths (except rectangles) but are not paths themselves. </doc:para> + <doc:para>This will remove every attribute except d (the path attribute) style and id. id will not change. The appearance will be the same as well, it essentially encodes all information about the shape into the path.</doc:para> + <doc:para>After doing this you will no longer be able to modify the shape using shape specific attributes (cx, radius etc.) except transform</doc:para> + <doc:para>Required for certain functions that work on paths (not yet present in this API.)</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Paths">Paths</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="get_path"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="val" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The path of the object. NULL if the object has no path.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Get the path value of an object. Equivalent to calling <doc:ref type="method" to="document.get_attribute">get_attribte()</doc:ref> with argument "d". Will not turn object into a path if it is not already.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Paths">Paths</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="transform"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>The id of any node or object.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="transformstr" direction="in" > + <doc:doc> + <doc:summary>A string that represents a transformation.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Takes a transformation string ("matrix(0.96629885,0.25742286,-0.25742286,0.96629885,0,0)" or "rotate(45)") and applies it to any shape or path.</doc:para> + <doc:para>Will merge with existing transformations.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="get_css"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>Any object with a style attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="css" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>A CSS Style string</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Retrieve the style of a object. Equivalent to calling <doc:ref type="method" to="document.get_attribute">get_attribute()</doc:ref> for "style".</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Style Strings">Style Strings</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="modify_css"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>Any object with a style attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="cssattrib" direction="in" > + <doc:doc> + <doc:summary>An attribute such as "fill" or "stroke-width".</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="newval" direction="in" > + <doc:doc> + <doc:summary>The new value.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Set a particular attribute of a style string. Overwrites just that part of the style.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Style Strings">Style Strings</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="merge_css"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>Any object with a style attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="stylestring" direction="in" > + <doc:doc> + <doc:summary>A full or partial CSS Style string.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Takes a CSS Style string and merges it with the objects current style, overwriting only the elements present in stylestring.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Style Strings">Style Strings</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="set_color"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>Any object, or 'document' to apply to document style.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="red" direction="in" > + <doc:doc> + <doc:summary>The red component.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="green" direction="in" > + <doc:doc> + <doc:summary>The green component.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="blue" direction="in" > + <doc:doc> + <doc:summary>The blue component.</doc:summary> + </doc:doc> + </arg> + <arg type="b" name="fill" direction="in" > + <doc:doc> + <doc:summary>True to change fill color, false for stroke color.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Modifies the fill or stroke color of an object (or the document style) based on RGB values.</doc:para> + <doc:para>Red green and blue must be between 0-255 inclusive.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="move_to_layer"> + <arg type="s" name="objectname" direction="in" > + <doc:doc> + <doc:summary>The id of an object.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="layername" direction="in" > + <doc:doc> + <doc:summary>A layer name.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Moves an object to a different layer.</doc:para> + <doc:para>Will error if layer does not exist.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="document.layer_new">layer_new()</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="get_node_coordinates"> + <arg type="s" name="shape" direction="in" > + <doc:doc> + <doc:summary>A object that contains a path ("d") attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="ai" name="points" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> + <doc:doc> + <doc:summary>An array of points.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Returns an array of all of the X,Y coordinates of the points in the objects path.</doc:para> + <doc:para>If the path is a closed loop the first point is repeated at the end.</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- FILE I/O FUNCTIONS --> + + <method name="save" > + <doc:doc> + <doc:description> + <doc:para>Saves the current document with current name or a default name if has not been saved before.</doc:para> + <doc:para>Will overwrite without confirmation.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="save_as"> + <arg type="s" name="pathname" direction="in" > + <doc:doc> + <doc:summary>The path for the file to be saved as.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Saves the current document as pathname.</doc:para> + <doc:para>Will overwrite without confirmation.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="load"> + <arg type="s" name="pathname" direction="in" > + <doc:doc> + <doc:summary>The path to a valid svg file.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Loads the file at pathname.</doc:para> + <doc:para>Will lose all unsaved work in current document.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="mark_as_unmodified" > + <doc:doc> + <doc:description> + <doc:para>Marks the document as unmodified/saved.</doc:para> + <doc:para>Will prevent save confirmation on close if called at end of script.</doc:para> + </doc:description> + </doc:doc> + </method> + + + + <!-- + <method name="print_to_file" > + <doc:doc> + <doc:description> + <doc:para>Prints the current document with default settings.</doc:para> + <doc:para>Will only print things visible within the document boundaries.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="document.document_resize_to_fit_selection">document_resize_to_fit_selection()</doc:ref></doc:seealso> + </doc:doc> + </method> + --> + + <!-- PROGRAM CONTROL FUNCTIONS --> + + <method name="close" > + <doc:doc> + <doc:description> + <doc:para>Close this document.</doc:para> + <doc:para>You will not be able to send any more commands on this interface.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="exit" > + <doc:doc> + <doc:description> + <doc:para>Exit Inkscape.</doc:para> + <doc:para>You will not be able to send any more commands on any interface.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="undo" > + <doc:doc> + <doc:description> + <doc:para>Undo the last action.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="redo" > + <doc:doc> + <doc:description> + <doc:para>Redo the last undone action.</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- UPDATE FUNCTIONS --> + + <method name="pause_updates" > + <doc:doc> + <doc:description> + <doc:para>When updates are paused Inkscape will not draw every change as it is made. Also you will not be able to undo individual actions made while updates were paused and will only be able to undo them in a group. Inkscape may refresh the screen every couple of seconds even with updates off.</doc:para> + <doc:para>The advantage is a 2-5x speed increase, depending on the type of functions being called. This is most useful when creating large numbers of shapes.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="resume_updates" > + <doc:doc> + <doc:description> + <doc:para>Resume updates after they have been paused. If undo is called at this point it will undo everything that happened since pause_updates() was called.</doc:para> + <doc:para>This will update the display to show any changes that happened while updates were paused, a separate call to update() is not necessary.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="update" > + <doc:doc> + <doc:description> + <doc:para>This will update the document once if updates are paused but it will not resume updates.</doc:para> + <doc:para>This could be used to check on the progress of a complex drawing function, or to add in undo steps at certain points in a render.</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- SELECTION FUNCTIONS --> + + <method name="selection_get"> + <arg type="as" name="listy" direction="out" > + <!-- <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> --> + <doc:doc> + <doc:summary>List of the ids of currently selected objects.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Returns the current selection in the form of a list of ids of selected objects.</doc:para> + <doc:para>Manipulating this list will not affect the selection.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_add"> + <arg type="s" name="name" direction="in" > + <doc:doc> + <doc:summary>A object to add to the selection.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Adds a single object to the selection.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_add_list"> + <arg type="as" name="name" direction="in" > + <doc:doc> + <doc:summary>An array of object ids to add to the selection.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Adds a list of objects to the selection.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_set"> + <arg type="s" name="name" direction="in" > + <doc:doc> + <doc:summary>A object to select.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Replaces the selection with one containing just this object.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_set_list"> + <arg type="as" name="name" direction="in" > + <doc:doc> + <doc:summary>A list of objects to select.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Replaces the selection with one containing just these objects.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_rotate"> + <arg type="i" name="angle" direction="in" > + <doc:doc> + <doc:summary>Angle in degrees to rotate.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Rotates the selection around the center of it's bounding box.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_delete" > + <doc:doc> + <doc:description> + <doc:para>Delete all objects in the selection.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_clear" > + <doc:doc> + <doc:description> + <doc:para>Deselect everything. Selection will be empty.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="select_all" > + <doc:doc> + <doc:description> + <doc:para>Select all objects in current layer.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="select_all_in_all_layers" > + <doc:doc> + <doc:description> + <doc:para>Select all objects in every layer.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_box"> + <arg type="i" name="x" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the first point.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the first point.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="x2" direction="in" > + <doc:doc> + <doc:summary>X coordinate for the second point.</doc:summary> + </doc:doc> + </arg> + <arg type="i" name="y2" direction="in" > + <doc:doc> + <doc:summary>Y coordinate for the second point.</doc:summary> + </doc:doc> + </arg> + <arg type="b" name="replace" direction="in" > + <doc:doc> + <doc:summary>True to replace selection, false to add to selection.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This method finds all of the objects inside the box and adds them to the current selection. If replace is true it will clear the old selection first.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_invert" > + <doc:doc> + <doc:description> + <doc:para>Invert the selection in the current layer.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_group" > + <doc:doc> + <doc:description> + <doc:para>Group the selection.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Groups">Groups</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="selection_ungroup" > + <doc:doc> + <doc:description> + <doc:para>Ungroup the selection.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Groups">Groups</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="selection_cut" > + <doc:doc> + <doc:description> + <doc:para>Cut the current selection.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_copy" > + <doc:doc> + <doc:description> + <doc:para>Copy the current selection.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_paste" > + <doc:doc> + <doc:description> + <doc:para>Paste the current selection at the same location it was cut from.</doc:para> + <doc:para>To paste to a particular location, simply use selection_paste() followed by <doc:ref type="method" to="document.selection_move_to">selection_move_to()</doc:ref>.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_scale"> + <arg type="d" name="grow" direction="in" > + <doc:doc> + <doc:summary>The amount to scale the selection, 1 has no effect. Between 0 and 1 will shrink it proportionally. Greater than one will grow it proportionally.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Scale the selection relative to it's current size.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_move"> + <arg type="d" name="x" direction="in" > + <doc:doc> + <doc:summary>Amount to move in the x direction.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="y" direction="in" > + <doc:doc> + <doc:summary>Amount to move in the y direction.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This will move the selection relative to it's current location.</doc:para> + <doc:para>This may be accomplished with transformation attributes or by changing x and y attributes depending on the state of the objects in the selection.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="selection_move_to"> + <arg type="d" name="x" direction="in" > + <doc:doc> + <doc:summary>X coordinate to move to.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="y" direction="in" > + <doc:doc> + <doc:summary>Y coordinate to move to.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>This will move the center of the selection to a specific location.</doc:para> + <doc:para>This may be accomplished with transformation attributes or by changing x and y attributes depending on the state of the objects in the selection.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="selection_move_to_layer"> + <arg type="s" name="layer" direction="in" > + <doc:doc> + <doc:summary>layer to move the selection to.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Move every item in the selection to a different layer.</doc:para> + <doc:para>Will error if layer does not exist.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Layers and Levels">Layers and Levels</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="selection_get_center"> + <arg type="ad" name="centerpoint" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> + <doc:doc> + <doc:summary>Center of the selection.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Gets the center of the selections bounding box in X,Y coordinates.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Coordinate System">Coordinate System</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="selection_to_path" > + <doc:doc> + <doc:description> + <doc:para>Turns all the objects in the selection into paths.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="method" to="document.object_to_path">object_to_path()</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="selection_combine" > + <arg type="s" name="type" direction="in" > + <doc:doc> + <doc:summary>Type of combination.</doc:summary> + </doc:doc> + </arg> + <arg type="as" name="newpaths" direction="out" > + <doc:doc> + <doc:summary>List of the ids of resulting paths after applying the operation.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Will erase all objects in the selection and replace with a single aggregate path.</doc:para> + <doc:para>There are 5 types that can be passed in:</doc:para> + <doc:para>'union': The new shape is all of the other shapes put together, even if they don't overlap (paths can have multiple non-contiguous areas.)</doc:para> + <doc:para>'intersection': The new shape is composed of the area where ALL the objects in the selection overlap. If there is no area where all shapes overlap the new shape will be empty.</doc:para> + <doc:para>'difference': The area of the second shape is subtracted from the first, only works with two objects.</doc:para> + <doc:para>'exclusion': The new shape is the area(s) where none of the objects in the selection overlapped. Only works with two objects.</doc:para> + <doc:para>'division': the first object is split into multiple segments by the second object. Only works with two objects.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="selection_change_level" > + <arg type="s" name="command" direction="in" > + <doc:doc> + <doc:summary>How to change the level</doc:summary> + </doc:doc> + </arg> + <arg type="b" name="objectsmoved" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>True if the objects changed levels. False if they don't(if they were already on top when being raised for example.)</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Will change the level of a selection, respective of other objects in the same layer. Will not affect the overlap of objects in different layers. Will do nothing if the selection contains objects in multiple layers.</doc:para> + <doc:para>There are 4 commands that can be passed in:</doc:para> + <doc:para>"raise" or "lower": Move the selection one level up, or one level down.</doc:para> + <doc:para>"to_top" of "to_bottom": Move the selection above all other objects or below all other objects.</doc:para> + </doc:description> + </doc:doc> + </method> + + <!-- LAYER FUNCTIONS --> + + <method name="layer_new"> + <arg type="s" name="layername" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The name of the new layer.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Turns all the objects in the selection into paths.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Layers and Levels">Layers and Levels</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="layer_set"> + <arg type="s" name="layer" direction="in" > + <doc:doc> + <doc:summary>The name of any layer.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Sets the layer given as the current layer</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Layers and Levels">Layers and Levels</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="layer_get_all"> + <arg type="as" name="layers" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value=""/> + <doc:doc> + <doc:summary>list of layers.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Get a list of all the layers in this document.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Layers and Levels">Layers and Levels</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="layer_change_level" > + <arg type="s" name="command" direction="in" > + <doc:doc> + <doc:summary>How to change the level</doc:summary> + </doc:doc> + </arg> + <arg type="b" name="layermoved" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>True if the layer was moved. False if it was not (if it was already on top when being raised for example.)</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Will change the level of a layer, respective of other layers. Will not affect the relative level of objects within the layer.</doc:para> + <doc:para>There are 4 commands that can be passed in:</doc:para> + <doc:para>"raise" or "lower": Move the layer one level up, or one level down.</doc:para> + <doc:para>"to_top" of "to_bottom": Move the layer above all other layers or below all other layers.</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="layer_next" > + <doc:doc> + <doc:description> + <doc:para>Sets the next (or higher) layer as active.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Layers and Levels">Layers and Levels</doc:ref></doc:seealso> + </doc:doc> + </method> + + <method name="layer_previous" > + <doc:doc> + <doc:description> + <doc:para>Sets the previous (or lower) layer as active.</doc:para> + </doc:description> + <doc:seealso><doc:ref type="interface" to="Layers and Levels">Layers and Levels</doc:ref></doc:seealso> + </doc:doc> + </method> + +<!-- signals --> + <signal name="ObjectMoved"> + <arg name="object_name" type="s"> + <doc:doc> + <doc:summary>The id of the object.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when an object has been moved.</doc:para> + </doc:description> + </doc:doc> + </signal> +<!-- tree --> + + <method name="get_children" > + <arg type="s" name="type" direction="in" > + <doc:doc> + <doc:summary>Any node with an "id" attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="as" name="parentid" direction="out" > + <doc:doc> + <doc:summary>The ids of this nodes children, NULL if bottom level.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Returns the children of any node. This function along with <doc:ref type="method" to="get_parent">get_parent()</doc:ref> can be used to navigate the XML tree. </doc:para> + </doc:description> + </doc:doc> + </method> + <method name="get_parent" > + <arg type="s" name="type" direction="in" > + <doc:doc> + <doc:summary>Any node with an "id" attribute.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="parentid" direction="out" > + <annotation name="org.freedesktop.DBus.GLib.ReturnVal" value="error"/> + <doc:doc> + <doc:summary>The id of this nodes parent, NULL if toplevel.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Returns the parent of any node. This function along with <doc:ref type="method" to="get_children">get_children()</doc:ref> can be used to navigate the XML tree. </doc:para> + </doc:description> + </doc:doc> + </method> + +</interface> +</node> diff --git a/src/extension/dbus/org.inkscape.service.in b/src/extension/dbus/org.inkscape.service.in new file mode 100644 index 0000000..9fffa02 --- /dev/null +++ b/src/extension/dbus/org.inkscape.service.in @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +[D-BUS Service] +Name=org.inkscape +Exec=bindir/bin/inkscape + + diff --git a/src/extension/dbus/proposed-interface.xml b/src/extension/dbus/proposed-interface.xml new file mode 100644 index 0000000..829ee7e --- /dev/null +++ b/src/extension/dbus/proposed-interface.xml @@ -0,0 +1,142 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- SPDX-License-Identifier: GPL-2.0-or-later --> +<!-- + * These are some of the proposed functions for the document interface. + * + * It is only used in generating documentation. + * + * None of these methods are implemented. If someone does code one of + * these methods, remove it from here and add it to document-interface.xml. + * + * Authors: + * Soren Berg <Glimmer07@gmail.com> + * + * Copyright (C) 2009 Soren Berg + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. +--> +<node name="/org/inkscape/proposed" + xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd" +> + <interface name="org.inkscape.proposed"> + + <signal name="Signals_Proposal"> + <doc:doc> + <doc:description> + <doc:para>Signals would undoubtedly be a useful thing to have in many circumstances. They are in proposed for two reasons: One, they complicate things for script writers and may conflict with the proposed C wrapper library. Two, I'm not sure how much coding it would take to implement them because I am familiar with neither Dbus signals or Inkscape events. Until I have done more experimenting I don't want to promise anything I'm not sure can be implemented in a timely fashion.</doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="ObjectResized"> + <arg name="object_name" type="s"> + <doc:doc> + <doc:summary>The id of the object.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when an object has been resized.</doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="ObjectStyleModified"> + <arg name="object_name" type="s"> + <doc:doc> + <doc:summary>The id of the object.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when the style of an object has been changed.</doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="ObjectCreated"> + <arg name="object_name" type="s"> + <doc:doc> + <doc:summary>The id of the object.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when an object has been created. Possibly useful for working in conjunction with a live user.</doc:para> + </doc:description> + </doc:doc> + </signal> + + <signal name="ObjectAddedToSelection"> + <arg name="object_name" type="s"> + <doc:doc> + <doc:summary>The id of the object.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Emitted when an object has been added to the selection. Possibly useful for working in conjunction with a live user.</doc:para> + </doc:description> + </doc:doc> + </signal> + + <method name="path_new" > + <arg type="d" name="x" direction="in" > + <doc:doc> + <doc:summary>The x value to begin the path.</doc:summary> + </doc:doc> + </arg> + <arg type="d" name="y" direction="in" > + <doc:doc> + <doc:summary>The y value to begin the path.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Begins a new path, extra nodes can be added with path_append().</doc:para> + </doc:description> + </doc:doc> + </method> + + <method name="path_append" > + <arg type="s" name="path" direction="in" > + <doc:doc> + <doc:summary>The name of the path to append to.</doc:summary> + </doc:doc> + </arg> + <arg type="s" name="type" direction="in" > + <doc:doc> + <doc:summary>A single letter denoting what type of node is being appended.</doc:summary> + </doc:doc> + </arg> + <arg type="ad" name="arguments" direction="in" > + <doc:doc> + <doc:summary>An array of numbers that describe the position and attributes of the path node.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Adds to an existing path. Close the path by sending "z" and no arguments.</doc:para> + <doc:para>You can no longer append to a path if it is closed.</doc:para> + </doc:description> + </doc:doc> + </method> + + +<!-- USE document-subset.h FILES --> + + <method name="selection_remove"> + <arg type="s" name="name" direction="in" > + <doc:doc> + <doc:summary>A object to remove from the selection.</doc:summary> + </doc:doc> + </arg> + <doc:doc> + <doc:description> + <doc:para>Removes a single object from the selection. In proposed because I already have a ton of selection functions and am not sure people would need this.</doc:para> + </doc:description> + </doc:doc> + </method> + + </interface> +</node> diff --git a/src/extension/dbus/pytester.py b/src/extension/dbus/pytester.py new file mode 100644 index 0000000..fdd7e9a --- /dev/null +++ b/src/extension/dbus/pytester.py @@ -0,0 +1,291 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +##################################################################### +# Python test script for Inkscape DBus API. +# +# Contains many examples of functions and various stress tests etc. +# Multiple tests can be run at once but the output will be a bit chaotic. +# List of test functions can be found at the bottom of the script. +##################################################################### + +import dbus +import random + +##################################################################### +# Various test functions, called at bottom of script +##################################################################### + +def randomrect (document): + document.rectangle( random.randint(0,1000), + random.randint(0,1000), + random.randint(1,100), + random.randint(1,100)) + +def lottarects ( document ): + document.pause_updates() + listy = [] + for x in range(1,2000): + if x == 1000: + print "HALFWAY" + if x == 1: + print "BEGUN" + document.rectangle( 0, 0, 100, 100) + #randomrect(document) + print "DONE" + for x in listy: + print x + selection_set(x) + document.resume_updates() + +def lottaverbs (doc): + doc.pause_updates() + doc.document_set_css ("fill:#ff0000;fill-opacity:.5;stroke:#0000ff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none") + doc.rectangle( 0, 0, 100, 100) + doc.select_all() + doc.selection_copy() + for x in range(1,2000): + if x == 1000: + print "HALFWAY" + if x == 1: + print "BEGUN" + doc.selection_paste() + #doc.rectangle( 0, 0, 100, 100) + doc.resume_updates() + +def testDrawing (doc): + doc.document_set_css ("fill:#000000;fill-opacity:.5;stroke:#000000;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none") + doc.ellipse( 0, 0, 100, 100) + doc.select_all() + doc.selection_copy() + for x in range(1,2000): + if x == 1000: + print "HALFWAY" + if x == 1: + print "BEGUN" + doc.selection_paste() + newrect = doc.selection_get()[0] + doc.set_color(newrect, 255 - x%255, 0, 200, True) + doc.set_color(newrect, 0, 255 - x%75, x%75, False) + doc.mark_as_unmodified() + + +def testcopypaste (document ): + #document.pause_updates() + print document.rectangle (400, 500, 100, 100) + print document.rectangle (200, 200, 100, 100) + document.select_all() + document.selection_copy() + document.selection_paste() + #document.resume_updates() + +def testShapes (doc): + doc.rectangle (0, 0, 100, 100) + doc.ellipse (100, 100, 100, 100) + doc.star (250, 250, 50, 25, 5, False, .9, 1.4) + doc.polygon (350, 350, 50, 0, 5) + doc.line (400,400,500,500) + doc.spiral (550,550,50,3) + +def testMovement (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (0, 100, 100, 100) + rect3 = doc.rectangle (0, 200, 100, 100) + doc.select_all() + doc.move(rect2, 100,100) + +def testImport (doc): + # CHANGE TO LOCAL SVG FILE! + img1 = doc.image(0,0, "/home/soren/chavatar.jpg") + doc.selection_add(img1) + doc.selection_scale(500) + doc.transform(img1, "rotate(30)") + +def testSelections (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (0, 100, 100, 100) + rect3 = doc.rectangle (0, 200, 100, 100) + rect4 = doc.rectangle (0, 300, 100, 100) + + doc.selection_add (rect1) + center = doc.selection_get_center() + for d in center: + print d + doc.selection_to_path() + doc.get_path(rect1) + doc.selection_move(100.0, 100.0) + doc.selection_set(rect2) + doc.selection_move_to(0.0,0.0) + doc.selection_set(rect3) + doc.move(rect4, 500.0, 500.0) + doc.select_all() + doc.selection_to_path() + result = doc.selection_get() + print len(result) + for d in result: + print d + +def testLevels (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (20, 20, 100, 100) + rect3 = doc.rectangle (40, 40, 100, 100) + rect4 = doc.rectangle (60, 60, 100, 100) + + doc.selection_set(rect1) + doc.selection_change_level("raise") + + doc.selection_set(rect4) + doc.selection_change_level("to_bottom") + +def testCombinations (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (20, 20, 100, 100) + rect3 = doc.rectangle (40, 40, 100, 100) + rect4 = doc.rectangle (60, 60, 100, 100) + rect5 = doc.rectangle (80, 80, 100, 100) + rect6 = doc.rectangle (100, 100, 100, 100) + rect7 = doc.rectangle (120, 120, 100, 100) + rect8 = doc.rectangle (140, 140, 100, 100) + rect9 = doc.rectangle (160, 160, 100, 100) + rect10 = doc.rectangle (180, 180, 100, 100) + + doc.selection_set_list([rect1, rect2]) + print doc.selection_combine("union") + doc.selection_set_list([rect3, rect4]) + print doc.selection_combine("intersection") + doc.selection_set_list([rect5, rect6]) + print doc.selection_combine("difference") + doc.selection_set_list([rect7, rect8]) + print doc.selection_combine("exclusion") + doc.selection_set_list([rect9, rect10]) + for d in doc.selection_divide(): + print d + +def testTransforms (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (20, 20, 100, 100) + doc.set_attribute(rect1, "transform", "matrix(0.08881734,0.94288151,-0.99604793,0.68505564,245.36153,118.60315)") + doc.selection_set(rect1) + + doc.selection_move_to(200, 200) + +def testLayer (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + print doc.new_layer() + rect2 = doc.rectangle (20, 20, 100, 100) + +def testGetSelection (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (20, 20, 100, 100) + rect3 = doc.rectangle (40, 40, 100, 100) + doc.select_all() + result = doc.selection_get() + print result + print len(result) + for d in result: + print d + + +def testDocStyle (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + doc.document_set_css ("fill:#ff0000;fill-opacity:.5;stroke:#0000ff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none") + rect2 = doc.rectangle (20, 20, 100, 100) + doc.document_set_css ("fill:#ffff00;fill-opacity:1;stroke:#009002;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none") + rect3 = doc.rectangle (40, 40, 100, 100) + doc.document_set_css ("fill:#00ff00;fill-opacity:1") + rect4 = doc.rectangle (60, 60, 100, 100) + +def testStyle (doc): + doc.document_set_css ("fill:#ffff00;fill-opacity:1;stroke:#009002;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none") + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (20, 20, 100, 100) + rect3 = doc.rectangle (40, 40, 100, 100) + rect4 = doc.rectangle (60, 60, 100, 100) + + doc.modify_css (rect3, "fill-opacity", ".5") + doc.merge_css (rect4, "fill:#0000ff;fill-opacity:.25;") + print doc.get_css (rect4) + +def testLayers (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + layer1 = doc.layer_new() + layer2 = doc.layer_new() + rect2 = doc.rectangle (20, 20, 100, 100) + rect3 = doc.rectangle (40, 40, 100, 100) + doc.selection_add(rect3) + doc.selection_move_to_layer(layer1) + +def testLoadSave (doc): + doc.load("/home/soren/testfile.svg") + rect2 = doc.rectangle (0, 0, 200, 200) + doc.save_as("/home/soren/testsave.svg") + rect1 = doc.rectangle (20, 20, 200, 200) + doc.save() + rect3 = doc.rectangle (40, 40, 200, 200) + doc.save_as("/home/soren/testsave2.svg") + +def testArray (doc): + rect1 = doc.rectangle (0, 0, 100, 100) + rect2 = doc.rectangle (20, 20, 100, 100) + rect3 = doc.rectangle (40, 40, 100, 100) + doc.selection_set_list([rect1, rect2, rect3]) + +def testPath (doc): + cr1 = doc.ellipse(0,0,50,50) + print doc.get_path(cr1) + doc.object_to_path(cr1) + print doc.get_path(cr1) + #doc.get_node_coordinates(cr1) + +# Needs work. +def testText(doc): + print doc.text(200, 200, "svg:text") + + +##################################################################### +# Setup bus connection, create documents. +##################################################################### + +# Connect to bus +bus = dbus.SessionBus() + +# Get interface for default document +inkdoc1 = bus.get_object('org.inkscape', '/org/inkscape/desktop_0') +doc1 = dbus.Interface(inkdoc1, dbus_interface="org.inkscape.document") + +# Create new window and get the interface for that. (optional) +inkapp = bus.get_object('org.inkscape', + '/org/inkscape/application') +desk2 = inkapp.desktop_new(dbus_interface='org.inkscape.application') +inkdoc2 = bus.get_object('org.inkscape', desk2) +doc2 = dbus.Interface(inkdoc2, dbus_interface="org.inkscape.document") + + +##################################################################### +# Call desired test functions +##################################################################### + +#lottaverbs (doc1) +#lottarects (doc1) +#testDrawing (doc1) + +#doc1.pause_updates() + +testShapes(doc1) +#testMovement(doc1) +#testImport(doc1) # EDIT FUNCTION TO OPEN EXISTING FILE! +#testcopypaste (doc1) +#testTransforms (doc1) +#testDocStyle(doc1) +#testLayers(doc1) +#testLoadSave(doc1) +#testArray(doc1) +#testSelections(doc1) +#testCombinations(doc1) +#testText(doc1) +#testPath(doc1) + +#doc1.resume_updates + + +# Prevents asking if you want to save when closing document. +doc1.mark_as_unmodified() + diff --git a/src/extension/dbus/wrapper/inkdbus.pc.in b/src/extension/dbus/wrapper/inkdbus.pc.in new file mode 100644 index 0000000..512e3b3 --- /dev/null +++ b/src/extension/dbus/wrapper/inkdbus.pc.in @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +bindir=@bindir@ +includedir=@includedir@ + +Cflags: -I${includedir}/libinkdbus-0.1 +Requires: gobject-2.0 dbus-glib-1 +Libs: -L${libdir} -linkdbus + +Name: inkdbus +Description: Inkscape DBus Interface Wrapper +Version: @VERSION@ + diff --git a/src/extension/dbus/wrapper/inkscape-dbus-wrapper.c b/src/extension/dbus/wrapper/inkscape-dbus-wrapper.c new file mode 100644 index 0000000..cc9399c --- /dev/null +++ b/src/extension/dbus/wrapper/inkscape-dbus-wrapper.c @@ -0,0 +1,787 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#include "inkscape-dbus-wrapper.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + + + +#include "document-client-glue.h" +#include <dbus/dbus-glib.h> +#include <dbus/dbus.h> + +// (static.*(\n[^}]*)*(async)+.*(\n[^}]*)*})|typedef void .*; +// http://www.josephkahn.com/music/index.xml + +/* PRIVATE get a connection to the session bus */ +DBusGConnection * +dbus_get_connection() { + GError *error = NULL; + DBusGConnection *connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); + if (error) { + fprintf(stderr, "Failed to get connection"); + return NULL; + } + else + return connection; +} + +/* PRIVATE create a proxy object for a bus.*/ +DBusGProxy * +dbus_get_proxy(DBusGConnection *connection) { + return dbus_g_proxy_new_for_name (connection, + DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS); +} + +#if 0 +/* PRIVATE register an object on a bus */ +static gpointer +dbus_register_object (DBusGConnection *connection, + DBusGProxy * proxy, + GType object_type, + const DBusGObjectInfo *info, + const gchar *path) +{ + GObject *object = (GObject*)g_object_new (object_type, NULL); + dbus_g_object_type_install_info (object_type, info); + dbus_g_connection_register_g_object (connection, path, object); + return object; +} +#endif + +/**************************************************************************** + DOCUMENT INTERFACE CLASS STUFF +****************************************************************************/ + +struct _DocumentInterface { + GObject parent; + DBusGProxy * proxy; +}; + +G_DEFINE_TYPE(DocumentInterface, document_interface, G_TYPE_OBJECT) + +static void +document_interface_finalize (GObject *object) +{ + G_OBJECT_CLASS (document_interface_parent_class)->finalize (object); +} + + +static void +document_interface_class_init (DocumentInterfaceClass *klass) +{ + GObjectClass *object_class; + object_class = G_OBJECT_CLASS (klass); + object_class->finalize = document_interface_finalize; +} + +static void +document_interface_init (DocumentInterface *object) +{ + object->proxy = NULL; +} + + +DocumentInterface * +document_interface_new (void) +{ + return (DocumentInterface*)g_object_new (TYPE_DOCUMENT_INTERFACE, NULL); +} + +DocumentInterface * +inkscape_desktop_init_dbus () +{ + DBusGConnection *connection; + GError *error; + DBusGProxy *proxy; + +#if !GLIB_CHECK_VERSION(2,36,0) + g_type_init (); +#endif + + error = NULL; + connection = dbus_g_bus_get (DBUS_BUS_SESSION, + &error); + if (connection == NULL) + { + g_printerr ("Failed to open connection to bus: %s\n", + error->message); + g_error_free (error); + exit (1); + } + + proxy = dbus_g_proxy_new_for_name (connection, + "org.inkscape", + "/org/inkscape/desktop_0", + "org.inkscape.document"); + + DocumentInterface * inkdesk = (DocumentInterface *)g_object_new (TYPE_DOCUMENT_INTERFACE, NULL); + inkdesk->proxy = proxy; + return inkdesk; +} + + +//static +gboolean +inkscape_delete_all (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_delete_all (proxy, error); +} + +//static +gboolean +inkscape_call_verb (DocumentInterface *doc, const char * IN_verbid, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_call_verb(proxy, IN_verbid, error); +} + + +//static +gchar * +inkscape_rectangle (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_width, const gint IN_height, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_rectangle (proxy, IN_x, IN_y, IN_width, IN_height, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_ellipse (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_width, const gint IN_height, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_ellipse (proxy, IN_x, IN_y, IN_width, IN_height, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_polygon (DocumentInterface *doc, const gint IN_cx, const gint IN_cy, const gint IN_radius, const gint IN_rotation, const gint IN_sides, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_polygon (proxy, IN_cx, IN_cy, IN_radius, IN_rotation, IN_sides, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_star (DocumentInterface *doc, const gint IN_cx, const gint IN_cy, const gint IN_r1, const gint IN_r2, const gdouble IN_arg1, const gdouble IN_arg2, const gint IN_sides, const gdouble IN_rounded, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_star (proxy, IN_cx, IN_cy, IN_r1, IN_r2, IN_arg1, IN_arg2, IN_sides, IN_rounded, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_spiral (DocumentInterface *doc, const gint IN_cx, const gint IN_cy, const gint IN_r, const gint IN_revolutions, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_spiral (proxy, IN_cx, IN_cy, IN_r, IN_revolutions, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_line (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_x2, const gint IN_y2, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_line (proxy, IN_x, IN_y, IN_x2, IN_y2, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_text (DocumentInterface *doc, const gint IN_x, const gint IN_y, const char * IN_text, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_text (proxy, IN_x, IN_y, IN_text, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_image (DocumentInterface *doc, const gint IN_x, const gint IN_y, const char * IN_text, GError **error) +{ + char * OUT_object_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_image (proxy, IN_x, IN_y, IN_text, &OUT_object_name, error); + return OUT_object_name; +} + +//static +char * +inkscape_node (DocumentInterface *doc, const char * IN_svgtype, GError **error) +{ + char *OUT_node_name; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_node (proxy, IN_svgtype, &OUT_node_name, error); + return OUT_node_name; +} + +//static +gdouble +inkscape_document_get_width (DocumentInterface *doc, GError **error) +{ + gdouble OUT_val; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_document_get_width (proxy, &OUT_val, error); + return OUT_val; +} + +//static +gdouble +inkscape_document_get_height (DocumentInterface *doc, GError **error) +{ + gdouble OUT_val; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_document_get_height (proxy, &OUT_val, error); + return OUT_val; +} + +//static +char * +inkscape_document_get_css (DocumentInterface *doc, GError **error) +{ + char * OUT_css; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_document_get_css (proxy, &OUT_css, error); + return OUT_css; +} + +//static +gboolean +inkscape_document_set_css (DocumentInterface *doc, const char * IN_stylestring, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_document_set_css (proxy, IN_stylestring, error); +} + +//static +gboolean +inkscape_document_merge_css (DocumentInterface *doc, const char * IN_stylestring, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_document_merge_css (proxy, IN_stylestring, error); +} + +//static +gboolean +inkscape_document_resize_to_fit_selection (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_document_resize_to_fit_selection (proxy, error); +} + +//static +gboolean +inkscape_set_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, const char * IN_newval, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_set_attribute (proxy, IN_shape, IN_attribute, IN_newval, error); +} + +//static +gboolean +inkscape_set_int_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, const gint IN_newval, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_set_int_attribute (proxy, IN_shape, IN_attribute, IN_newval, error); +} + +//static +gboolean +inkscape_set_double_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, const gdouble IN_newval, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_set_double_attribute (proxy, IN_shape, IN_attribute, IN_newval, error); +} + +//static +char * +inkscape_get_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, GError **error) +{ + char * OUT_val; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_get_attribute (proxy, IN_shape, IN_attribute, &OUT_val, error); + return OUT_val; +} + +//static +gboolean +inkscape_move (DocumentInterface *doc, const char * IN_shape, const gdouble IN_x, const gdouble IN_y, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_move (proxy, IN_shape, IN_x, IN_y, error); +} + +//static +gboolean +inkscape_move_to (DocumentInterface *doc, const char * IN_shape, const gdouble IN_x, const gdouble IN_y, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_move_to (proxy, IN_shape, IN_x, IN_y, error); +} + +//static +gboolean +inkscape_object_to_path (DocumentInterface *doc, const char * IN_objectname, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_object_to_path (proxy, IN_objectname, error); +} + +//static +char * +inkscape_get_path (DocumentInterface *doc, const char * IN_shape, GError **error) +{ + char * OUT_val; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_get_path (proxy, IN_shape, &OUT_val, error); + return OUT_val; +} + +//static +gboolean +inkscape_transform (DocumentInterface *doc, const char * IN_shape, const char * IN_transformstr, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_transform (proxy, IN_shape, IN_transformstr, error); +} + +//static +char * +inkscape_get_css (DocumentInterface *doc, const char * IN_shape, GError **error) +{ + char * OUT_css; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_get_css (proxy, IN_shape, &OUT_css, error); + return OUT_css; +} + +//static +gboolean +inkscape_modify_css (DocumentInterface *doc, const char * IN_shape, const char * IN_cssattrib, const char * IN_newval, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_modify_css (proxy, IN_shape, IN_cssattrib, IN_newval, error); +} + +//static +gboolean +inkscape_merge_css (DocumentInterface *doc, const char * IN_shape, const char * IN_stylestring, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_merge_css (proxy, IN_shape, IN_stylestring, error); +} + +//static +gboolean +inkscape_set_color (DocumentInterface *doc, const char * IN_shape, const gint IN_red, const gint IN_green, const gint IN_blue, const gboolean IN_fill, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_set_color (proxy, IN_shape, IN_red, IN_green, IN_blue, IN_fill, error); +} + +//static +gboolean +inkscape_move_to_layer (DocumentInterface *doc, const char * IN_objectname, const char * IN_layername, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_move_to_layer (proxy, IN_objectname, IN_layername, error); +} + +//static +GArray* +inkscape_get_node_coordinates (DocumentInterface *doc, const char * IN_shape, GError **error) +{ + GArray* OUT_points; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_get_node_coordinates (proxy, IN_shape, &OUT_points, error); + return OUT_points; +} + +//static +gboolean +inkscape_save (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_save (proxy, error); +} + +//static +gboolean +inkscape_save_as (DocumentInterface *doc, const char * IN_pathname, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_save_as (proxy, IN_pathname, error); +} + +//static +gboolean +inkscape_load (DocumentInterface *doc, const char * IN_pathname, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_load (proxy, IN_pathname, error); +} + +//static +gboolean +inkscape_mark_as_unmodified (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_mark_as_unmodified (proxy, error); +} + +//static +gboolean +inkscape_close (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_close (proxy, error); +} + +//static +gboolean +inkscape_inkscape_exit (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_exit (proxy, error); +} + +//static +gboolean +inkscape_undo (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_undo (proxy, error); +} + +//static +gboolean +inkscape_redo (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_redo (proxy, error); +} + +//static +gboolean +inkscape_pause_updates (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_pause_updates (proxy, error); +} + +//static +gboolean +inkscape_resume_updates (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_resume_updates (proxy, error); +} + +//static +gboolean +inkscape_update (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_update (proxy, error); +} + +//static +char ** +inkscape_selection_get (DocumentInterface *doc, GError **error) +{ + char ** OUT_listy; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_selection_get (proxy, &OUT_listy, error); + return OUT_listy; +} + +//static +gboolean +inkscape_selection_add (DocumentInterface *doc, const char * IN_name, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_add (proxy, IN_name, error); +} + +//static +gboolean +inkscape_selection_add_list (DocumentInterface *doc, const char ** IN_name, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_add_list (proxy, IN_name, error); +} + +//static +gboolean +inkscape_selection_set (DocumentInterface *doc, const char * IN_name, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_set (proxy, IN_name, error); +} + +//static +gboolean +inkscape_selection_set_list (DocumentInterface *doc, const char ** IN_name, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_set_list (proxy, IN_name, error); +} + +//static +gboolean +inkscape_selection_rotate (DocumentInterface *doc, const gint IN_angle, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_rotate (proxy, IN_angle, error); +} + +//static +gboolean +inkscape_selection_delete (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_delete (proxy, error); +} + +//static +gboolean +inkscape_selection_clear (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_clear (proxy, error); +} + +//static +gboolean +inkscape_select_all (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_select_all (proxy, error); +} + +//static +gboolean +inkscape_select_all_in_all_layers (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_select_all_in_all_layers (proxy, error); +} + +//static +gboolean +inkscape_selection_box (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_x2, const gint IN_y2, const gboolean IN_replace, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_box (proxy, IN_x, IN_y, IN_x2, IN_y2, IN_replace, error); +} + +//static +gboolean +inkscape_selection_invert (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_invert (proxy, error); +} + +//static +gboolean +inkscape_selection_group (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_group (proxy, error); +} + +//static +gboolean +inkscape_selection_ungroup (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_ungroup (proxy, error); +} + +//static +gboolean +inkscape_selection_cut (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_cut (proxy, error); +} + +//static +gboolean +inkscape_selection_copy (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_copy (proxy, error); +} + +//static +gboolean +inkscape_selection_paste (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_paste (proxy, error); +} + +//static +gboolean +inkscape_selection_scale (DocumentInterface *doc, const gdouble IN_grow, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_scale (proxy, IN_grow, error); +} + +//static +gboolean +inkscape_selection_move (DocumentInterface *doc, const gdouble IN_x, const gdouble IN_y, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_move (proxy, IN_x, IN_y, error); +} + +//static +gboolean +inkscape_selection_move_to (DocumentInterface *doc, const gdouble IN_x, const gdouble IN_y, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_move_to (proxy, IN_x, IN_y, error); +} + +//static +gboolean +inkscape_selection_move_to_layer (DocumentInterface *doc, const char * IN_layer, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_move_to_layer (proxy, IN_layer, error); +} + +//static +GArray * +inkscape_selection_get_center (DocumentInterface *doc, GError **error) +{ + GArray* OUT_centerpoint; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_selection_get_center (proxy, &OUT_centerpoint, error); + return OUT_centerpoint; +} + +//static +gboolean +inkscape_selection_to_path (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_selection_to_path (proxy, error); +} + +//static +char ** +inkscape_selection_combine (DocumentInterface *doc, const char * IN_type, GError **error) +{ + char ** OUT_newpaths; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_selection_combine (proxy, IN_type, &OUT_newpaths, error); + return OUT_newpaths; +} + +//static +gboolean +inkscape_selection_change_level (DocumentInterface *doc, const char * IN_command, GError **error) +{ + gboolean OUT_objectsmoved; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_selection_change_level (proxy, IN_command, &OUT_objectsmoved, error); + return OUT_objectsmoved; +} + +//static +char * +inkscape_layer_new (DocumentInterface *doc, GError **error) +{ + char * OUT_layername; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_layer_new (proxy, &OUT_layername, error); + return OUT_layername; +} + +//static +gboolean +inkscape_layer_set (DocumentInterface *doc, const char * IN_layer, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_layer_set (proxy, IN_layer, error); +} + +//static +char ** +inkscape_layer_get_all (DocumentInterface *doc, GError **error) +{ + char ** OUT_layers; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_layer_get_all (proxy, &OUT_layers, error); + return OUT_layers; +} + +//static +gboolean +inkscape_layer_change_level (DocumentInterface *doc, const char * IN_command, GError **error) +{ + gboolean OUT_layermoved; + DBusGProxy *proxy = doc->proxy; + org_inkscape_document_layer_change_level (proxy, IN_command, &OUT_layermoved, error); + return OUT_layermoved; +} + +//static +gboolean +inkscape_layer_next (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_layer_next (proxy, error); +} + +//static +gboolean +inkscape_layer_previous (DocumentInterface *doc, GError **error) +{ + DBusGProxy *proxy = doc->proxy; + return org_inkscape_document_layer_previous (proxy, error); +} + +/* +int +main (int argc, char** argv) +{ + gchar * result; + GError *error = NULL; + DocumentInterface * doc = inkscape_desktop_init_dbus (); + result = rectangle (doc->proxy, 10, 10, 100, 100, &error); + printf("RESULT: %s\n", result); + + //dbus_g_proxy_call(doc->proxy, "rectangle", &error, G_TYPE_INT, 100, G_TYPE_INT, 100, G_TYPE_INT, 100, G_TYPE_INT, 100, G_TYPE_INVALID, G_TYPE_INVALID); + printf("yes\n"); +} +*/ + diff --git a/src/extension/dbus/wrapper/inkscape-dbus-wrapper.h b/src/extension/dbus/wrapper/inkscape-dbus-wrapper.h new file mode 100644 index 0000000..e2abd30 --- /dev/null +++ b/src/extension/dbus/wrapper/inkscape-dbus-wrapper.h @@ -0,0 +1,347 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO: insert short description here + *//* + * Authors: see git history + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef INKSCAPE_EXTENSION_DOCUMENT_INTERFACE_H_ +#define INKSCAPE_EXTENSION_DOCUMENT_INTERFACE_H_ + +#include <glib.h> +#include <glib-object.h> + +//#include "document-client-glue-mod.h" + +//#include <dbus/dbus-glib-bindings.h> +//#include <dbus/dbus-glib-lowlevel.h> + +#define TYPE_DOCUMENT_INTERFACE (document_interface_get_type ()) +#define DOCUMENT_INTERFACE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), TYPE_DOCUMENT_INTERFACE, DocumentInterface)) +#define DOCUMENT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_DOCUMENT_INTERFACE, DocumentInterfaceClass)) +#define IS_DOCUMENT_INTERFACE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), TYPE_DOCUMENT_INTERFACE)) +#define IS_DOCUMENT_INTERFACE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_DOCUMENT_INTERFACE)) +#define DOCUMENT_INTERFACE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_DOCUMENT_INTERFACE, DocumentInterfaceClass)) + +G_BEGIN_DECLS + +typedef struct _DocumentInterface DocumentInterface; +typedef struct _DocumentInterfaceClass DocumentInterfaceClass; + +struct _DocumentInterface; + +struct _DocumentInterfaceClass { + GObjectClass parent; +}; + + +DocumentInterface *document_interface_new (void); +GType document_interface_get_type (void); + + + +DocumentInterface * +inkscape_desktop_init_dbus (); + +//static +gboolean +inkscape_delete_all (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_call_verb (DocumentInterface *doc, const char * IN_verbid, GError **error); + + +//static +gchar * +inkscape_rectangle (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_width, const gint IN_height, GError **error); + +//static +char * +inkscape_ellipse (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_width, const gint IN_height, GError **error); + +//static +char * +inkscape_polygon (DocumentInterface *doc, const gint IN_cx, const gint IN_cy, const gint IN_radius, const gint IN_rotation, const gint IN_sides, GError **error); + +//static +char * +inkscape_star (DocumentInterface *doc, const gint IN_cx, const gint IN_cy, const gint IN_r1, const gint IN_r2, const gdouble IN_arg1, const gdouble IN_arg2, const gint IN_sides, const gdouble IN_rounded, GError **error); + +//static +char * +inkscape_spiral (DocumentInterface *doc, const gint IN_cx, const gint IN_cy, const gint IN_r, const gint IN_revolutions, GError **error); + +//static +char * +inkscape_line (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_x2, const gint IN_y2, GError **error); + +//static +char * +inkscape_text (DocumentInterface *doc, const gint IN_x, const gint IN_y, const char * IN_text, GError **error); + +//static +char * +inkscape_image (DocumentInterface *doc, const gint IN_x, const gint IN_y, const char * IN_text, GError **error); + +//static +char * +inkscape_node (DocumentInterface *doc, const char * IN_svgtype, GError **error); + +//static +gdouble +inkscape_document_get_width (DocumentInterface *doc, GError **error); + +//static +gdouble +inkscape_document_get_height (DocumentInterface *doc, GError **error); + +//static +char * +inkscape_document_get_css (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_document_set_css (DocumentInterface *doc, const char * IN_stylestring, GError **error); + +//static +gboolean +inkscape_document_merge_css (DocumentInterface *doc, const char * IN_stylestring, GError **error); + +//static +gboolean +inkscape_document_resize_to_fit_selection (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_set_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, const char * IN_newval, GError **error); + +//static +gboolean +inkscape_set_int_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, const gint IN_newval, GError **error); + +//static +gboolean +inkscape_set_double_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, const gdouble IN_newval, GError **error); + +//static +char * +inkscape_get_attribute (DocumentInterface *doc, const char * IN_shape, const char * IN_attribute, GError **error); + +//static +gboolean +inkscape_move (DocumentInterface *doc, const char * IN_shape, const gdouble IN_x, const gdouble IN_y, GError **error); + +//static +gboolean +inkscape_move_to (DocumentInterface *doc, const char * IN_shape, const gdouble IN_x, const gdouble IN_y, GError **error); + +//static +gboolean +inkscape_object_to_path (DocumentInterface *doc, const char * IN_objectname, GError **error); + +//static +char * +inkscape_get_path (DocumentInterface *doc, const char * IN_shape, GError **error); + +//static +gboolean +inkscape_transform (DocumentInterface *doc, const char * IN_shape, const char * IN_transformstr, GError **error); + +//static +char * +inkscape_get_css (DocumentInterface *doc, const char * IN_shape, GError **error); + +//static +gboolean +inkscape_modify_css (DocumentInterface *doc, const char * IN_shape, const char * IN_cssattrib, const char * IN_newval, GError **error); + +//static +gboolean +inkscape_inkscape_merge_css (DocumentInterface *doc, const char * IN_shape, const char * IN_stylestring, GError **error); + +//static +gboolean +inkscape_set_color (DocumentInterface *doc, const char * IN_shape, const gint IN_red, const gint IN_green, const gint IN_blue, const gboolean IN_fill, GError **error); + +//static +gboolean +inkscape_move_to_layer (DocumentInterface *doc, const char * IN_objectname, const char * IN_layername, GError **error); + +//static +GArray* +inkscape_get_node_coordinates (DocumentInterface *doc, const char * IN_shape, GError **error); + +//static +gboolean +inkscape_save (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_save_as (DocumentInterface *doc, const char * IN_pathname, GError **error); + +//static +gboolean +inkscape_load (DocumentInterface *doc, const char * IN_pathname, GError **error); + +//static +gboolean +inkscape_mark_as_unmodified (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_close (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_inkscape_exit (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_undo (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_redo (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_pause_updates (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_resume_updates (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_update (DocumentInterface *doc, GError **error); + +//static +char ** +inkscape_selection_get (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_add (DocumentInterface *doc, const char * IN_name, GError **error); + +//static +gboolean +inkscape_selection_add_list (DocumentInterface *doc, const char ** IN_name, GError **error); + +//static +gboolean +inkscape_selection_set (DocumentInterface *doc, const char * IN_name, GError **error); + +//static +gboolean +inkscape_selection_set_list (DocumentInterface *doc, const char ** IN_name, GError **error); + +//static +gboolean +inkscape_selection_rotate (DocumentInterface *doc, const gint IN_angle, GError **error); + +//static +gboolean +inkscape_selection_delete (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_clear (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_select_all (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_select_all_in_all_layers (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_box (DocumentInterface *doc, const gint IN_x, const gint IN_y, const gint IN_x2, const gint IN_y2, const gboolean IN_replace, GError **error); + +//static +gboolean +inkscape_selection_invert (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_group (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_ungroup (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_cut (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_copy (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_paste (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_scale (DocumentInterface *doc, const gdouble IN_grow, GError **error); + +//static +gboolean +inkscape_selection_move (DocumentInterface *doc, const gdouble IN_x, const gdouble IN_y, GError **error); + +//static +gboolean +inkscape_selection_move_to (DocumentInterface *doc, const gdouble IN_x, const gdouble IN_y, GError **error); + +//static +gboolean +inkscape_selection_move_to_layer (DocumentInterface *doc, const char * IN_layer, GError **error); + +//static +GArray * +inkscape_selection_get_center (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_selection_to_path (DocumentInterface *doc, GError **error); + +//static +char ** +inkscape_selection_combine (DocumentInterface *doc, const char * IN_type, GError **error); + +//static +gboolean +inkscape_selection_change_level (DocumentInterface *doc, const char * IN_command, GError **error); + +//static +char * +inkscape_layer_new (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_layer_set (DocumentInterface *doc, const char * IN_layer, GError **error); + +//static +char ** +inkscape_layer_get_all (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_layer_change_level (DocumentInterface *doc, const char * IN_command, GError **error); + +//static +gboolean +inkscape_layer_next (DocumentInterface *doc, GError **error); + +//static +gboolean +inkscape_layer_previous (DocumentInterface *doc, GError **error); + +G_END_DECLS + +#endif // INKSCAPE_EXTENSION_DOCUMENT_INTERFACE_H_ diff --git a/src/extension/dependency.cpp b/src/extension/dependency.cpp new file mode 100644 index 0000000..d017aef --- /dev/null +++ b/src/extension/dependency.cpp @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Author: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006 Johan Engelen, johan@shouraizou.nl + * Copyright (C) 2004 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> +#include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> +#include "dependency.h" +#include "db.h" +#include "extension.h" +#include "io/resource.h" + +namespace Inkscape { +namespace Extension { + +// These strings are for XML attribute comparisons and should not be translated; +// make sure to keep in sync with enum defined in dependency.h +gchar const * Dependency::_type_str[] = { + "executable", + "file", + "extension", +}; + +// These strings are for XML attribute comparisons and should not be translated +// make sure to keep in sync with enum defined in dependency.h +gchar const * Dependency::_location_str[] = { + "path", + "extensions", + "inx", + "absolute", +}; + +/** + \brief Create a dependency using an XML definition + \param in_repr XML definition of the dependency + \param extension Reference to the extension requesting this dependency + \param default_type Default file type of the dependency (unless overridden by XML definition's "type" attribute) + + This function mostly looks for the 'location' and 'type' attributes + and turns them into the enums of the same name. This makes things + a little bit easier to use later. Also, a pointer to the core + content is pulled out -- also to make things easier. +*/ +Dependency::Dependency (Inkscape::XML::Node * in_repr, const Extension *extension, type_t default_type) + : _repr(in_repr) + , _extension(extension) + , _type(default_type) +{ + Inkscape::GC::anchor(_repr); + + if (const gchar * location = _repr->attribute("location")) { + for (int i = 0; i < LOCATION_CNT && location != nullptr; i++) { + if (!strcmp(location, _location_str[i])) { + _location = (location_t)i; + break; + } + } + } else if (const gchar * location = _repr->attribute("reldir")) { // backwards-compatibility + for (int i = 0; i < LOCATION_CNT && location != nullptr; i++) { + if (!strcmp(location, _location_str[i])) { + _location = (location_t)i; + break; + } + } + } + + const gchar * type = _repr->attribute("type"); + for (int i = 0; i < TYPE_CNT && type != nullptr; i++) { + if (!strcmp(type, _type_str[i])) { + _type = (type_t)i; + break; + } + } + + _string = _repr->firstChild()->content(); + + _description = _repr->attribute("description"); + if (_description == nullptr) + _description = _repr->attribute("_description"); + + return; +} + +/** + \brief This dependency is not longer needed + + Unreference the XML structure. +*/ +Dependency::~Dependency () +{ + Inkscape::GC::release(_repr); +} + +/** + \brief Check if the dependency passes. + \return Whether or not the dependency passes. + + This function depends largely on all of the enums. The first level + that is evaluated is the \c _type. + + If the type is \c TYPE_EXTENSION then the id for the extension is + looked up in the database. If the extension is found, and it is + not deactivated, the dependency passes. + + If the type is \c TYPE_EXECUTABLE or \c TYPE_FILE things are getting + even more interesting because now the \c _location variable is also + taken into account. First, the difference between the two is that + the file test for \c TYPE_EXECUTABLE also tests to make sure the + file is executable, besides checking that it exists. + + If the \c _location is \c LOCATION_EXTENSIONS then the \c INKSCAPE_EXTENSIONDIR + is put on the front of the string with \c build_filename. Then the + appropriate filetest is run. + + If the \c _location is \c LOCATION_ABSOLUTE then the file test is + run directly on the string. + + If the \c _location is \c LOCATION_PATH or not specified then the + path is used to find the file. Each entry in the path is stepped + through, attached to the string, and then tested. If the file is + found then a TRUE is returned. If we get all the way through the + path then a FALSE is returned, the command could not be found. +*/ +bool Dependency::check () +{ + if (_string == nullptr) { + return false; + } + + _absolute_location = ""; + + switch (_type) { + case TYPE_EXTENSION: { + Extension * myext = db.get(_string); + if (myext == nullptr) return false; + if (myext->deactivated()) return false; + break; + } + case TYPE_EXECUTABLE: + case TYPE_FILE: { + Glib::FileTest filetest = Glib::FILE_TEST_EXISTS; + + std::string location(_string); + + // get potential file extension for later usage + std::string extension; + size_t index = location.find_last_of("."); + if (index != std::string::npos) { + extension = location.substr(index); + } + + // check interpreted scripts as "file" for backwards-compatibility, even if "executable" was requested + static const std::vector<std::string> interpreted = {".py", ".pl", ".rb"}; + if (!extension.empty() && + std::find(interpreted.begin(), interpreted.end(), extension) != interpreted.end()) + { + _type = TYPE_FILE; + } + +#ifndef _WIN32 + // There's no executable bit on Windows, so this is unreliable + // glib would search for "executable types" instead, which are only {".exe", ".cmd", ".bat", ".com"}, + // and would therefore miss files without extension and other script files (like .py files) + if (_type == TYPE_EXECUTABLE) { + filetest = Glib::FILE_TEST_IS_EXECUTABLE; + } +#endif + + switch (_location) { + case LOCATION_EXTENSIONS: { + // get_filename will warn if the resource isn't found, while returning an empty string. + std::string temploc = + Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::EXTENSIONS, location.c_str()); + if (!temploc.empty()) { + location = temploc; + _absolute_location = temploc; + break; + } + /* Look for deprecated locations next */ + auto deprloc = g_build_filename("inkex", "deprecated-simple", location.c_str(), NULL); + std::string tempdepr = + Inkscape::IO::Resource::get_filename(Inkscape::IO::Resource::EXTENSIONS, deprloc, false, true); + g_free(deprloc); + if (!tempdepr.empty()) { + location = tempdepr; + _absolute_location = tempdepr; + break; + } + + } /* PASS THROUGH!!! */ // TODO: the pass-through seems wrong - either it's relative or not. + case LOCATION_ABSOLUTE: { + // TODO: should we check if the directory actually is absolute and/or sanitize the filename somehow? + if (!Glib::file_test(location, filetest)) { + return false; + } + _absolute_location = location; + break; + } + case LOCATION_INX: { + std::string base_directory = _extension->get_base_directory(); + if (base_directory.empty()) { + g_warning("Dependency '%s' requests location relative to .inx file, " + "which is unknown for extension '%s'", _string, _extension->get_id()); + } + std::string absolute_location = Glib::build_filename(base_directory, location); + if (!Glib::file_test(absolute_location, filetest)) { + return false; + } + _absolute_location = absolute_location; + break; + } + /* The default case is to look in the path */ + case LOCATION_PATH: + default: { + // TODO: we can likely use g_find_program_in_path (or its glibmm equivalent) for executable types + + gchar * path = g_strdup(g_getenv("PATH")); + + if (path == nullptr) { + /* There is no `PATH' in the environment. + The default search path is the current directory */ + path = g_strdup(G_SEARCHPATH_SEPARATOR_S); + } + + gchar * orig_path = path; + + for (; path != nullptr;) { + gchar * local_path; // to have the path after detection of the separator + std::string final_name; + + local_path = path; + path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR); + /* Not sure whether this is UTF8 happy, but it would seem + like it considering that I'm searching (and finding) + the ':' character */ + if (path != nullptr) { + path[0] = '\0'; + path++; + } + + if (*local_path == '\0') { + final_name = _string; + } else { + final_name = Glib::build_filename(local_path, _string); + } + + if (Glib::file_test(final_name, filetest)) { + g_free(orig_path); + _absolute_location = final_name; + return true; + } + +#ifdef _WIN32 + // Unfortunately file extensions tend to be different on Windows and we can't know + // which one it is, so try all extensions glib assumes to be executable. + // As we can only guess here, return the version without extension if either one is found, + // so that we don't accidentally override (or conflict with) some g_spawn_* magic. + if (_type == TYPE_EXECUTABLE) { + static const std::vector<std::string> extensions = {".exe", ".cmd", ".bat", ".com"}; + if (extension.empty() || + std::find(extensions.begin(), extensions.end(), extension) == extensions.end()) + { + for (auto extension : extensions) { + if (Glib::file_test(final_name + extension, filetest)) { + g_free(orig_path); + _absolute_location = final_name; + return true; + } + } + } + } +#endif + } + + g_free(orig_path); + return false; /* Reverse logic in this one */ + } + } /* switch _location */ + break; + } /* TYPE_FILE, TYPE_EXECUTABLE */ + default: + return false; + } /* switch _type */ + + return true; +} + +/** + \brief Accessor to the name attribute. + \return A string containing the name of the dependency. + + Returns the name of the dependency as found in the configuration file. + +*/ +const gchar* Dependency::get_name() +{ + return _string; +} + +/** + \brief Path of this dependency + \return Absolute path to the dependency file + (or an empty string if dependency was not found or is of TYPE_EXTENSION) + + Returns the verified absolute path of the dependency file. + This value is only available after checking the Dependency by calling Dependency::check(). +*/ +std::string Dependency::get_path() +{ + if (_type == TYPE_EXTENSION) { + g_warning("Requested absolute path of dependency '%s' which is of 'extension' type.", _string); + return ""; + } + if (_absolute_location == UNCHECKED) { + g_warning("Requested absolute path of dependency '%s' which is unchecked.", _string); + return ""; + } + + return _absolute_location; +} + +/** + \brief Print out a dependency to a string. +*/ +Glib::ustring Dependency::info_string() +{ + Glib::ustring str = Glib::ustring::compose("%1:\n\t%2: %3\n\t%4: %5\n\t%6: %7", + _("Dependency"), + _("type"), _(_type_str[_type]), + _("location"), _(_location_str[_location]), + _("string"), _string); + + if (_description) { + str += Glib::ustring::compose("\n\t%1: %2\n", _(" description: "), _(_description)); + } + + return str; +} + +} } /* namespace Inkscape, Extension */ + +/* + 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/extension/dependency.h b/src/extension/dependency.h new file mode 100644 index 0000000..e2c2791 --- /dev/null +++ b/src/extension/dependency.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_DEPENDENCY_H__ +#define INKSCAPE_EXTENSION_DEPENDENCY_H__ + +#include <glibmm/ustring.h> +#include "xml/repr.h" + +namespace Inkscape { +namespace Extension { + +class Extension; + +/** \brief A class to represent a dependency for an extension. There + are different things that can be done in a dependency, and + this class takes care of all of them. */ +class Dependency { + +public: + /** \brief All the possible types of dependencies. */ + enum type_t { + TYPE_EXECUTABLE, /**< Look for an executable */ + TYPE_FILE, /**< Look to make sure a file exists */ + TYPE_EXTENSION, /**< Make sure a specific extension is loaded and functional */ + TYPE_CNT /**< Number of types */ + }; + + /** \brief All of the possible locations to look for the dependency. */ + enum location_t { + LOCATION_PATH, /**< Look in the PATH for this dependency - historically this is the default + (it's a bit odd for interpreted script files but makes sense for other executables) */ + LOCATION_EXTENSIONS, /**< Look in the extensions directory + (note: this can be in both, user and system locations!) */ + LOCATION_INX, /**< Look relative to the inx file's location */ + LOCATION_ABSOLUTE, /**< This dependency is already defined in absolute terms */ + LOCATION_CNT /**< Number of locations to look */ + }; + +private: + static constexpr const char *UNCHECKED = "---unchecked---"; + + /** \brief The XML representation of the dependency. */ + Inkscape::XML::Node * _repr; + /** \brief The string that is in the XML tags pulled out. */ + const gchar * _string = nullptr; + /** \brief The description of the dependency for the users. */ + const gchar * _description = nullptr; + /** \brief The absolute path to the dependency file determined while checking this dependency. */ + std::string _absolute_location = UNCHECKED; + + /** \brief Storing the type of this particular dependency. */ + type_t _type = TYPE_FILE; + /** \brief The location to look for this particular dependency. */ + location_t _location = LOCATION_PATH; + + /** \brief Strings to represent the different enum values in + \c type_t in the XML */ + static gchar const * _type_str[TYPE_CNT]; + /** \brief Strings to represent the different enum values in + \c location_t in the XML */ + static gchar const * _location_str[LOCATION_CNT]; + + /** \brief Reference to the extension requesting this dependency. */ + const Extension *_extension; + +public: + Dependency (Inkscape::XML::Node *in_repr, const Extension *extension, type_t type=TYPE_FILE); + virtual ~Dependency (); + bool check(); + const gchar* get_name(); + std::string get_path(); + + Glib::ustring info_string(); +}; /* class Dependency */ + + +} } /* namespace Extension, Inkscape */ + +#endif /* INKSCAPE_EXTENSION_DEPENDENCY_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/extension/effect.cpp b/src/extension/effect.cpp new file mode 100644 index 0000000..1a39d4b --- /dev/null +++ b/src/extension/effect.cpp @@ -0,0 +1,393 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * Abhishek Sharma + * + * Copyright (C) 2002-2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "effect.h" + +#include "execution-env.h" +#include "inkscape.h" +#include "timer.h" + +#include "helper/action.h" +#include "implementation/implementation.h" +#include "prefdialog/prefdialog.h" +#include "ui/view/view.h" + + + +/* Inkscape::Extension::Effect */ + +namespace Inkscape { +namespace Extension { + +Effect * Effect::_last_effect = nullptr; +Inkscape::XML::Node * Effect::_effects_list = nullptr; +Inkscape::XML::Node * Effect::_filters_list = nullptr; + +#define EFFECTS_LIST "effects-list" +#define FILTERS_LIST "filters-list" + +Effect::Effect (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory) + : Extension(in_repr, in_imp, base_directory) + , _id_noprefs(Glib::ustring(get_id()) + ".noprefs") + , _name_noprefs(Glib::ustring(_(get_name())) + _(" (No preferences)")) + , _verb(get_id(), get_name(), nullptr, nullptr, this, true) + , _verb_nopref(_id_noprefs.c_str(), _name_noprefs.c_str(), nullptr, nullptr, this, false) + , _menu_node(nullptr), _workingDialog(true) + , _prefDialog(nullptr) +{ + Inkscape::XML::Node * local_effects_menu = nullptr; + + // This is a weird hack + if (!strcmp(this->get_id(), "org.inkscape.filter.dropshadow")) + return; + + bool hidden = false; + + no_doc = false; + no_live_preview = false; + + if (repr != nullptr) { + + for (Inkscape::XML::Node *child = repr->firstChild(); child != nullptr; child = child->next()) { + if (!strcmp(child->name(), INKSCAPE_EXTENSION_NS "effect")) { + if (child->attribute("needs-document") && !strcmp(child->attribute("needs-document"), "false")) { + no_doc = true; + } + if (child->attribute("needs-live-preview") && !strcmp(child->attribute("needs-live-preview"), "false")) { + no_live_preview = true; + } + if (child->attribute("implements-custom-gui") && !strcmp(child->attribute("implements-custom-gui"), "true")) { + _workingDialog = false; + } + for (Inkscape::XML::Node *effect_child = child->firstChild(); effect_child != nullptr; effect_child = effect_child->next()) { + if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "effects-menu")) { + // printf("Found local effects menu in %s\n", this->get_name()); + local_effects_menu = effect_child->firstChild(); + if (effect_child->attribute("hidden") && !strcmp(effect_child->attribute("hidden"), "true")) { + hidden = true; + } + } + if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "menu-name") || + !strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "_menu-name")) { + // printf("Found local effects menu in %s\n", this->get_name()); + _verb.set_name(effect_child->firstChild()->content()); + } + if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "menu-tip") || + !strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "_menu-tip")) { + // printf("Found local effects menu in %s\n", this->get_name()); + _verb.set_tip(effect_child->firstChild()->content()); + } + } // children of "effect" + break; // there can only be one effect + } // find "effect" + } // children of "inkscape-extension" + } // if we have an XML file + + // \TODO this gets called from the Inkscape::Application constructor, where it initializes the menus. + // But in the constructor, our object isn't quite there yet! + if (Inkscape::Application::exists() && INKSCAPE.use_gui()) { + if (_effects_list == nullptr) + _effects_list = find_menu(INKSCAPE.get_menus(), EFFECTS_LIST); + if (_filters_list == nullptr) + _filters_list = find_menu(INKSCAPE.get_menus(), FILTERS_LIST); + } + + if ((_effects_list != nullptr || _filters_list != nullptr)) { + Inkscape::XML::Document *xml_doc; + xml_doc = _effects_list->document(); + _menu_node = xml_doc->createElement("verb"); + _menu_node->setAttribute("verb-id", this->get_id()); + + if (!hidden) { + if (_filters_list && + local_effects_menu && + local_effects_menu->attribute("name") && + !strcmp(local_effects_menu->attribute("name"), ("Filters"))) { + merge_menu(_filters_list->parent(), _filters_list, local_effects_menu->firstChild(), _menu_node); + } else if (_effects_list) { + merge_menu(_effects_list->parent(), _effects_list, local_effects_menu, _menu_node); + } + } + } + + return; +} + +void +Effect::merge_menu (Inkscape::XML::Node * base, + Inkscape::XML::Node * start, + Inkscape::XML::Node * pattern, + Inkscape::XML::Node * merge) { + Glib::ustring mergename; + Inkscape::XML::Node * tomerge = nullptr; + Inkscape::XML::Node * submenu = nullptr; + + if (pattern == nullptr) { + // Merge the verb name + tomerge = merge; + mergename = get_translation(get_name()); + } else { + gchar const *menuname = pattern->attribute("name"); + if (menuname == nullptr) menuname = pattern->attribute("_name"); + if (menuname == nullptr) return; + + Inkscape::XML::Document *xml_doc; + xml_doc = base->document(); + tomerge = xml_doc->createElement("submenu"); + if (_translation_enabled) { + mergename = get_translation(menuname); + } else { + // Even if the extension author requested the extension not to be translated, + // it still seems desirable to be able to put the extension into the existing (translated) submenus. + mergename = _(menuname); + } + tomerge->setAttribute("name", mergename); + } + + int position = -1; + + if (start != nullptr) { + Inkscape::XML::Node * menupass; + for (menupass = start; menupass != nullptr && strcmp(menupass->name(), "separator"); menupass = menupass->next()) { + gchar const * compare_char = nullptr; + if (!strcmp(menupass->name(), "verb")) { + gchar const * verbid = menupass->attribute("verb-id"); + Inkscape::Verb * verb = Inkscape::Verb::getbyid(verbid); + if (verb == nullptr) { + g_warning("Unable to find verb '%s' which is referred to in the menus.", verbid); + continue; + } + compare_char = verb->get_name(); + } else if (!strcmp(menupass->name(), "submenu")) { + compare_char = menupass->attribute("name"); + if (compare_char == nullptr) + compare_char = menupass->attribute("_name"); + } + + position = menupass->position() + 1; + + /* This will cause us to skip tags we don't understand */ + if (compare_char == nullptr) { + continue; + } + + Glib::ustring compare(_(compare_char)); + + if (mergename == compare) { + Inkscape::GC::release(tomerge); + tomerge = nullptr; + submenu = menupass; + break; + } + + if (mergename < compare) { + position = menupass->position(); + break; + } + } // for menu items + } // start != NULL + + if (tomerge != nullptr) { + if (position != -1) { + base->addChildAtPos(tomerge, position); + } else { + base->appendChild(tomerge); + } + Inkscape::GC::release(tomerge); + } + + if (pattern != nullptr) { + if (submenu == nullptr) + submenu = tomerge; + merge_menu(submenu, submenu->firstChild(), pattern->firstChild(), merge); + } + + return; +} + +Effect::~Effect () +{ + if (get_last_effect() == this) + set_last_effect(nullptr); + if (_menu_node) + Inkscape::GC::release(_menu_node); + return; +} + +bool +Effect::check () +{ + if (!Extension::check()) { + _verb.sensitive(nullptr, false); + _verb.set_tip(Extension::getErrorReason().c_str()); // TODO: insensitive menuitems don't show a tooltip + return false; + } + return true; +} + +bool +Effect::prefs (Inkscape::UI::View::View * doc) +{ + if (_prefDialog != nullptr) { + _prefDialog->raise(); + return true; + } + + if (widget_visible_count() == 0) { + effect(doc); + return true; + } + + if (!loaded()) + set_state(Extension::STATE_LOADED); + if (!loaded()) return false; + + Glib::ustring name = get_translation(this->get_name()); + _prefDialog = new PrefDialog(name, nullptr, this); + _prefDialog->show(); + + return true; +} + +/** + \brief The function that 'does' the effect itself + \param doc The Inkscape::UI::View::View to do the effect on + + This function first insures that the extension is loaded, and if not, + loads it. It then calls the implementation to do the actual work. It + also resets the last effect pointer to be this effect. Finally, it + executes a \c SPDocumentUndo::done to commit the changes to the undo + stack. +*/ +void +Effect::effect (Inkscape::UI::View::View * doc) +{ + //printf("Execute effect\n"); + if (!loaded()) + set_state(Extension::STATE_LOADED); + if (!loaded()) return; + ExecutionEnv executionEnv(this, doc, nullptr, _workingDialog, true); + execution_env = &executionEnv; + timer->lock(); + executionEnv.run(); + if (executionEnv.wait()) { + executionEnv.commit(); + } else { + executionEnv.cancel(); + } + timer->unlock(); + + return; +} + +/** \brief Sets which effect was called last + \param in_effect The effect that has been called + + This function sets the static variable \c _last_effect and it + ensures that the last effect verb is sensitive. + + If the \c in_effect variable is \c NULL then the last effect + verb is made insesitive. +*/ +void +Effect::set_last_effect (Effect * in_effect) +{ + if (in_effect == nullptr) { + Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(nullptr, false); + Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(nullptr, false); + } else if (_last_effect == nullptr) { + Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(nullptr, true); + Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(nullptr, true); + } + + _last_effect = in_effect; + return; +} + +Inkscape::XML::Node * +Effect::find_menu (Inkscape::XML::Node * menustruct, const gchar *name) +{ + if (menustruct == nullptr) return nullptr; + for (Inkscape::XML::Node * child = menustruct; + child != nullptr; + child = child->next()) { + if (!strcmp(child->name(), name)) { + return child; + } + Inkscape::XML::Node * firstchild = child->firstChild(); + if (firstchild != nullptr) { + Inkscape::XML::Node *found = find_menu (firstchild, name); + if (found) + return found; + } + } + return nullptr; +} + + +Gtk::VBox * +Effect::get_info_widget() +{ + return Extension::get_info_widget(); +} + +PrefDialog * +Effect::get_pref_dialog () +{ + return _prefDialog; +} + +void +Effect::set_pref_dialog (PrefDialog * prefdialog) +{ + _prefDialog = prefdialog; + return; +} + +SPAction * +Effect::EffectVerb::make_action (Inkscape::ActionContext const & context) +{ + return make_action_helper(context, &perform, static_cast<void *>(this)); +} + +/** \brief Decode the verb code and take appropriate action */ +void +Effect::EffectVerb::perform( SPAction *action, void * data ) +{ + g_return_if_fail(ensure_desktop_valid(action)); + Inkscape::UI::View::View * current_view = sp_action_get_view(action); + + Effect::EffectVerb * ev = reinterpret_cast<Effect::EffectVerb *>(data); + Effect * effect = ev->_effect; + + if (effect == nullptr) return; + + if (ev->_showPrefs) { + effect->prefs(current_view); + } else { + effect->effect(current_view); + } + + return; +} + +} } /* namespace Inkscape, Extension */ + +/* + 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/extension/effect.h b/src/extension/effect.h new file mode 100644 index 0000000..39307e4 --- /dev/null +++ b/src/extension/effect.h @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +#ifndef INKSCAPE_EXTENSION_EFFECT_H__ +#define INKSCAPE_EXTENSION_EFFECT_H__ + +#include <glibmm/i18n.h> +#include "verbs.h" +#include "extension.h" + +namespace Gtk { + class VBox; +} + +class SPDocument; + +namespace Inkscape { + + +namespace Extension { +class PrefDialog; + +/** \brief Effects are extensions that take a document and do something + to it in place. This class adds the extra functions required + to make extensions effects. +*/ +class Effect : public Extension { + /** \brief This is the last effect that was used. This is used in + a menu item to rapidly recall the same effect. */ + static Effect * _last_effect; + /** \brief The location of the Extensions and Filters menus on the menu structure + XML file. This is saved so it only has to be discovered + once. */ + static Inkscape::XML::Node * _effects_list; + static Inkscape::XML::Node * _filters_list; + Inkscape::XML::Node *find_menu (Inkscape::XML::Node * menustruct, const gchar *name); + void merge_menu (Inkscape::XML::Node * base, Inkscape::XML::Node * start, Inkscape::XML::Node * pattern, Inkscape::XML::Node * merge); + + /** \brief This is the verb type that is used for all effect's verbs. + It provides convenience functions and maintains a pointer + back to the effect that created it. */ + class EffectVerb : public Inkscape::Verb { + private: + static void perform (SPAction * action, void * mydata); + + /** \brief The effect that this verb represents. */ + Effect * _effect; + /** \brief Whether or not to show preferences on display */ + bool _showPrefs; + /** \brief Name with ellipses if that makes sense */ + gchar * _elip_name; + protected: + SPAction * make_action (Inkscape::ActionContext const & context) override; + public: + /** \brief Use the Verb initializer with the same parameters. */ + EffectVerb(gchar const * id, + gchar const * name, + gchar const * tip, + gchar const * image, + Effect * effect, + bool showPrefs) : + Verb(id, _(name), tip ? _(tip) : nullptr, image, _("Extensions")), + _effect(effect), + _showPrefs(showPrefs), + _elip_name(nullptr) { + /* No clue why, but this is required */ + this->set_default_sensitive(true); + if (_showPrefs && effect != nullptr && effect->widget_visible_count() != 0) { + _elip_name = g_strdup_printf("%s...", _(name)); + set_name(_elip_name); + } + } + + /** \brief Destructor */ + ~EffectVerb() override { + if (_elip_name != nullptr) { + g_free(_elip_name); + } + } + }; + + /** \brief ID used for the verb without preferences */ + Glib::ustring _id_noprefs; + /** \brief Name used for the verb without preferences */ + Glib::ustring _name_noprefs; + + /** \brief The verb representing this effect. */ + EffectVerb _verb; + /** \brief The verb representing this effect. Without preferences. */ + EffectVerb _verb_nopref; + /** \brief Menu node created for this effect */ + Inkscape::XML::Node * _menu_node; + /** \brief Whether a working dialog should be shown */ + bool _workingDialog; + + /** \brief The preference dialog if it is shown */ + PrefDialog * _prefDialog; +public: + Effect(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory); + ~Effect () override; + + bool check() override; + + bool prefs (Inkscape::UI::View::View * doc); + void effect (Inkscape::UI::View::View * doc); + /** \brief Accessor function for a pointer to the verb */ + Inkscape::Verb * get_verb () { return &_verb; }; + + /** \brief Static function to get the last effect used */ + static Effect * get_last_effect () { return _last_effect; }; + static void set_last_effect (Effect * in_effect); + + static void place_menus (); + void place_menu (Inkscape::XML::Node * menus); + + Gtk::VBox * get_info_widget(); + + bool no_doc; // if true, the effect does not process SVG document at all, so no need to save, read, and watch for errors + bool no_live_preview; // if true, the effect does not need "live preview" checkbox in its dialog + + PrefDialog *get_pref_dialog (); + void set_pref_dialog (PrefDialog * prefdialog); +private: + static gchar * remove_ (gchar * instr); +}; + +} } /* namespace Inkscape, Extension */ +#endif /* INKSCAPE_EXTENSION_EFFECT_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/extension/error-file.cpp b/src/extension/error-file.cpp new file mode 100644 index 0000000..ad65457 --- /dev/null +++ b/src/extension/error-file.cpp @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/dialog/extensions.h" + +#include <glibmm/i18n.h> +#include "inkscape.h" +#include "preferences.h" +#include "extension/extension.h" +#include "io/resource.h" + +#include "error-file.h" + +/** The name and group of the preference to say whether the error + dialog should be shown on startup. */ +#define PREFERENCE_ID "/dialogs/extension-error/show-on-startup" + +namespace Inkscape { +namespace Extension { + +/** \brief An initializer which builds the dialog + + Really a simple function. Basically the message dialog itself gets + built with the first initializer. The next step is to add in the + message, and attach the filename for the error file. After that + the checkbox is built, and has the call back attached to it. Also, + it is set based on the preferences setting for show on startup (really, + it should always be checked if you can see the dialog, but it is + probably good to check anyway). +*/ +ErrorFileNotice::ErrorFileNotice () : + Gtk::MessageDialog( + "", /* message */ + false, /* use markup */ + Gtk::MESSAGE_WARNING, /* dialog type */ + Gtk::BUTTONS_OK, /* buttons */ + true /* modal */ + ) + +{ + // \FIXME change this + /* This is some filler text, needs to change before release */ + Glib::ustring dialog_text(_("<span weight=\"bold\" size=\"larger\">One or more extensions failed to load</span>\n\nThe failed extensions have been skipped. Inkscape will continue to run normally but those extensions will be unavailable. For details to troubleshoot this problem, please refer to the error log located at: ")); + gchar * ext_error_file = Inkscape::IO::Resource::log_path(EXTENSION_ERROR_LOG_FILENAME); + dialog_text += ext_error_file; + g_free(ext_error_file); + set_message(dialog_text, true); + + auto vbox = get_content_area(); + + /* This is some filler text, needs to change before release */ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + checkbutton = Gtk::manage(new Gtk::CheckButton(_("Show dialog on startup"))); + vbox->pack_start(*checkbutton, true, false, 5); + checkbutton->show(); + checkbutton->set_active(prefs->getBool(PREFERENCE_ID, true)); + + checkbutton->signal_toggled().connect(sigc::mem_fun(this, &ErrorFileNotice::checkbox_toggle)); + + set_resizable(true); + + Inkscape::UI::Dialogs::ExtensionsPanel* extens = new Inkscape::UI::Dialogs::ExtensionsPanel(); + extens->set_full(false); + vbox->pack_start( *extens, true, true ); + extens->show(); + + return; +} + +/** \brief Sets the preferences based on the checkbox value */ +void +ErrorFileNotice::checkbox_toggle () +{ + // std::cout << "Toggle value" << std::endl; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(PREFERENCE_ID, checkbutton->get_active()); +} + +/** \brief Shows the dialog + + This function only shows the dialog if the preferences say that the + user wants to see the dialog, otherwise it just exits. +*/ +int +ErrorFileNotice::run () +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + if (!prefs->getBool(PREFERENCE_ID, true)) + return 0; + return Gtk::Dialog::run(); +} + +}; }; /* namespace Inkscape, Extension */ + +/* + 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/extension/error-file.h b/src/extension/error-file.h new file mode 100644 index 0000000..442bc8f --- /dev/null +++ b/src/extension/error-file.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_ERROR_FILE_H__ +#define INKSCAPE_EXTENSION_ERROR_FILE_H__ + +#include <gtkmm/messagedialog.h> +#include <gtkmm/checkbutton.h> + +namespace Inkscape { +namespace Extension { + +/** \brief A warning dialog to say that some extensions failed to load, + will not run if the preference controlling running is turned + off. */ +class ErrorFileNotice : public Gtk::MessageDialog { + /** The checkbutton, this is so we can figure out when it gets checked */ + Gtk::CheckButton * checkbutton; + + void checkbox_toggle(); +public: + ErrorFileNotice (); + int run (); +}; + +}; }; /* namespace Inkscape, Extension */ + +#endif /* INKSCAPE_EXTENSION_ERROR_FILE_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/extension/execution-env.cpp b/src/extension/execution-env.cpp new file mode 100644 index 0000000..9293e56 --- /dev/null +++ b/src/extension/execution-env.cpp @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * Abhishek Sharma + * + * Copyright (C) 2007-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/dialog.h> +#include <gtkmm/messagedialog.h> + +#include "execution-env.h" +#include "prefdialog/prefdialog.h" +#include "implementation/implementation.h" + +#include "selection.h" +#include "effect.h" +#include "document.h" +#include "desktop.h" +#include "inkscape.h" +#include "document-undo.h" +#include "desktop.h" +#include "object/sp-namedview.h" + +#include "display/sp-canvas.h" + +namespace Inkscape { +namespace Extension { + +/** \brief Create an execution environment that will allow the effect + to execute independently. + \param effect The effect that we should execute + \param doc The Document to execute on + \param docCache The cache created for that document + \param show_working Show the working dialog + \param show_error Show the error dialog (not working) + + Grabs the selection of the current document so that it can get + restored. Will generate a document cache if one isn't provided. +*/ +ExecutionEnv::ExecutionEnv (Effect * effect, Inkscape::UI::View::View * doc, Implementation::ImplementationDocumentCache * docCache, bool show_working, bool show_errors) : + _state(ExecutionEnv::INIT), + _visibleDialog(nullptr), + _mainloop(nullptr), + _doc(doc), + _docCache(docCache), + _effect(effect), + _show_working(show_working), + _show_errors(show_errors) +{ + genDocCache(); + + return; +} + +/** \brief Destroy an execution environment + + Destroys the dialog if created and the document cache. +*/ +ExecutionEnv::~ExecutionEnv () { + if (_visibleDialog != nullptr) { + _visibleDialog->hide(); + delete _visibleDialog; + _visibleDialog = nullptr; + } + killDocCache(); + return; +} + +/** \brief Generate a document cache if needed + + If there isn't one we create a new one from the implementation + from the effect's implementation. +*/ +void +ExecutionEnv::genDocCache () { + if (_docCache == nullptr) { + // printf("Gen Doc Cache\n"); + _docCache = _effect->get_imp()->newDocCache(_effect, _doc); + } + return; +} + +/** \brief Destroy a document cache + + Just delete it. +*/ +void +ExecutionEnv::killDocCache () { + if (_docCache != nullptr) { + // printf("Killed Doc Cache\n"); + delete _docCache; + _docCache = nullptr; + } + return; +} + +/** \brief Create the working dialog + + Builds the dialog with a message saying that the effect is working. + And make sure to connect to the cancel. +*/ +void +ExecutionEnv::createWorkingDialog () { + if (_visibleDialog != nullptr) { + _visibleDialog->hide(); + delete _visibleDialog; + _visibleDialog = nullptr; + } + + SPDesktop *desktop = (SPDesktop *)_doc; + GtkWidget *toplevel = gtk_widget_get_toplevel(GTK_WIDGET(desktop->canvas)); + if (!toplevel || !gtk_widget_is_toplevel (toplevel)) + return; + Gtk::Window *window = Glib::wrap(GTK_WINDOW(toplevel), false); + + gchar * dlgmessage = g_strdup_printf(_("'%s' working, please wait..."), _(_effect->get_name())); + _visibleDialog = new Gtk::MessageDialog(*window, + dlgmessage, + false, // use markup + Gtk::MESSAGE_INFO, + Gtk::BUTTONS_CANCEL, + true); // modal + _visibleDialog->signal_response().connect(sigc::mem_fun(this, &ExecutionEnv::workingCanceled)); + g_free(dlgmessage); + + Gtk::Dialog *dlg = _effect->get_pref_dialog(); + if (dlg) { + _visibleDialog->set_transient_for(*dlg); + } else { + // ToDo: Do we need to make the window transient for the main window here? + // Currently imossible to test because of GUI freezing during save, + // see https://bugs.launchpad.net/inkscape/+bug/967416 + } + _visibleDialog->show_now(); + + return; +} + +void +ExecutionEnv::workingCanceled( const int /*resp*/) { + cancel(); + undo(); + return; +} + +void +ExecutionEnv::cancel () { + SPDesktop *desktop = (SPDesktop *)_doc; + desktop->clearWaitingCursor(); + _effect->get_imp()->cancelProcessing(); + return; +} + +void +ExecutionEnv::undo () { + DocumentUndo::cancel(_doc->doc()); + return; +} + +void +ExecutionEnv::commit () { + DocumentUndo::done(_doc->doc(), SP_VERB_NONE, _(_effect->get_name())); + Effect::set_last_effect(_effect); + _effect->get_imp()->commitDocument(); + killDocCache(); + return; +} + +void +ExecutionEnv::reselect () { + SPDesktop *desktop = SP_ACTIVE_DESKTOP; + if(desktop) { + Inkscape::Selection *selection = desktop->getSelection(); + if (selection) { + selection->restoreBackup(); + } + } + return; +} + +void +ExecutionEnv::run () { + _state = ExecutionEnv::RUNNING; + if (_show_working) { + createWorkingDialog(); + } + SPDesktop *desktop = (SPDesktop *)_doc; + Inkscape::Selection *selection = desktop->getSelection(); + selection->setBackup(); + desktop->setWaitingCursor(); + _effect->get_imp()->effect(_effect, _doc, _docCache); + desktop->clearWaitingCursor(); + _state = ExecutionEnv::COMPLETE; + selection->restoreBackup(); + // _runComplete.signal(); + return; +} + +void +ExecutionEnv::runComplete () { + _mainloop->quit(); +} + +bool +ExecutionEnv::wait () { + if (_state != ExecutionEnv::COMPLETE) { + if (_mainloop) { + _mainloop = Glib::MainLoop::create(false); + } + + sigc::connection conn = _runComplete.connect(sigc::mem_fun(this, &ExecutionEnv::runComplete)); + _mainloop->run(); + + conn.disconnect(); + } + + return true; +} + + + +} } /* namespace Inkscape, Extension */ + + + +/* + 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/extension/execution-env.h b/src/extension/execution-env.h new file mode 100644 index 0000000..15d2cce --- /dev/null +++ b/src/extension/execution-env.h @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_EXECUTION_ENV_H__ +#define INKSCAPE_EXTENSION_EXECUTION_ENV_H__ + +#include <glibmm/main.h> +#include <glibmm/ustring.h> + +#include <gtkmm/dialog.h> + +namespace Inkscape { + +namespace UI { +namespace View { +class View; +} // namespace View +} // namespace UI + +namespace Extension { + +class Effect; + +namespace Implementation +{ +class ImplementationDocumentCache; +} + +class ExecutionEnv { +private: + enum state_t { + INIT, //< The context has been initialized + COMPLETE, //< We've completed atleast once + RUNNING //< The effect is currently running + }; + /** \brief What state the execution engine is in. */ + state_t _state; + + /** \brief If there is a working dialog it'll be referenced + right here. */ + Gtk::Dialog * _visibleDialog; + /** \brief Signal that the run is complete. */ + sigc::signal<void> _runComplete; + /** \brief In some cases we need a mainLoop, when we do, this is + a pointer to it. */ + Glib::RefPtr<Glib::MainLoop> _mainloop; + /** \brief The document that we're working on. */ + Inkscape::UI::View::View * _doc; + /** \brief A document cache if we were passed one. */ + Implementation::ImplementationDocumentCache * _docCache; + + /** \brief The effect that we're executing in this context. */ + Effect * _effect; + + /** \brief Show the working dialog when the effect is executing. */ + bool _show_working; + /** \brief Display errors if they occur. */ + bool _show_errors; +public: + + /** \brief Create a new context for execution of an effect + \param effect The effect to execute + \param doc The document to execute the effect on + \param docCache The implementation cache of the document. May be + NULL in which case it'll be created by the execution + environment. + \prarm show_working Show a small dialog signaling the effect + is working. Allows for user canceling. + \param show_errors If the effect has an error, show it or not. + */ + ExecutionEnv (Effect * effect, + Inkscape::UI::View::View * doc, + Implementation::ImplementationDocumentCache * docCache = nullptr, + bool show_working = true, + bool show_errors = true); + virtual ~ExecutionEnv (); + + /** \brief Starts the execution of the effect + \return Returns whether the effect was executed to completion */ + void run (); + /** \brief Cancel the execution of the effect */ + void cancel (); + /** \brief Commit the changes to the document */ + void commit (); + /** \brief Undoes what the effect completed. */ + void undo (); + /** \brief Wait for the effect to complete if it hasn't. */ + bool wait (); + void reselect (); + + /** \brief Return reference to working dialog (if any) */ + Gtk::Dialog *get_working_dialog () { return _visibleDialog; }; + +private: + void runComplete (); + void createWorkingDialog (); + void workingCanceled (const int resp); + void genDocCache (); + void killDocCache (); +}; + +} } /* namespace Inkscape, Extension */ +#endif /* INKSCAPE_EXTENSION_EXECUTION_ENV_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/extension/extension.cpp b/src/extension/extension.cpp new file mode 100644 index 0000000..6d182af --- /dev/null +++ b/src/extension/extension.cpp @@ -0,0 +1,1014 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * + * Inkscape::Extension::Extension: + * the ability to have features that are more modular so that they + * can be added and removed easily. This is the basis for defining + * those actions. + */ + +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension.h" +#include "implementation/implementation.h" + +#include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> + +#include <glib/gstdio.h> +#include <glib/gprintf.h> + +#include <glibmm/i18n.h> +#include <gtkmm/box.h> +#include <gtkmm/frame.h> +#include <gtkmm/grid.h> +#include <gtkmm/label.h> + +#include "db.h" +#include "dependency.h" +#include "inkscape.h" +#include "timer.h" + +#include "io/resource.h" +#include "io/sys.h" + +#include "prefdialog/parameter.h" +#include "prefdialog/widget.h" + +#include "xml/repr.h" + + +namespace Inkscape { +namespace Extension { + +/* Inkscape::Extension::Extension */ + +FILE *Extension::error_file = nullptr; + +/** + \return none + \brief Constructs an Extension from a Inkscape::XML::Node + \param in_repr The repr that should be used to build it + \param base_directory Base directory of extensions that were loaded from a file (.inx file's location) + + This function is the basis of building an extension for Inkscape. It + currently extracts the fields from the Repr that are used in the + extension. The Repr will likely include other children that are + not related to the module directly. If the Repr does not include + a name and an ID the module will be left in an errored state. +*/ +Extension::Extension(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory) + : _gui(true) + , execution_env(nullptr) +{ + g_return_if_fail(in_repr); // should be ensured in system.cpp + repr = in_repr; + Inkscape::GC::anchor(repr); + + if (in_imp == nullptr) { + imp = new Implementation::Implementation(); + } else { + imp = in_imp; + } + + if (base_directory) { + _base_directory = *base_directory; + } + + // get name of the translation catalog ("gettext textdomain") that the extension wants to use for translations + // and lookup the locale directory for it + const char *translationdomain = repr->attribute("translationdomain"); + if (translationdomain) { + _translationdomain = translationdomain; + } else { + _translationdomain = "inkscape"; // default to the Inkscape catalog + } + if (!strcmp(_translationdomain, "none")) { + // special keyword "none" means the extension author does not want translation of extension strings + _translation_enabled = false; + _translationdomain = nullptr; + } else if (!strcmp(_translationdomain, "inkscape")) { + // this is our default domain; we know the location already (also respects INKSCAPE_LOCALEDIR) + _gettext_catalog_dir = bindtextdomain("inkscape", nullptr); + } else { + lookup_translation_catalog(); + } + + // Read XML tree and parse extension + Inkscape::XML::Node *child_repr = repr->firstChild(); + while (child_repr) { + const char *chname = child_repr->name(); + if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + chname += strlen(INKSCAPE_EXTENSION_NS); + } + if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility + chname++; + } + + if (!strcmp(chname, "id")) { + const char *id = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr; + if (id) { + _id = g_strdup(id); + } else { + throw extension_no_id(); + } + } else if (!strcmp(chname, "name")) { + const char *name = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr; + if (name) { + _name = g_strdup(name); + } else { + throw extension_no_name(); + } + } else if (InxWidget::is_valid_widget_name(chname)) { + InxWidget *widget = InxWidget::make(child_repr, this); + if (widget) { + _widgets.push_back(widget); + } + } else if (!strcmp(chname, "dependency")) { + _deps.push_back(new Dependency(child_repr, this)); + } else if (!strcmp(chname, "script")) { // TODO: should these be parsed in their respective Implementation? + for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) { + if (child->type() == Inkscape::XML::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200) + const char *interpreted = child->attribute("interpreter"); + Dependency::type_t type = interpreted ? Dependency::TYPE_FILE : Dependency::TYPE_EXECUTABLE; + _deps.push_back(new Dependency(child, this, type)); + break; + } + } + } else if (!strcmp(chname, "xslt")) { // TODO: should these be parsed in their respective Implementation? + for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) { + if (child->type() == Inkscape::XML::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200) + _deps.push_back(new Dependency(child, this, Dependency::TYPE_FILE)); + break; + } + } + } else { + // We could do some sanity checking here. + // However, we don't really know which additional elements Extension subclasses might need... + } + + child_repr = child_repr->next(); + } + + // all extensions need an ID and a name + if (!_id) { + throw extension_no_id(); + } + if (!_name) { + throw extension_no_name(); + } + + // filter out extensions that are not compatible with the current platform +#ifndef _WIN32 + if (strstr(_id, "win32")) { + throw extension_not_compatible(); + } +#endif + + // finally register the extension if all checks passed + db.register_ext (this); +} + +/** + \return none + \brief Destroys the Extension + + This function frees all of the strings that could be attached + to the extension and also unreferences the repr. This is better + than freeing it because it may (I wouldn't know why) be referenced + in another place. +*/ +Extension::~Extension () +{ + set_state(STATE_UNLOADED); + + db.unregister_ext(this); + + Inkscape::GC::release(repr); + + g_free(_id); + g_free(_name); + + delete timer; + timer = nullptr; + + for (auto widget : _widgets) { + delete widget; + } + + for (auto & _dep : _deps) { + delete _dep; + } + _deps.clear(); +} + +/** + \return none + \brief A function to set whether the extension should be loaded + or unloaded + \param in_state Which state should the extension be in? + + It checks to see if this is a state change or not. If we're changing + states it will call the appropriate function in the implementation, + load or unload. Currently, there is no error checking in this + function. There should be. +*/ +void +Extension::set_state (state_t in_state) +{ + if (_state == STATE_DEACTIVATED) return; + if (in_state != _state) { + /** \todo Need some more error checking here! */ + switch (in_state) { + case STATE_LOADED: + if (imp->load(this)) + _state = STATE_LOADED; + + if (timer != nullptr) { + delete timer; + } + timer = new ExpirationTimer(this); + + break; + case STATE_UNLOADED: + // std::cout << "Unloading: " << name << std::endl; + imp->unload(this); + _state = STATE_UNLOADED; + + if (timer != nullptr) { + delete timer; + timer = nullptr; + } + break; + case STATE_DEACTIVATED: + _state = STATE_DEACTIVATED; + + if (timer != nullptr) { + delete timer; + timer = nullptr; + } + break; + default: + break; + } + } + + return; +} + +/** + \return The state the extension is in + \brief A getter for the state variable. +*/ +Extension::state_t +Extension::get_state () +{ + return _state; +} + +/** + \return Whether the extension is loaded or not + \brief A quick function to test the state of the extension +*/ +bool +Extension::loaded () +{ + return get_state() == STATE_LOADED; +} + +/** + \return A boolean saying whether the extension passed the checks + \brief A function to check the validity of the extension + + This function chekcs to make sure that there is an id, a name, a + repr and an implementation for this extension. Then it checks all + of the dependencies to see if they pass. Finally, it asks the + implementation to do a check of itself. + + On each check, if there is a failure, it will print a message to the + error log for that failure. It is important to note that the function + keeps executing if it finds an error, to try and get as many of them + into the error log as possible. This should help people debug + installations, and figure out what they need to get for the full + functionality of Inkscape to be available. +*/ +bool +Extension::check () +{ + const char * inx_failure = _(" This is caused by an improper .inx file for this extension." + " An improper .inx file could have been caused by a faulty installation of Inkscape."); + + if (repr == nullptr) { + printFailure(Glib::ustring(_("the XML description of it got lost.")) + inx_failure); + return false; + } + if (imp == nullptr) { + printFailure(Glib::ustring(_("no implementation was defined for the extension.")) + inx_failure); + return false; + } + + bool retval = true; + for (auto _dep : _deps) { + if (_dep->check() == false) { + printFailure(Glib::ustring(_("a dependency was not met."))); + error_file_write(_dep->info_string()); + retval = false; + } + } + + if (retval) { + return imp->check(this); + } + + error_file_write(""); + return retval; +} + +/** \brief A quick function to print out a standard start of extension + errors in the log. + \param reason A string explaining why this failed + + Real simple, just put everything into \c error_file. +*/ +void +Extension::printFailure (Glib::ustring reason) +{ + _error_reason = Glib::ustring::compose(_("Extension \"%1\" failed to load because %2"), _name, reason); + error_file_write(_error_reason); +} + +/** + \return The XML tree that is used to define the extension + \brief A getter for the internal Repr, does not add a reference. +*/ +Inkscape::XML::Node * +Extension::get_repr () +{ + return repr; +} + +/** + \return The textual id of this extension + \brief Get the ID of this extension - not a copy don't delete! +*/ +gchar * +Extension::get_id () const +{ + return _id; +} + +/** + \return The textual name of this extension + \brief Get the name of this extension - not a copy don't delete! +*/ +gchar * +Extension::get_name () const +{ + return _name; +} + +/** + \return None + \brief This function diactivates the extension (which makes it + unusable, but not deleted) + + This function is used to removed an extension from functioning, but + not delete it completely. It sets the state to \c STATE_DEACTIVATED to + mark to the world that it has been deactivated. It also removes + the current implementation and replaces it with a standard one. This + makes it so that we don't have to continually check if there is an + implementation, but we are guaranteed to have a benign one. + + \warning It is important to note that there is no 'activate' function. + Running this function is irreversable. +*/ +void +Extension::deactivate () +{ + set_state(STATE_DEACTIVATED); + + /* Removing the old implementation, and making this use the default. */ + /* This should save some memory */ + delete imp; + imp = new Implementation::Implementation(); + + return; +} + +/** + \return Whether the extension has been deactivated + \brief Find out the status of the extension +*/ +bool +Extension::deactivated () +{ + return get_state() == STATE_DEACTIVATED; +} + +/** Gets the location of the dependency file as an absolute path + * + * Iterates over all dependencies of this extension and finds the one with matching name, + * then returns the absolute path to this dependency file as determined previously. + * + * TODO: This function should not be necessary, but we parse script dependencies twice: + * - Once here in the Extension::Extension() constructor + * - A second time in Script::load() in "script.cpp" when determining the script location + * Theoretically we could return the wrong path if an extension depends on two files with the same name + * in different relative locations. In practice this risk should be close to zero, though. + * + * @return Absolute path of the dependency file + */ +std::string Extension::get_dependency_location(const char *name) +{ + for (auto dep : _deps) { + if (!strcmp(name, dep->get_name())) { + return dep->get_path(); + } + } + + return ""; +} + +/** recursively searches directory for a file named filename; returns true if found */ +static bool _find_filename_recursive(std::string directory, std::string filename) { + Glib::Dir dir(directory); + + std::string name = dir.read_name(); + while (!name.empty()) { + std::string fullpath = Glib::build_filename(directory, name); + // g_message("%s", fullpath.c_str()); + + if (Glib::file_test(fullpath, Glib::FILE_TEST_IS_DIR)) { + if (_find_filename_recursive(fullpath, filename)) { + return true; + } + } else if (name == filename) { + return true; + } + name = dir.read_name(); + } + + return false; +} + +/** Searches for a gettext catalog matching the extension's translationdomain + * + * This function will attempt to find the correct gettext catalog for the translationdomain + * requested by the extension. + * + * For this the following three locations are recursively searched for "${translationdomain}.mo": + * - the 'locale' directory in the .inx file's folder + * - the 'locale' directory in the "extensions" folder containing the .inx + * - the system location for gettext catalogs, i.e. where Inkscape's own catalog is located + * + * If one matching file is found, the directory is assumed to be the correct location and registered with gettext + */ +void Extension::lookup_translation_catalog() { + g_assert(!_base_directory.empty()); + + // get locale folder locations + std::string locale_dir_current_extension; + std::string locale_dir_extensions; + std::string locale_dir_system; + + locale_dir_current_extension = Glib::build_filename(_base_directory, "locale"); + + size_t index = _base_directory.find_last_of("extensions"); + if (index != std::string::npos) { + locale_dir_extensions = Glib::build_filename(_base_directory.substr(0, index+1), "locale"); + } + + locale_dir_system = bindtextdomain("inkscape", nullptr); + + // collect unique locations into vector + std::vector<std::string> locale_dirs; + if (locale_dir_current_extension != locale_dir_extensions) { + locale_dirs.push_back(std::move(locale_dir_current_extension)); + } + locale_dirs.push_back(std::move(locale_dir_extensions)); + locale_dirs.push_back(std::move(locale_dir_system)); + + // iterate over locations and look for the one that has the correct catalog + std::string search_name; + search_name += _translationdomain; + search_name += ".mo"; + for (auto locale_dir : locale_dirs) { + if (!Glib::file_test(locale_dir, Glib::FILE_TEST_IS_DIR)) { + continue; + } + + if (_find_filename_recursive(locale_dir, search_name)) { + _gettext_catalog_dir = locale_dir; + break; + } + } + + // register catalog with gettext if found, disable translation for this extension otherwise + if (!_gettext_catalog_dir.empty()) { + const char *current_dir = bindtextdomain(_translationdomain, nullptr); + if (_gettext_catalog_dir != current_dir) { + g_info("Binding textdomain '%s' to '%s'.", _translationdomain, _gettext_catalog_dir.c_str()); + bindtextdomain(_translationdomain, _gettext_catalog_dir.c_str()); + bind_textdomain_codeset(_translationdomain, "UTF-8"); + } + } else { + g_warning("Failed to locate message catalog for textdomain '%s'.", _translationdomain); + _translation_enabled = false; + _translationdomain = nullptr; + } +} + +/** Gets a translation within the context of the current extension + * + * Query gettext for the translated version of the input string, + * handling the preferred translation domain of the extension internally. + * + * @param msgid String to translate + * @param msgctxt Context for the translation + * + * @return Translated string (or original string if extension is not supposed to be translated) + */ +const char *Extension::get_translation(const char *msgid, const char *msgctxt) { + if (!_translation_enabled) { + return msgid; + } + + if (!strcmp(msgid, "")) { + g_warning("Attempting to translate an empty string in extension '%s', which is not supported.", _id); + return msgid; + } + + if (msgctxt) { + return g_dpgettext2(_translationdomain, msgctxt, msgid); + } else { + return g_dgettext(_translationdomain, msgid); + } +} + +/** Sets environment suitable for executing this Extension + * + * Currently sets the environment variables INKEX_GETTEXT_DOMAIN and INKEX_GETTEXT_DIRECTORY + * to make the "translationdomain" accessible to child processes spawned by this extension's Implementation. + */ +void Extension::set_environment() { + Glib::unsetenv("INKEX_GETTEXT_DOMAIN"); + Glib::unsetenv("INKEX_GETTEXT_DIRECTORY"); + + if (_translationdomain) { + Glib::setenv("INKEX_GETTEXT_DOMAIN", std::string(_translationdomain)); + } + if (!_gettext_catalog_dir.empty()) { + Glib::setenv("INKEX_GETTEXT_DIRECTORY", _gettext_catalog_dir); + } +} + +/** + \brief A function to get the parameters in a string form + \return An array with all the parameters in it. + +*/ +void +Extension::paramListString (std::list <std::string> &retlist) +{ + // first collect all widgets in the current extension + std::vector<InxWidget *> widget_list; + for (auto widget : _widgets) { + widget->get_widgets(widget_list); + } + + // then build a list of parameter strings from parameter names and values, as '--name=value' + for (auto widget : widget_list) { + InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets + if (parameter) { + const char *name = parameter->name(); + std::string value = parameter->value_to_string(); + + if (name && !value.empty()) { // TODO: Shouldn't empty string values be allowed? + std::string parameter_string; + parameter_string += "--"; + parameter_string += name; + parameter_string += "="; + parameter_string += value; + retlist.push_back(parameter_string); + } + } + } + + return; +} + +InxParameter *Extension::get_param(const gchar *name) +{ + if (!name || _widgets.empty()) { + throw Extension::param_not_exist(); + } + + // first collect all widgets in the current extension + std::vector<InxWidget *> widget_list; + for (auto widget : _widgets) { + widget->get_widgets(widget_list); + } + + // then search for a parameter with a matching name + for (auto widget : widget_list) { + InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets + if (parameter && !strcmp(parameter->name(), name)) { + return parameter; + } + } + + // if execution reaches here, no parameter matching 'name' was found + throw Extension::param_not_exist(); +} + +InxParameter const *Extension::get_param(const gchar *name) const +{ + return const_cast<Extension *>(this)->get_param(name); +} + + +/** + \return The value of the parameter identified by the name + \brief Gets a parameter identified by name with the bool placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +bool +Extension::get_param_bool(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_bool(); +} + +/** + \return The integer value for the parameter specified + \brief Gets a parameter identified by name with the integer placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +int +Extension::get_param_int(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_int(); +} + +/** + \return The float value for the parameter specified + \brief Gets a parameter identified by name with the float in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +float +Extension::get_param_float(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_float(); +} + +/** + \return The string value for the parameter specified + \brief Gets a parameter identified by name with the string placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::get_param_string(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_string(); +} + +/** + \return The string value for the parameter specified + \brief Gets a parameter identified by name with the string placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::get_param_optiongroup(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_optiongroup(); +} + +/** + * This is useful to find out, if a given string \c value is selectable in a optiongroup named \cname. + * + * @param name The name of the optiongroup parameter to get. + * @return true if value exists, false if not + */ +bool +Extension::get_param_optiongroup_contains(const gchar *name, const char *value) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_optiongroup_contains(value); +} + +/** + \return The unsigned integer RGBA value for the parameter specified + \brief Gets a parameter identified by name with the unsigned int placed in value. + \param name The name of the parameter to get + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +guint32 +Extension::get_param_color(const gchar *name) const +{ + const InxParameter *param; + param = get_param(name); + return param->get_color(); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the boolean in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +bool +Extension::set_param_bool(const gchar *name, const bool value) +{ + InxParameter *param; + param = get_param(name); + return param->set_bool(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the integer in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +int +Extension::set_param_int(const gchar *name, const int value) +{ + InxParameter *param; + param = get_param(name); + return param->set_int(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the float in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +float +Extension::set_param_float(const gchar *name, const float value) +{ + InxParameter *param; + param = get_param(name); + return param->set_float(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the string in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::set_param_string(const gchar *name, const char *value) +{ + InxParameter *param; + param = get_param(name); + return param->set_string(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the string in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + + Look up in the parameters list, const then execute the function on that found parameter. +*/ +const char * +Extension::set_param_optiongroup(const gchar *name, const char *value) +{ + InxParameter *param; + param = get_param(name); + return param->set_optiongroup(value); +} + +/** + \return The passed in value + \brief Sets a parameter identified by name with the unsigned integer RGBA value in the parameter value. + \param name The name of the parameter to set + \param value The value to set the parameter to + +Look up in the parameters list, const then execute the function on that found parameter. +*/ +guint32 +Extension::set_param_color(const gchar *name, const guint32 color) +{ + InxParameter *param; + param = get_param(name); + return param->set_color(color); +} + + +/** \brief A function to open the error log file. */ +void +Extension::error_file_open () +{ + gchar *ext_error_file = Inkscape::IO::Resource::log_path(EXTENSION_ERROR_LOG_FILENAME); + error_file = Inkscape::IO::fopen_utf8name(ext_error_file, "w+"); + if (!error_file) { + g_warning(_("Could not create extension error log file '%s'"), ext_error_file); + } + g_free(ext_error_file); +}; + +/** \brief A function to close the error log file. */ +void +Extension::error_file_close () +{ + if (error_file) { + fclose(error_file); + } +}; + +/** \brief A function to write to the error log file. */ +void +Extension::error_file_write (Glib::ustring text) +{ + if (error_file) { + g_fprintf(error_file, "%s\n", text.c_str()); + } +}; + +/** \brief A widget to represent the inside of an AutoGUI widget */ +class AutoGUI : public Gtk::VBox { +public: + /** \brief Create an AutoGUI object */ + AutoGUI () : Gtk::VBox() {}; + + /** + * Adds a widget with a tool tip into the autogui. + * + * If there is no widget, nothing happens. Otherwise it is just + * added into the VBox. If there is a tooltip (non-NULL) then it + * is placed on the widget. + * + * @param widg Widget to add. + * @param tooltip Tooltip for the widget. + */ + void addWidget(Gtk::Widget *widg, gchar const *tooltip, int indent) { + if (widg) { + widg->set_margin_start(indent * InxParameter::GUI_INDENTATION); + this->pack_start(*widg, false, true, 0); // fill=true does not have an effect here, but allows the + // child to choose to expand by setting hexpand/vexpand + if (tooltip) { + widg->set_tooltip_text(tooltip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + }; +}; + +/** \brief A function to automatically generate a GUI from the extensions' widgets + \return Generated widget + + This function just goes through each widget, and calls it's 'get_widget'. + Then, each of those is placed into a Gtk::VBox, which is then returned to the calling function. + + If there are no visible parameters, this function just returns NULL. +*/ +Gtk::Widget * +Extension::autogui (SPDocument *doc, Inkscape::XML::Node *node, sigc::signal<void> *changeSignal) +{ + if (!_gui || widget_visible_count() == 0) { + return nullptr; + } + + AutoGUI * agui = Gtk::manage(new AutoGUI()); + agui->set_border_width(InxParameter::GUI_BOX_MARGIN); + agui->set_spacing(InxParameter::GUI_BOX_SPACING); + + // go through the list of widgets and add the all non-hidden ones + for (auto widget : _widgets) { + if (widget->get_hidden()) { + continue; + } + + Gtk::Widget *widg = widget->get_widget(changeSignal); + gchar const *tip = widget->get_tooltip(); + int indent = widget->get_indent(); + + agui->addWidget(widg, tip, indent); + } + + agui->show(); + return agui; +}; + +/* Extension editor dialog stuff */ + +Gtk::VBox * +Extension::get_info_widget() +{ + Gtk::VBox * retval = Gtk::manage(new Gtk::VBox()); + retval->set_border_width(4); + + Gtk::Frame * info = Gtk::manage(new Gtk::Frame("General Extension Information")); + retval->pack_start(*info, true, true, 4); + + auto table = Gtk::manage(new Gtk::Grid()); + table->set_border_width(4); + table->set_column_spacing(4); + + info->add(*table); + + int row = 0; + add_val(_("Name:"), get_translation(_name), table, &row); + add_val(_("ID:"), _id, table, &row); + add_val(_("State:"), _state == STATE_LOADED ? _("Loaded") : _state == STATE_UNLOADED ? _("Unloaded") : _("Deactivated"), table, &row); + + retval->show_all(); + return retval; +} + +void Extension::add_val(Glib::ustring labelstr, Glib::ustring valuestr, Gtk::Grid * table, int * row) +{ + Gtk::Label * label; + Gtk::Label * value; + + (*row)++; + label = Gtk::manage(new Gtk::Label(labelstr, Gtk::ALIGN_START)); + value = Gtk::manage(new Gtk::Label(valuestr, Gtk::ALIGN_START)); + + table->attach(*label, 0, (*row) - 1, 1, 1); + table->attach(*value, 1, (*row) - 1, 1, 1); + + label->show(); + value->show(); + + return; +} + +Gtk::VBox * +Extension::get_params_widget() +{ + Gtk::VBox * retval = Gtk::manage(new Gtk::VBox()); + Gtk::Widget * content = Gtk::manage(new Gtk::Label("Params")); + retval->pack_start(*content, true, true, 4); + content->show(); + retval->show(); + return retval; +} + +unsigned int Extension::widget_visible_count ( ) +{ + unsigned int _visible_count = 0; + for (auto widget : _widgets) { + if (!widget->get_hidden()) { + _visible_count++; + } + } + return _visible_count; +} + +} /* namespace Extension */ +} /* 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/extension/extension.h b/src/extension/extension.h new file mode 100644 index 0000000..476be37 --- /dev/null +++ b/src/extension/extension.h @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INK_EXTENSION_H +#define INK_EXTENSION_H + +/** \file + * Frontend to certain, possibly pluggable, actions. + */ + +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <fstream> +#include <ostream> +#include <vector> + +#include <glib.h> +#include <sigc++/signal.h> + +namespace Glib { + class ustring; +} + +namespace Gtk { + class Grid; + class VBox; + class Widget; +} + +/** The key that is used to identify that the I/O should be autodetected */ +#define SP_MODULE_KEY_AUTODETECT "autodetect" +/** This is the key for the SVG input module */ +#define SP_MODULE_KEY_INPUT_SVG "org.inkscape.input.svg" +#define SP_MODULE_KEY_INPUT_SVGZ "org.inkscape.input.svgz" +/** Specifies the input module that should be used if none are selected */ +#define SP_MODULE_KEY_INPUT_DEFAULT SP_MODULE_KEY_AUTODETECT +/** The key for outputting standard W3C SVG */ +#define SP_MODULE_KEY_OUTPUT_SVG "org.inkscape.output.svg.plain" +#define SP_MODULE_KEY_OUTPUT_SVGZ "org.inkscape.output.svgz.plain" +/** This is an output file that has SVG data with the Sodipodi namespace extensions */ +#define SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE "org.inkscape.output.svg.inkscape" +#define SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE "org.inkscape.output.svgz.inkscape" +/** Which output module should be used? */ +#define SP_MODULE_KEY_OUTPUT_DEFAULT SP_MODULE_KEY_AUTODETECT + +/** Defines the key for Postscript printing */ +#define SP_MODULE_KEY_PRINT_PS "org.inkscape.print.ps" +#define SP_MODULE_KEY_PRINT_CAIRO_PS "org.inkscape.print.ps.cairo" +#define SP_MODULE_KEY_PRINT_CAIRO_EPS "org.inkscape.print.eps.cairo" +/** Defines the key for PDF printing */ +#define SP_MODULE_KEY_PRINT_PDF "org.inkscape.print.pdf" +#define SP_MODULE_KEY_PRINT_CAIRO_PDF "org.inkscape.print.pdf.cairo" +/** Defines the key for LaTeX printing */ +#define SP_MODULE_KEY_PRINT_LATEX "org.inkscape.print.latex" +/** Defines the key for printing with GNOME Print */ +#define SP_MODULE_KEY_PRINT_GNOME "org.inkscape.print.gnome" + +/** Mime type for SVG */ +#define MIME_SVG "image/svg+xml" + +/** Name of the extension error file */ +#define EXTENSION_ERROR_LOG_FILENAME "extension-errors.log" + + +#define INKSCAPE_EXTENSION_URI "http://www.inkscape.org/namespace/inkscape/extension" +#define INKSCAPE_EXTENSION_NS_NC "extension" +#define INKSCAPE_EXTENSION_NS "extension:" + +class SPDocument; + +namespace Inkscape { + +namespace XML { +class Node; +} + +namespace Extension { + +class ExecutionEnv; +class Dependency; +class ExpirationTimer; +class ExpirationTimer; +class InxParameter; +class InxWidget; + +namespace Implementation +{ +class Implementation; +} + + +/** The object that is the basis for the Extension system. This object + contains all of the information that all Extension have. The + individual items are detailed within. This is the interface that + those who want to _use_ the extensions system should use. This + is most likely to be those who are inside the Inkscape program. */ +class Extension { +public: + /** An enumeration to identify if the Extension has been loaded or not. */ + enum state_t { + STATE_LOADED, /**< The extension has been loaded successfully */ + STATE_UNLOADED, /**< The extension has not been loaded */ + STATE_DEACTIVATED /**< The extension is missing something which makes it unusable */ + }; + +private: + gchar *_id = nullptr; /**< The unique identifier for the Extension */ + gchar *_name = nullptr; /**< A user friendly name for the Extension */ + state_t _state = STATE_UNLOADED; /**< Which state the Extension is currently in */ + std::vector<Dependency *> _deps; /**< Dependencies for this extension */ + static FILE *error_file; /**< This is the place where errors get reported */ + bool _gui; + std::string _error_reason; /**< Short, textual explanation for the latest error */ + +protected: + Inkscape::XML::Node *repr; /**< The XML description of the Extension */ + Implementation::Implementation * imp; /**< An object that holds all the functions for making this work */ + ExecutionEnv * execution_env; /**< Execution environment of the extension + * (currently only used by Effects) */ + std::string _base_directory; /**< Directory containing the .inx file, + * relative paths in the extension should usually be relative to it */ + ExpirationTimer * timer = nullptr; /**< Timeout to unload after a given time */ + bool _translation_enabled = true; /**< Attempt translation of strings provided by the extension? */ + +private: + const char *_translationdomain = nullptr; /**< Domainname of gettext textdomain that should + * be used for translation of the extension's strings */ + std::string _gettext_catalog_dir; /**< Directory containing the gettext catalog for _translationdomain */ + + void lookup_translation_catalog(); + +public: + Extension(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory); + virtual ~Extension(); + + void set_state (state_t in_state); + state_t get_state (); + bool loaded (); + virtual bool check (); + Inkscape::XML::Node * get_repr (); + gchar * get_id () const; + gchar * get_name () const; + void deactivate (); + bool deactivated (); + void printFailure (Glib::ustring reason); + std::string const &getErrorReason() { return _error_reason; }; + Implementation::Implementation * get_imp () { return imp; }; + void set_execution_env (ExecutionEnv * env) { execution_env = env; }; + ExecutionEnv *get_execution_env () { return execution_env; }; + std::string get_base_directory() const { return _base_directory; }; + void set_base_directory(std::string base_directory) { _base_directory = base_directory; }; + std::string get_dependency_location(const char *name); + const char *get_translation(const char* msgid, const char *msgctxt=nullptr); + void set_environment(); + +/* Parameter Stuff */ +private: + std::vector<InxWidget *> _widgets; /**< A list of widgets for this extension. */ + +public: + /** \brief A function to get the number of visible parameters of the extension. + \return The number of visible parameters. */ + unsigned int widget_visible_count ( ); + +public: + /** An error class for when a parameter is looked for that just + * simply doesn't exist */ + class param_not_exist {}; + + /** no valid ID found while parsing XML representation */ + class extension_no_id{}; + + /** no valid name found while parsing XML representation */ + class extension_no_name{}; + + /** extension is not compatible with the current system and should not be loaded */ + class extension_not_compatible{}; + + /** An error class for when a filename already exists, but the user + * doesn't want to overwrite it */ + class no_overwrite {}; + +private: + void make_param (Inkscape::XML::Node * paramrepr); + + /** + * Looks up the parameter with the specified name. + * + * Searches the list of parameters attached to this extension, + * looking for a parameter with a matching name. + * + * This function can throw a 'param_not_exist' exception if the + * name is not found. + * + * @param name Name of the parameter to search for. + * @return Parameter with matching name. + */ + InxParameter *get_param(const gchar *name); + + InxParameter const *get_param(const gchar *name) const; + +public: + bool get_param_bool (const gchar *name) const; + int get_param_int (const gchar *name) const; + float get_param_float (const gchar *name) const; + const char *get_param_string (const gchar *name) const; + const char *get_param_optiongroup (const gchar *name) const; + guint32 get_param_color (const gchar *name) const; + + bool get_param_optiongroup_contains (const gchar *name, const char *value) const; + + bool set_param_bool (const gchar *name, const bool value); + int set_param_int (const gchar *name, const int value); + float set_param_float (const gchar *name, const float value); + const char *set_param_string (const gchar *name, const char *value); + const char *set_param_optiongroup (const gchar *name, const char *value); + guint32 set_param_color (const gchar *name, const guint32 color); + + + /* Error file handling */ +public: + static void error_file_open (); + static void error_file_close (); + static void error_file_write (Glib::ustring text); + +public: + Gtk::Widget *autogui (SPDocument *doc, Inkscape::XML::Node *node, sigc::signal<void> *changeSignal = nullptr); + void paramListString(std::list <std::string> &retlist); + void set_gui(bool s) { _gui = s; } + bool get_gui() { return _gui; } + + /* Extension editor dialog stuff */ +public: + Gtk::VBox *get_info_widget(); + Gtk::VBox *get_params_widget(); +protected: + inline static void add_val(Glib::ustring labelstr, Glib::ustring valuestr, Gtk::Grid * table, int * row); +}; + + + +/* + +This is a prototype for how collections should work. Whoever gets +around to implementing this gets to decide what a 'folder' and an +'item' really is. That is the joy of implementing it, eh? + +class Collection : public Extension { + +public: + folder get_root (void); + int get_count (folder); + thumbnail get_thumbnail(item); + item[] get_items(folder); + folder[] get_folders(folder); + metadata get_metadata(item); + image get_image(item); + +}; +*/ + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif // INK_EXTENSION_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/extension/find_extension_by_mime.h b/src/extension/find_extension_by_mime.h new file mode 100644 index 0000000..f58580e --- /dev/null +++ b/src/extension/find_extension_by_mime.h @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Find an extension by its mime type. + */ +/* Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Frank Felfe <innerspace@iname.com> + * bulia byak <buliabyak@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * Kris De Gussem <Kris.DeGussem@gmail.com> + * + * Copyright (C) 2012 Kris De Gussem + * Copyright (C) 2010 authors + * Copyright (C) 1999-2005 authors + * Copyright (C) 2004 David Turner + * Copyright (C) 2001-2002 Ximian, Inc. + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "input.h" + +namespace Inkscape { +namespace Extension { +static inline Inkscape::Extension::Extension *find_by_mime(const char *const mime) +{ + + Inkscape::Extension::DB::InputList o; + Inkscape::Extension::db.get_input_list(o); + Inkscape::Extension::DB::InputList::const_iterator i = o.begin(); + while (i != o.end() && strcmp((*i)->get_mimetype(), mime) != 0) { + ++i; + } + return *i; +} +} +} diff --git a/src/extension/implementation/implementation.cpp b/src/extension/implementation/implementation.cpp new file mode 100644 index 0000000..36cd299 --- /dev/null +++ b/src/extension/implementation/implementation.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Author: Ted Gould <ted@gould.cx> + Copyright (c) 2003-2005,2007 + + Released under GNU GPL v2+, read the file 'COPYING' for more information. + + This file is the backend to the extensions system. These are + the parts of the system that most users will never see, but are + important for implementing the extensions themselves. This file + contains the base class for all of that. +*/ + +#include "implementation.h" + +#include <extension/output.h> +#include <extension/input.h> +#include <extension/effect.h> + +#include "selection.h" +#include "desktop.h" + + +namespace Inkscape { +namespace Extension { +namespace Implementation { + +Gtk::Widget * +Implementation::prefs_input(Inkscape::Extension::Input *module, gchar const */*filename*/) { + return module->autogui(nullptr, nullptr); +} + +Gtk::Widget * +Implementation::prefs_output(Inkscape::Extension::Output *module) { + return module->autogui(nullptr, nullptr); +} + +Gtk::Widget *Implementation::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, ImplementationDocumentCache * /*docCache*/) +{ + if (module->widget_visible_count() == 0) { + return nullptr; + } + + SPDocument * current_document = view->doc(); + + auto selected = ((SPDesktop *) view)->getSelection()->items(); + Inkscape::XML::Node const* first_select = nullptr; + if (!selected.empty()) { + const SPItem * item = selected.front(); + first_select = item->getRepr(); + } + + // TODO deal with this broken const correctness: + return module->autogui(current_document, const_cast<Inkscape::XML::Node *>(first_select), changeSignal); +} // Implementation::prefs_effect + +} /* namespace Implementation */ +} /* namespace Extension */ +} /* 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/implementation/implementation.h b/src/extension/implementation/implementation.h new file mode 100644 index 0000000..ba4f773 --- /dev/null +++ b/src/extension/implementation/implementation.h @@ -0,0 +1,204 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + Author: Ted Gould <ted@gould.cx> + Copyright (c) 2003-2005,2007 + + Released under GNU GPL v2+, read the file 'COPYING' for more information. + + This file is the backend to the extensions system. These are + the parts of the system that most users will never see, but are + important for implementing the extensions themselves. This file + contains the base class for all of that. +*/ +#ifndef SEEN_INKSCAPE_EXTENSION_IMPLEMENTATION_H +#define SEEN_INKSCAPE_EXTENSION_IMPLEMENTATION_H + +#include <vector> +#include <sigc++/signal.h> +#include <glibmm/value.h> +#include <2geom/forward.h> + +namespace Gtk { + class Widget; +} + +class SPDocument; +class SPStyle; + +namespace Inkscape { + +namespace UI { +namespace View { +class View; +} +} + +namespace XML { + class Node; +} + +namespace Extension { + +class Effect; +class Extension; +class Input; +class Output; +class Print; + +namespace Implementation { + +/** + * A cache for the document and this implementation. + */ +class ImplementationDocumentCache { + + /** + * The document that this instance is working on. + */ + Inkscape::UI::View::View * _view; +public: + ImplementationDocumentCache (Inkscape::UI::View::View * view) { return; }; + + virtual ~ImplementationDocumentCache ( ) { return; }; + Inkscape::UI::View::View const * view ( ) { return _view; }; +}; + +/** + * Base class for all implementations of modules. This is whether they are done systematically by + * having something like the scripting system, or they are implemented internally they all derive + * from this class. + */ +class Implementation { +public: + // ----- Constructor / destructor ----- + Implementation() = default; + + virtual ~Implementation() = default; + + // ----- Basic functions for all Extension ----- + virtual bool load(Inkscape::Extension::Extension * /*module*/) { return true; } + + virtual void unload(Inkscape::Extension::Extension * /*module*/) {} + + /** + * Create a new document cache object. + * This function just returns \c NULL. Subclasses are likely + * to reimplement it to do something useful. + * @param ext The extension that is referencing us + * @param doc The document to create the cache of + * @return A new document cache that is valid as long as the document + * is not changed. + */ + virtual ImplementationDocumentCache * newDocCache (Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * /*doc*/) { return nullptr; } + + /** Verify any dependencies. */ + virtual bool check(Inkscape::Extension::Extension * /*module*/) { return true; } + + virtual bool cancelProcessing () { return true; } + virtual void commitDocument () {} + + // ----- Input functions ----- + /** Find out information about the file. */ + virtual Gtk::Widget *prefs_input(Inkscape::Extension::Input *module, + gchar const *filename); + + virtual SPDocument *open(Inkscape::Extension::Input * /*module*/, + gchar const * /*filename*/) { return nullptr; } + + // ----- Output functions ----- + /** Find out information about the file. */ + virtual Gtk::Widget *prefs_output(Inkscape::Extension::Output *module); + virtual void save(Inkscape::Extension::Output * /*module*/, SPDocument * /*doc*/, gchar const * /*filename*/) {} + + // ----- Effect functions ----- + /** Find out information about the file. */ + virtual Gtk::Widget * prefs_effect(Inkscape::Extension::Effect *module, + Inkscape::UI::View::View *view, + sigc::signal<void> *changeSignal, + ImplementationDocumentCache *docCache); + virtual void effect(Inkscape::Extension::Effect * /*module*/, + Inkscape::UI::View::View * /*document*/, + ImplementationDocumentCache * /*docCache*/) {} + + // ----- Print functions ----- + virtual unsigned setup(Inkscape::Extension::Print * /*module*/) { return 0; } + virtual unsigned set_preview(Inkscape::Extension::Print * /*module*/) { return 0; } + + virtual unsigned begin(Inkscape::Extension::Print * /*module*/, + SPDocument * /*doc*/) { return 0; } + virtual unsigned finish(Inkscape::Extension::Print * /*module*/) { return 0; } + + /** + * Tell the printing engine whether text should be text or path. + * Default value is false because most printing engines will support + * paths more than they'll support text. (at least they do today) + * \retval true Render the text as a path + * \retval false Render text using the text function (above) + */ + virtual bool textToPath(Inkscape::Extension::Print * /*ext*/) { return false; } + + /** + * Get "fontEmbedded" param, i.e. tell the printing engine whether fonts should be embedded. + * Only available for Adobe Type 1 fonts in EPS output as of now + * \retval true Fonts have to be embedded in the output so that the user might not need + * to install fonts to have the interpreter read the document correctly + * \retval false Do not embed fonts + */ + virtual bool fontEmbedded(Inkscape::Extension::Print * /*ext*/) { return false; } + + // ----- Rendering methods ----- + virtual unsigned bind(Inkscape::Extension::Print * /*module*/, + Geom::Affine const & /*transform*/, + float /*opacity*/) { return 0; } + virtual unsigned release(Inkscape::Extension::Print * /*module*/) { return 0; } + virtual unsigned comment(Inkscape::Extension::Print * /*module*/, char const * /*comment*/) { return 0; } + virtual unsigned fill(Inkscape::Extension::Print * /*module*/, + Geom::PathVector const & /*pathv*/, + Geom::Affine const & /*ctm*/, + SPStyle const * /*style*/, + Geom::OptRect const & /*pbox*/, + Geom::OptRect const & /*dbox*/, + Geom::OptRect const & /*bbox*/) { return 0; } + virtual unsigned stroke(Inkscape::Extension::Print * /*module*/, + Geom::PathVector const & /*pathv*/, + Geom::Affine const & /*transform*/, + SPStyle const * /*style*/, + Geom::OptRect const & /*pbox*/, + Geom::OptRect const & /*dbox*/, + Geom::OptRect const & /*bbox*/) { return 0; } + virtual unsigned image(Inkscape::Extension::Print * /*module*/, + unsigned char * /*px*/, + unsigned int /*w*/, + unsigned int /*h*/, + unsigned int /*rs*/, + Geom::Affine const & /*transform*/, + SPStyle const * /*style*/) { return 0; } + virtual unsigned text(Inkscape::Extension::Print * /*module*/, + char const * /*text*/, + Geom::Point const & /*p*/, + SPStyle const * /*style*/) { return 0; } + virtual void processPath(Inkscape::XML::Node * /*node*/) {} + + /** + * If detach = true, when saving to a file, don't store URIs realtive to the filename + */ + virtual void setDetachBase(bool detach) {} +}; + + +} // namespace Implementation +} // namespace Extension +} // namespace Inkscape + +#endif // __INKSCAPE_EXTENSION_IMPLEMENTATION_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/extension/implementation/script.cpp b/src/extension/implementation/script.cpp new file mode 100644 index 0000000..f885caf --- /dev/null +++ b/src/extension/implementation/script.cpp @@ -0,0 +1,978 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * Code for handling extensions (i.e. scripts). + * + * Authors: + * Bryce Harrington <bryce@osdl.org> + * Ted Gould <ted@gould.cx> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2002-2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glib/gstdio.h> +#include <glibmm.h> +#include <glibmm/convert.h> +#include <glibmm/miscutils.h> +#include <gtkmm/main.h> +#include <gtkmm/messagedialog.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/textview.h> + +#include "desktop.h" +#include "inkscape.h" +#include "path-prefix.h" +#include "preferences.h" +#include "script.h" +#include "selection.h" + +#include "extension/db.h" +#include "extension/effect.h" +#include "extension/execution-env.h" +#include "extension/input.h" +#include "extension/output.h" +#include "extension/system.h" +#include "io/resource.h" +#include "object/sp-namedview.h" +#include "object/sp-path.h" +#include "ui/dialog-events.h" +#include "ui/tool/control-point-selection.h" +#include "ui/tool/multi-path-manipulator.h" +#include "ui/tool/path-manipulator.h" +#include "ui/tools/node-tool.h" +#include "ui/view/view.h" +#include "xml/attribute-record.h" +#include "xml/node.h" + +/* Namespaces */ +namespace Inkscape { +namespace Extension { +namespace Implementation { + +/** \brief Make GTK+ events continue to come through a little bit + + This just keeps coming the events through so that we'll make the GUI + update and look pretty. +*/ +void Script::pump_events () { + while ( Gtk::Main::events_pending() ) { + Gtk::Main::iteration(); + } + return; +} + + +/** \brief A table of what interpreters to call for a given language + + This table is used to keep track of all the programs to execute a + given script. It also tracks the preference to use to overwrite + the given interpreter to a custom one per user. +*/ +const std::map<std::string, Script::interpreter_t> Script::interpreterTab = { +#ifdef _WIN32 + { "perl", {"perl-interpreter", {"wperl" }}}, + { "python", {"python-interpreter", {"pythonw" }}}, +#elif defined __APPLE__ + { "perl", {"perl-interpreter", {"perl" }}}, + { "python", {"python-interpreter", {"python3" }}}, +#else + { "perl", {"perl-interpreter", {"perl" }}}, + { "python", {"python-interpreter", {"python3", "python" }}}, +#endif + { "python2", {"python2-interpreter", {"python2", "python" }}}, + { "ruby", {"ruby-interpreter", {"ruby" }}}, + { "shell", {"shell-interpreter", {"sh" }}}, +}; + + + +/** \brief Look up an interpreter name, and translate to something that + is executable + \param interpNameArg The name of the interpreter that we're looking + for, should be an entry in interpreterTab +*/ +std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg) +{ + // 0. Do we have a supported interpreter type? + auto interp = interpreterTab.find(interpNameArg); + if (interp == interpreterTab.end()) { + g_critical("Script::resolveInterpreterExecutable(): unknown script interpreter '%s'", interpNameArg.c_str()); + return ""; + } + + std::list<Glib::ustring> searchList; + std::copy(interp->second.defaultvals.begin(), interp->second.defaultvals.end(), std::back_inserter(searchList)); + + // 1. Check preferences for an override. + auto prefs = Inkscape::Preferences::get(); + auto prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->second.prefstring)); + + if (!prefInterp.empty()) { + searchList.push_front(prefInterp); + } + + // 2. Search for things in the path if they're there or an absolute + for (const auto& binname : searchList) { + auto interpreter_path = Glib::filename_from_utf8(binname); + + if (!Glib::path_is_absolute(interpreter_path)) { + auto found_path = Glib::find_program_in_path(interpreter_path); + if (!found_path.empty()) { + return found_path; + } + } else { + return interpreter_path; + } + } + + // 3. Error + g_critical("Script::resolveInterpreterExecutable(): failed to locate script interpreter '%s'", interpNameArg.c_str()); + return ""; +} + +/** \brief This function creates a script object and sets up the + variables. + \return A script object + + This function just sets the command to NULL. It should get built + officially in the load function. This allows for less allocation + of memory in the unloaded state. +*/ +Script::Script() + : Implementation() + , _canceled(false) + , parent_window(nullptr) +{ +} + +/** + * \brief Destructor + */ +Script::~Script() += default; + + +/** + \return none + \brief This function 'loads' an extension, basically it determines + the full command for the extension and stores that. + \param module The extension to be loaded. + + The most difficult part about this function is finding the actual + command through all of the Reprs. Basically it is hidden down a + couple of layers, and so the code has to move down too. When + the command is actually found, it has its relative directory + solved. + + At that point all of the loops are exited, and there is an + if statement to make sure they didn't exit because of not finding + the command. If that's the case, the extension doesn't get loaded + and should error out at a higher level. +*/ + +bool Script::load(Inkscape::Extension::Extension *module) +{ + if (module->loaded()) { + return true; + } + + helper_extension = ""; + + /* This should probably check to find the executable... */ + Inkscape::XML::Node *child_repr = module->get_repr()->firstChild(); + while (child_repr != nullptr) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) { + for (child_repr = child_repr->firstChild(); child_repr != nullptr; child_repr = child_repr->next()) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) { + const gchar *interpretstr = child_repr->attribute("interpreter"); + if (interpretstr != nullptr) { + std::string interpString = resolveInterpreterExecutable(interpretstr); + if (interpString.empty()) { + continue; // can't have a script extension with empty interpreter + } + command.push_back(interpString); + } + // TODO: we already parse commands as dependencies in extension.cpp + // can can we optimize this to be less indirect? + const char *script_name = child_repr->firstChild()->content(); + std::string script_location = module->get_dependency_location(script_name); + command.push_back(std::move(script_location)); + } else if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) { + helper_extension = child_repr->firstChild()->content(); + } + } + + break; + } + child_repr = child_repr->next(); + } + + // TODO: Currently this causes extensions to fail silently, see comment in Extension::set_state() + g_return_val_if_fail(command.size() > 0, false); + + return true; +} + + +/** + \return None. + \brief Unload this puppy! + \param module Extension to be unloaded. + + This function just sets the module to unloaded. It free's the + command if it has been allocated. +*/ +void Script::unload(Inkscape::Extension::Extension */*module*/) +{ + command.clear(); + helper_extension = ""; +} + + + + +/** + \return Whether the check passed or not + \brief Check every dependency that was given to make sure we should keep this extension + \param module The Extension in question + +*/ +bool Script::check(Inkscape::Extension::Extension *module) +{ + int script_count = 0; + Inkscape::XML::Node *child_repr = module->get_repr()->firstChild(); + while (child_repr != nullptr) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) { + script_count++; + + // check if all helper_extensions attached to this script were registered + child_repr = child_repr->firstChild(); + while (child_repr != nullptr) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) { + gchar const *helper = child_repr->firstChild()->content(); + if (Inkscape::Extension::db.get(helper) == nullptr) { + return false; + } + } + + child_repr = child_repr->next(); + } + + break; + } + child_repr = child_repr->next(); + } + + if (script_count == 0) { + return false; + } + + return true; +} + +class ScriptDocCache : public ImplementationDocumentCache { + friend class Script; +protected: + std::string _filename; + int _tempfd; +public: + ScriptDocCache (Inkscape::UI::View::View * view); + ~ScriptDocCache ( ) override; +}; + +ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) : + ImplementationDocumentCache(view), + _filename(""), + _tempfd(0) +{ + try { + _tempfd = Glib::file_open_tmp(_filename, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + return; + } + + SPDesktop *desktop = (SPDesktop *) view; + sp_namedview_document_from_window(desktop); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/options/svgoutput/disable_optimizations", true); + Inkscape::Extension::save( + Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), + view->doc(), _filename.c_str(), false, false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); + prefs->setBool("/options/svgoutput/disable_optimizations", false); + return; +} + +ScriptDocCache::~ScriptDocCache ( ) +{ + close(_tempfd); + unlink(_filename.c_str()); +} + +ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) { + return new ScriptDocCache(view); +} + + +/** + \return A dialog for preferences + \brief A stub function right now + \param module Module who's preferences need getting + \param filename Hey, the file you're getting might be important + + This function should really do something, right now it doesn't. +*/ +Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module, + const gchar */*filename*/) +{ + return module->autogui(nullptr, nullptr); +} + + + +/** + \return A dialog for preferences + \brief A stub function right now + \param module Module whose preferences need getting + + This function should really do something, right now it doesn't. +*/ +Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module) +{ + return module->autogui(nullptr, nullptr); +} + +/** + \return A new document that has been opened + \brief This function uses a filename that is put in, and calls + the extension's command to create an SVG file which is + returned. + \param module Extension to use. + \param filename File to open. + + First things first, this function needs a temporary file name. To + create one of those the function Glib::file_open_tmp is used with + the header of ink_ext_. + + The extension is then executed using the 'execute' function + with the filename assigned and then the temporary filename. + After execution the SVG should be in the temporary file. + + Finally, the temporary file is opened using the SVG input module and + a document is returned. That document has its filename set to + the incoming filename (so that it's not the temporary filename). + That document is then returned from this function. +*/ +SPDocument *Script::open(Inkscape::Extension::Input *module, + const gchar *filenameArg) +{ + std::list<std::string> params; + module->paramListString(params); + module->set_environment(); + + std::string tempfilename_out; + int tempfd_out = 0; + try { + tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + return nullptr; + } + + std::string lfilename = Glib::filename_from_utf8(filenameArg); + + file_listener fileout; + int data_read = execute(command, params, lfilename, fileout); + fileout.toFile(tempfilename_out); + + SPDocument * mydoc = nullptr; + if (data_read > 10) { + if (helper_extension.size()==0) { + mydoc = Inkscape::Extension::open( + Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), + tempfilename_out.c_str()); + } else { + mydoc = Inkscape::Extension::open( + Inkscape::Extension::db.get(helper_extension.c_str()), + tempfilename_out.c_str()); + } + } // data_read + + if (mydoc != nullptr) { + mydoc->setDocumentBase(nullptr); + mydoc->changeUriAndHrefs(filenameArg); + } + + // make sure we don't leak file descriptors from Glib::file_open_tmp + close(tempfd_out); + + unlink(tempfilename_out.c_str()); + + return mydoc; +} // open + + + +/** + \return none + \brief This function uses an extension to save a document. It first + creates an SVG file of the document, and then runs it through + the script. + \param module Extension to be used + \param doc Document to be saved + \param filename The name to save the final file as + \return false in case of any failure writing the file, otherwise true + + Well, at some point people need to save - it is really what makes + the entire application useful. And, it is possible that someone + would want to use an extension for this, so we need a function to + do that, eh? + + First things first, the document is saved to a temporary file that + is an SVG file. To get the temporary filename Glib::file_open_tmp is used with + ink_ext_ as a prefix. Don't worry, this file gets deleted at the + end of the function. + + After we have the SVG file, then Script::execute is called with + the temporary file name and the final output filename. This should + put the output of the script into the final output file. We then + delete the temporary file. +*/ +void Script::save(Inkscape::Extension::Output *module, + SPDocument *doc, + const gchar *filenameArg) +{ + std::list<std::string> params; + module->paramListString(params); + module->set_environment(); + + std::string tempfilename_in; + int tempfd_in = 0; + try { + tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + throw Inkscape::Extension::Output::save_failed(); + } + + if (helper_extension.size() == 0) { + Inkscape::Extension::save( + Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), + doc, tempfilename_in.c_str(), false, false, false, + Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); + } else { + Inkscape::Extension::save( + Inkscape::Extension::db.get(helper_extension.c_str()), + doc, tempfilename_in.c_str(), false, false, false, + Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY); + } + + + file_listener fileout; + int data_read = execute(command, params, tempfilename_in, fileout); + + bool success = false; + + if (data_read > 0) { + std::string lfilename = Glib::filename_from_utf8(filenameArg); + success = fileout.toFile(lfilename); + } + + // make sure we don't leak file descriptors from Glib::file_open_tmp + close(tempfd_in); + // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name + unlink(tempfilename_in.c_str()); + + if (success == false) { + throw Inkscape::Extension::Output::save_failed(); + } + + return; +} + + + +/** + \return none + \brief This function uses an extension as an effect on a document. + \param module Extension to effect with. + \param doc Document to run through the effect. + + This function is a little bit trickier than the previous two. It + needs two temporary files to get its work done. Both of these + files have random names created for them using the Glib::file_open_temp function + with the ink_ext_ prefix in the temporary directory. Like the other + functions, the temporary files are deleted at the end. + + To save/load the two temporary documents (both are SVG) the internal + modules for SVG load and save are used. They are both used through + the module system function by passing their keys into the functions. + + The command itself is built a little bit differently than in other + functions because the effect support selections. So on the command + line a list of all the ids that are selected is included. Currently, + this only works for a single selected object, but there will be more. + The command string is filled with the data, and then after the execution + it is freed. + + The execute function is used at the core of this function + to execute the Script on the two SVG documents (actually only one + exists at the time, the other is created by that script). At that + point both should be full, and the second one is loaded. +*/ +void Script::effect(Inkscape::Extension::Effect *module, + Inkscape::UI::View::View *doc, + ImplementationDocumentCache * docCache) +{ + if (docCache == nullptr) { + docCache = newDocCache(module, doc); + } + ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache); + if (dc == nullptr) { + printf("TOO BAD TO LIVE!!!"); + exit(1); + } + if (doc == nullptr) + { + g_warning("Script::effect: View not defined"); + return; + } + + SPDesktop *desktop = reinterpret_cast<SPDesktop *>(doc); + sp_namedview_document_from_window(desktop); + + std::list<std::string> params; + module->paramListString(params); + module->set_environment(); + + parent_window = module->get_execution_env()->get_working_dialog(); + + if (module->no_doc) { + // this is a no-doc extension, e.g. a Help menu command; + // just run the command without any files, ignoring errors + + Glib::ustring empty; + file_listener outfile; + execute(command, params, empty, outfile); + + return; + } + + std::string tempfilename_out; + int tempfd_out = 0; + try { + tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg"); + } catch (...) { + /// \todo Popup dialog here + return; + } + + if (desktop) { + Inkscape::Selection * selection = desktop->getSelection(); + if (selection) { + params = selection->params; + module->paramListString(params); + selection->clear(); + } + } + + file_listener fileout; + int data_read = execute(command, params, dc->_filename, fileout); + fileout.toFile(tempfilename_out); + + pump_events(); + + SPDocument * mydoc = nullptr; + if (data_read > 10) { + try { + mydoc = Inkscape::Extension::open( + Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), + tempfilename_out.c_str()); + } catch (const Inkscape::Extension::Input::open_failed &e) { + g_warning("Extension returned output that could not be parsed: %s", e.what()); + Gtk::MessageDialog warning( + _("The output from the extension could not be parsed."), + false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true); + warning.set_transient_for( parent_window ? *parent_window : *(INKSCAPE.active_desktop()->getToplevel()) ); + warning.run(); + } + } // data_read + + pump_events(); + + // make sure we don't leak file descriptors from Glib::file_open_tmp + close(tempfd_out); + + g_unlink(tempfilename_out.c_str()); + + if (mydoc) { + SPDocument* vd=doc->doc(); + if (vd != nullptr) + { + mydoc->changeUriAndHrefs(vd->getDocumentURI()); + + vd->emitReconstructionStart(); + copy_doc(vd->getReprRoot(), mydoc->getReprRoot()); + vd->emitReconstructionFinish(); + + // Getting the named view from the document generated by the extension + SPNamedView *nv = sp_document_namedview(mydoc, nullptr); + + //Check if it has a default layer set up + SPObject *layer = nullptr; + if ( nv != nullptr) + { + if( nv->default_layer_id != 0 ) { + SPDocument *document = desktop->doc(); + //If so, get that layer + if (document != nullptr) + { + layer = document->getObjectById(g_quark_to_string(nv->default_layer_id)); + } + } + desktop->showGrids(nv->grids_visible); + } + + sp_namedview_update_layers_from_document(desktop); + //If that layer exists, + if (layer) { + //set the current layer + desktop->setCurrentLayer(layer); + } + } + mydoc->release(); + } + + return; +} + + + +/** + \brief A function to replace all the elements in an old document + by those from a new document. + document and repinserts them into an emptied old document. + \param oldroot The root node of the old (destination) document. + \param newroot The root node of the new (source) document. + + This function first deletes all the root attributes in the old document followed + by copying all the root attributes from the new document to the old document. + + It then deletes all the elements in the old document by + making two passes, the first to create a list of the old elements and + the second to actually delete them. This two pass approach removes issues + with the list being changed while parsing through it... lots of nasty bugs. + + Then, it copies all the element in the new document into the old document. + + Finally, it copies the attributes in namedview. +*/ +void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot) +{ + if ((oldroot == nullptr) ||(newroot == nullptr)) + { + g_warning("Error on copy_doc: NULL pointer input."); + return; + } + + // For copying attributes in root and in namedview + using Inkscape::Util::List; + using Inkscape::XML::AttributeRecord; + std::vector<gchar const *> attribs; + + // Must explicitly copy root attributes. This must be done first since + // copying grid lines calls "SPGuide::set()" which needs to know the + // width, height, and viewBox of the root element. + + // Make a list of all attributes of the old root node. + for (List<AttributeRecord const> iter = oldroot->attributeList(); iter; ++iter) { + attribs.push_back(g_quark_to_string(iter->key)); + } + + // Delete the attributes of the old root node. + for (auto attrib : attribs) { + oldroot->removeAttribute(attrib); + } + + // Set the new attributes. + for (List<AttributeRecord const> iter = newroot->attributeList(); iter; ++iter) { + gchar const *name = g_quark_to_string(iter->key); + oldroot->setAttribute(name, newroot->attribute(name)); + } + + // Question: Why is the "sodipodi:namedview" special? Treating it as a normal + // element results in crashes. + // Seems to be a bug: + // http://inkscape.13.x6.nabble.com/Effect-that-modifies-the-document-properties-tt2822126.html + + std::vector<Inkscape::XML::Node *> delete_list; + + // Make list + for (Inkscape::XML::Node * child = oldroot->firstChild(); + child != nullptr; + child = child->next()) { + if (!strcmp("sodipodi:namedview", child->name())) { + for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild(); + oldroot_namedview_child != nullptr; + oldroot_namedview_child = oldroot_namedview_child->next()) { + delete_list.push_back(oldroot_namedview_child); + } + break; + } + } + + // Unparent (delete) + for (auto & i : delete_list) { + sp_repr_unparent(i); + } + attribs.clear(); + oldroot->mergeFrom(newroot, "id", true, true); +} + +/** \brief This function checks the stderr file, and if it has data, + shows it in a warning dialog to the user + \param filename Filename of the stderr file +*/ +void Script::checkStderr (const Glib::ustring &data, + Gtk::MessageType type, + const Glib::ustring &message) +{ + Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true); + warning.set_resizable(true); + GtkWidget *dlg = GTK_WIDGET(warning.gobj()); + if (parent_window) { + warning.set_transient_for(*parent_window); + } else { + sp_transientize(dlg); + } + + auto vbox = warning.get_content_area(); + + /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */ + Gtk::TextView * textview = new Gtk::TextView(); + textview->set_editable(false); + textview->set_wrap_mode(Gtk::WRAP_WORD); + textview->show(); + + textview->get_buffer()->set_text(data.c_str()); + + Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow(); + scrollwindow->add(*textview); + scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + scrollwindow->set_shadow_type(Gtk::SHADOW_IN); + scrollwindow->show(); + scrollwindow->set_size_request(0, 60); + + vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */); + + warning.run(); + + delete textview; + delete scrollwindow; + + return; +} + +bool Script::cancelProcessing () { + _canceled = true; + _main_loop->quit(); + Glib::spawn_close_pid(_pid); + + return true; +} + + +/** \brief This is the core of the extension file as it actually does + the execution of the extension. + \param in_command The command to be executed + \param filein Filename coming in + \param fileout Filename of the out file + \return Number of bytes that were read into the output file. + + The first thing that this function does is build the command to be + executed. This consists of the first string (in_command) and then + the filename for input (filein). This file is put on the command + line. + + The next thing that this function does is open a pipe to the + command and get the file handle in the ppipe variable. It then + opens the output file with the output file handle. Both of these + operations are checked extensively for errors. + + After both are opened, then the data is copied from the output + of the pipe into the file out using \a fread and \a fwrite. These two + functions are used because of their primitive nature - they make + no assumptions about the data. A buffer is used in the transfer, + but the output of \a fread is stored so the exact number of bytes + is handled gracefully. + + At the very end (after the data has been copied) both of the files + are closed, and we return to what we were doing. +*/ +int Script::execute (const std::list<std::string> &in_command, + const std::list<std::string> &in_params, + const Glib::ustring &filein, + file_listener &fileout) +{ + g_return_val_if_fail(!in_command.empty(), 0); + + std::vector<std::string> argv; + + bool interpreted = (in_command.size() == 2); + std::string program = in_command.front(); + std::string script = interpreted ? in_command.back() : ""; + std::string working_directory = ""; + + // We should always have an absolute path here: + // - For interpreted scripts, see Script::resolveInterpreterExecutable() + // - For "normal" scripts this should be done as part of the dependency checking, see Dependency::check() + if (!Glib::path_is_absolute(program)) { + g_critical("Script::execute(): Got unexpected relative path '%s'. Please report a bug.", program.c_str()); + return 0; + } + argv.push_back(program); + + if (interpreted) { + // On Windows, Python garbles Unicode command line parameters + // in an useless way. This means extensions fail when Inkscape + // is run from an Unicode directory. + // As a workaround, we set the working directory to the one + // containing the script. + working_directory = Glib::path_get_dirname(script); + script = Glib::path_get_basename(script); + argv.push_back(script); + } + + // assemble the rest of argv + std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv)); + if (!filein.empty()) { + if(Glib::path_is_absolute(filein)) + argv.push_back(filein); + else { + std::vector<std::string> buildargs; + buildargs.push_back(Glib::get_current_dir()); + buildargs.push_back(filein); + argv.push_back(Glib::build_filename(buildargs)); + } + } + + //for(int i=0;i<argv.size(); ++i){printf("%s ",argv[i].c_str());}printf("\n"); + + int stdout_pipe, stderr_pipe; + + try { + Glib::spawn_async_with_pipes(working_directory, // working directory + argv, // arg v + static_cast<Glib::SpawnFlags>(0), // no flags + sigc::slot<void>(), + &_pid, // Pid + nullptr, // STDIN + &stdout_pipe, // STDOUT + &stderr_pipe); // STDERR + } catch (Glib::Error &e) { + g_critical("Script::execute(): failed to execute program '%s'.\n\tReason: %s", program.c_str(), e.what().data()); + return 0; + } + + // Create a new MainContext for the loop so that the original context sources are not run here, + // this enforces that only the file_listeners should be read in this new MainLoop + Glib::RefPtr<Glib::MainContext> _main_context = Glib::MainContext::create(); + _main_loop = Glib::MainLoop::create(_main_context, false); + + file_listener fileerr; + fileout.init(stdout_pipe, _main_loop); + fileerr.init(stderr_pipe, _main_loop); + + _canceled = false; + _main_loop->run(); + + // Ensure all the data is out of the pipe + while (!fileout.isDead()) { + fileout.read(Glib::IO_IN); + } + while (!fileerr.isDead()) { + fileerr.read(Glib::IO_IN); + } + + if (_canceled) { + // std::cout << "Script Canceled" << std::endl; + return 0; + } + + Glib::ustring stderr_data = fileerr.string(); + if (stderr_data.length() != 0 && + INKSCAPE.use_gui() + ) { + checkStderr(stderr_data, Gtk::MESSAGE_INFO, + _("Inkscape has received additional data from the script executed. " + "The script did not return an error, but this may indicate the results will not be as expected.")); + } + + Glib::ustring stdout_data = fileout.string(); + if (stdout_data.length() == 0) { + return 0; + } + + // std::cout << "Finishing Execution." << std::endl; + return stdout_data.length(); +} + + +void Script::file_listener::init(int fd, Glib::RefPtr<Glib::MainLoop> main) { + _channel = Glib::IOChannel::create_from_fd(fd); + _channel->set_encoding(); + _conn = main->get_context()->signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR); + _main_loop = main; + + return; +} + +bool Script::file_listener::read(Glib::IOCondition condition) { + if (condition != Glib::IO_IN) { + _main_loop->quit(); + return false; + } + + Glib::IOStatus status; + Glib::ustring out; + status = _channel->read_line(out); + _string += out; + + if (status != Glib::IO_STATUS_NORMAL) { + _main_loop->quit(); + _dead = true; + return false; + } + + return true; +} + +bool Script::file_listener::toFile(const Glib::ustring &name) { + try { + Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w"); + stdout_file->set_encoding(); + stdout_file->write(_string); + } catch (Glib::FileError &e) { + return false; + } + return true; +} + +} // namespace Implementation +} // namespace Extension +} // 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:fileencoding=utf-8 : diff --git a/src/extension/implementation/script.h b/src/extension/implementation/script.h new file mode 100644 index 0000000..12e2974 --- /dev/null +++ b/src/extension/implementation/script.h @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Code for handling extensions (i.e., scripts) + * + * Authors: + * Bryce Harrington <bryce@osdl.org> + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN +#define INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN + +#include "implementation.h" +#include <gtkmm/enums.h> +#include <gtkmm/window.h> +#include <glibmm/main.h> +#include <glibmm/spawn.h> +#include <glibmm/fileutils.h> + +namespace Inkscape { +namespace XML { +class Node; +} // namespace XML + +namespace Extension { +namespace Implementation { + +/** + * Utility class used for loading and launching script extensions + */ +class Script : public Implementation { +public: + + Script(); + ~Script() override; + bool load(Inkscape::Extension::Extension *module) override; + void unload(Inkscape::Extension::Extension *module) override; + bool check(Inkscape::Extension::Extension *module) override; + + ImplementationDocumentCache * newDocCache(Inkscape::Extension::Extension * ext, Inkscape::UI::View::View * view) override; + + Gtk::Widget *prefs_input(Inkscape::Extension::Input *module, gchar const *filename) override; + SPDocument *open(Inkscape::Extension::Input *module, gchar const *filename) override; + Gtk::Widget *prefs_output(Inkscape::Extension::Output *module) override; + void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override; + void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc, ImplementationDocumentCache * docCache) override; + bool cancelProcessing () override; + +private: + bool _canceled; + Glib::Pid _pid; + Glib::RefPtr<Glib::MainLoop> _main_loop; + + /** + * The command that has been derived from + * the configuration file with appropriate directories + */ + std::list<std::string> command; + + /** + * This is the extension that will be used + * as the helper to read in or write out the + * data + */ + Glib::ustring helper_extension; + + /** + * The window which should be considered as "parent window" of the script execution, + * e.g. when showin warning messages + * + * If set to NULL the main window of the currently active document is used. + */ + Gtk::Window *parent_window; + + void copy_doc(Inkscape::XML::Node * olddoc, Inkscape::XML::Node * newdoc); + void checkStderr (Glib::ustring const& filename, Gtk::MessageType type, Glib::ustring const& message); + + class file_listener { + Glib::ustring _string; + sigc::connection _conn; + Glib::RefPtr<Glib::IOChannel> _channel; + Glib::RefPtr<Glib::MainLoop> _main_loop; + bool _dead; + + public: + file_listener () : _dead(false) { }; + virtual ~file_listener () { + _conn.disconnect(); + }; + + bool isDead () { return _dead; } + void init(int fd, Glib::RefPtr<Glib::MainLoop> main); + bool read(Glib::IOCondition condition); + Glib::ustring string () { return _string; }; + bool toFile(const Glib::ustring &name); + }; + + int execute (const std::list<std::string> &in_command, + const std::list<std::string> &in_params, + const Glib::ustring &filein, + file_listener &fileout); + + void pump_events(); + + /** \brief A definition of an interpreter, which can be specified + in the INX file, but we need to know what to call */ + struct interpreter_t { + std::string prefstring; /**< The preferences key that can override the default */ + std::vector<std::string> defaultvals; /**< The default values to check if the preferences are wrong */ + }; + static const std::map<std::string, interpreter_t> interpreterTab; + + std::string resolveInterpreterExecutable(const Glib::ustring &interpNameArg); + +}; // class Script +} // namespace Implementation +} // namespace Extension +} // namespace Inkscape + +#endif // INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN + +/* + 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/extension/implementation/xslt.cpp b/src/extension/implementation/xslt.cpp new file mode 100644 index 0000000..dced1dd --- /dev/null +++ b/src/extension/implementation/xslt.cpp @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Code for handling XSLT extensions. + */ +/* + * Authors: + * Ted Gould <ted@gould.cx> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2006-2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "xslt.h" + +#include <unistd.h> +#include <cstring> + +#include <glibmm/fileutils.h> +#include <libxslt/transform.h> +#include <libxslt/xsltutils.h> + +#include "document.h" +#include "file.h" + +#include "extension/extension.h" +#include "extension/output.h" +#include "extension/input.h" + +#include "io/resource.h" + +#include "xml/node.h" +#include "xml/repr.h" + +#include <clocale> + +Inkscape::XML::Document * sp_repr_do_read (xmlDocPtr doc, const gchar * default_ns); + +/* Namespaces */ +namespace Inkscape { +namespace Extension { +namespace Implementation { + +/* Real functions */ +/** + \return A XSLT object + \brief This function creates a XSLT object and sets up the + variables. + +*/ +XSLT::XSLT() : + Implementation(), + _filename(""), + _parsedDoc(nullptr), + _stylesheet(nullptr) +{ +} + +bool XSLT::check(Inkscape::Extension::Extension *module) +{ + if (load(module)) { + unload(module); + return true; + } else { + return false; + } +} + +bool XSLT::load(Inkscape::Extension::Extension *module) +{ + if (module->loaded()) { return true; } + + Inkscape::XML::Node *child_repr = module->get_repr()->firstChild(); + while (child_repr != nullptr) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "xslt")) { + child_repr = child_repr->firstChild(); + while (child_repr != nullptr) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "file")) { + // TODO: we already parse xslt files as dependencies in extension.cpp + // can can we optimize this to be less indirect? + const char *filename = child_repr->firstChild()->content(); + _filename = module->get_dependency_location(filename); + } + child_repr = child_repr->next(); + } + + break; + } + child_repr = child_repr->next(); + } + + _parsedDoc = xmlParseFile(_filename.c_str()); + if (_parsedDoc == nullptr) { return false; } + + _stylesheet = xsltParseStylesheetDoc(_parsedDoc); + + return true; +} + +void XSLT::unload(Inkscape::Extension::Extension *module) +{ + if (!module->loaded()) { return; } + xsltFreeStylesheet(_stylesheet); + // No need to use xmlfreedoc(_parsedDoc), it's handled by xsltFreeStylesheet(_stylesheet); + return; +} + +SPDocument * XSLT::open(Inkscape::Extension::Input */*module*/, + gchar const *filename) +{ + xmlDocPtr filein = xmlParseFile(filename); + if (filein == nullptr) { return nullptr; } + + const char * params[1]; + params[0] = nullptr; + char *oldlocale = g_strdup(std::setlocale(LC_NUMERIC, nullptr)); + std::setlocale(LC_NUMERIC, "C"); + + xmlDocPtr result = xsltApplyStylesheet(_stylesheet, filein, params); + xmlFreeDoc(filein); + + Inkscape::XML::Document * rdoc = sp_repr_do_read( result, SP_SVG_NS_URI); + xmlFreeDoc(result); + std::setlocale(LC_NUMERIC, oldlocale); + g_free(oldlocale); + + if (rdoc == nullptr) { + return nullptr; + } + + if (strcmp(rdoc->root()->name(), "svg:svg") != 0) { + return nullptr; + } + + gchar * base = nullptr; + gchar * name = nullptr; + gchar * s = nullptr, * p = nullptr; + s = g_strdup(filename); + p = strrchr(s, '/'); + if (p) { + name = g_strdup(p + 1); + p[1] = '\0'; + base = g_strdup(s); + } else { + base = nullptr; + name = g_strdup(filename); + } + g_free(s); + + SPDocument * doc = SPDocument::createDoc(rdoc, filename, base, name, true, nullptr); + + g_free(base); g_free(name); + + return doc; +} + +void XSLT::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) +{ + /* TODO: Should we assume filename to be in utf8 or to be a raw filename? + * See JavaFXOutput::save for discussion. + * + * From JavaFXOutput::save (now removed): + * --- + * N.B. The name `filename_utf8' represents the fact that we want it to be in utf8; whereas in + * fact we know that some callers of Extension::save pass something in the filesystem's + * encoding, while others do g_filename_to_utf8 before calling. + * + * In terms of safety, it's best to make all callers pass actual filenames, since in general + * one can't round-trip from filename to utf8 back to the same filename. Whereas the argument + * for passing utf8 filenames is one of convenience: we often want to pass to g_warning or + * store as a string (rather than a byte stream) in XML, or the like. */ + g_return_if_fail(doc != nullptr); + g_return_if_fail(filename != nullptr); + + Inkscape::XML::Node *repr = doc->getReprRoot(); + + std::string tempfilename_out; + int tempfd_out = 0; + try { + tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX"); + } catch (...) { + /// \todo Popup dialog here + return; + } + + if (!sp_repr_save_rebased_file(repr->document(), tempfilename_out.c_str(), SP_SVG_NS_URI, + doc->getDocumentBase(), filename)) { + throw Inkscape::Extension::Output::save_failed(); + } + + xmlDocPtr svgdoc = xmlParseFile(tempfilename_out.c_str()); + close(tempfd_out); + if (svgdoc == nullptr) { + return; + } + + std::list<std::string> params; + module->paramListString(params); + const int max_parameters = params.size() * 2; + const char * xslt_params[max_parameters+1] ; + + int count = 0; + for(auto & param : params) { + std::size_t pos = param.find("="); + std::ostringstream parameter; + std::ostringstream value; + parameter << param.substr(2,pos-2); + value << param.substr(pos+1); + xslt_params[count++] = g_strdup_printf("%s", parameter.str().c_str()); + xslt_params[count++] = g_strdup_printf("'%s'", value.str().c_str()); + } + xslt_params[count] = nullptr; + + // workaround for inbox#2208 + char *oldlocale = g_strdup(std::setlocale(LC_NUMERIC, nullptr)); + std::setlocale(LC_NUMERIC, "C"); + xmlDocPtr newdoc = xsltApplyStylesheet(_stylesheet, svgdoc, xslt_params); + //xmlSaveFile(filename, newdoc); + int success = xsltSaveResultToFilename(filename, newdoc, _stylesheet, 0); + std::setlocale(LC_NUMERIC, oldlocale); + g_free(oldlocale); + + xmlFreeDoc(newdoc); + xmlFreeDoc(svgdoc); + + xsltCleanupGlobals(); + xmlCleanupParser(); + + if (success < 1) { + throw Inkscape::Extension::Output::save_failed(); + } + + return; +} + + +} /* Implementation */ +} /* module */ +} /* 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/implementation/xslt.h b/src/extension/implementation/xslt.h new file mode 100644 index 0000000..745d7f5 --- /dev/null +++ b/src/extension/implementation/xslt.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Code for handling XSLT extensions + * + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006-2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__ +#define __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__ + +#include "implementation.h" + +#include "libxml/tree.h" +#include "libxslt/xslt.h" +#include "libxslt/xsltInternals.h" + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +namespace Inkscape { +namespace Extension { +namespace Implementation { + +class XSLT : public Implementation { +private: + std::string _filename; + xmlDocPtr _parsedDoc; + xsltStylesheetPtr _stylesheet; + +public: + XSLT (); + + bool load(Inkscape::Extension::Extension *module) override; + void unload(Inkscape::Extension::Extension *module) override; + + bool check(Inkscape::Extension::Extension *module) override; + + SPDocument *open(Inkscape::Extension::Input *module, + gchar const *filename) override; + void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override; +}; + +} /* Inkscape */ +} /* Extension */ +} /* Implementation */ +#endif /* __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_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/extension/init.cpp b/src/extension/init.cpp new file mode 100644 index 0000000..5a95fb8 --- /dev/null +++ b/src/extension/init.cpp @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is what gets executed to initialize all of the modules. For + * the internal modules this involves executing their initialization + * functions, for external ones it involves reading their .spmodule + * files and bringing them into Sodipodi. + * + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2004 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 HAVE_POPPLER +# include "internal/pdfinput/pdf-input.h" +#endif + +#include "path-prefix.h" + +#include "inkscape.h" + +#include <glibmm/fileutils.h> +#include <glibmm/i18n.h> +#include <glibmm/ustring.h> + +#include "system.h" +#include "db.h" +#include "internal/svgz.h" +# include "internal/emf-inout.h" +# include "internal/emf-print.h" +# include "internal/wmf-inout.h" +# include "internal/wmf-print.h" + +#include <cairo.h> +#ifdef CAIRO_HAS_PDF_SURFACE +# include "internal/cairo-renderer-pdf-out.h" +#endif +#ifdef CAIRO_HAS_PS_SURFACE +# include "internal/cairo-ps-out.h" +#endif +#include "internal/pov-out.h" +#include "internal/odf.h" +#include "internal/latex-pstricks-out.h" +#include "internal/latex-pstricks.h" +#include "internal/gdkpixbuf-input.h" +#include "internal/bluredge.h" +#include "internal/gimpgrad.h" +#include "internal/grid.h" +#ifdef WITH_LIBWPG +#include "internal/wpg-input.h" +#endif +#ifdef WITH_LIBVISIO +#include "internal/vsd-input.h" +#endif +#ifdef WITH_LIBCDR +#include "internal/cdr-input.h" +#endif +#include "preferences.h" +#include "io/sys.h" +#include "io/resource.h" +#ifdef WITH_DBUS +#include "dbus/dbus-init.h" +#endif + +#ifdef WITH_MAGICK +#include <Magick++.h> +#include "internal/bitmap/adaptiveThreshold.h" +#include "internal/bitmap/addNoise.h" +#include "internal/bitmap/blur.h" +#include "internal/bitmap/channel.h" +#include "internal/bitmap/charcoal.h" +#include "internal/bitmap/colorize.h" +#include "internal/bitmap/contrast.h" +#include "internal/bitmap/crop.h" +#include "internal/bitmap/cycleColormap.h" +#include "internal/bitmap/despeckle.h" +#include "internal/bitmap/edge.h" +#include "internal/bitmap/emboss.h" +#include "internal/bitmap/enhance.h" +#include "internal/bitmap/equalize.h" +#include "internal/bitmap/gaussianBlur.h" +#include "internal/bitmap/implode.h" +#include "internal/bitmap/level.h" +#include "internal/bitmap/levelChannel.h" +#include "internal/bitmap/medianFilter.h" +#include "internal/bitmap/modulate.h" +#include "internal/bitmap/negate.h" +#include "internal/bitmap/normalize.h" +#include "internal/bitmap/oilPaint.h" +#include "internal/bitmap/opacity.h" +#include "internal/bitmap/raise.h" +#include "internal/bitmap/reduceNoise.h" +#include "internal/bitmap/sample.h" +#include "internal/bitmap/shade.h" +#include "internal/bitmap/sharpen.h" +#include "internal/bitmap/solarize.h" +#include "internal/bitmap/spread.h" +#include "internal/bitmap/swirl.h" +//#include "internal/bitmap/threshold.h" +#include "internal/bitmap/unsharpmask.h" +#include "internal/bitmap/wave.h" +#endif /* WITH_MAGICK */ + +#include "internal/filter/filter.h" + +#include "init.h" + +using namespace Inkscape::IO::Resource; + +namespace Inkscape { +namespace Extension { + +/** This is the extension that all files are that are pulled from + the extension directory and parsed */ +#define SP_MODULE_EXTENSION "inx" + +static void check_extensions(); + +/** + * \return none + * \brief Examines the given string preference and checks to see + * that at least one of the registered extensions matches + * it. If not, a default is assigned. + * \param pref_path Preference path to update + * \param pref_default Default string to set + * \param extension_family List of extensions to search + */ +static void +update_pref(Glib::ustring const &pref_path, + gchar const *pref_default) +{ + Glib::ustring pref = Inkscape::Preferences::get()->getString(pref_path); + if (!Inkscape::Extension::db.get( pref.data() ) /*missing*/) { + Inkscape::Preferences::get()->setString(pref_path, pref_default); + } +} + +/** + * Invokes the init routines for internal modules. + * + * This should be a list of all the internal modules that need to initialized. This is just a + * convenient place to put them. + */ +void +init() +{ + /* TODO: Change to Internal */ + Internal::Svg::init(); + Internal::Svgz::init(); + +#ifdef CAIRO_HAS_PDF_SURFACE + Internal::CairoRendererPdfOutput::init(); +#endif +#ifdef CAIRO_HAS_PS_SURFACE + Internal::CairoPsOutput::init(); + Internal::CairoEpsOutput::init(); +#endif +#ifdef HAVE_POPPLER + Internal::PdfInput::init(); +#endif + Internal::PrintEmf::init(); + Internal::Emf::init(); + Internal::PrintWmf::init(); + Internal::Wmf::init(); + Internal::PovOutput::init(); + Internal::OdfOutput::init(); + Internal::PrintLatex::init(); + Internal::LatexOutput::init(); +#ifdef WITH_LIBWPG + Internal::WpgInput::init(); +#endif +#ifdef WITH_LIBVISIO + Internal::VsdInput::init(); +#endif +#ifdef WITH_LIBCDR + Internal::CdrInput::init(); +#endif + + /* Effects */ + Internal::BlurEdge::init(); + Internal::GimpGrad::init(); + Internal::Grid::init(); + +#ifdef WITH_DBUS + Dbus::init(); +#endif + + /* Raster Effects */ +#ifdef WITH_MAGICK + Magick::InitializeMagick(NULL); + + Internal::Bitmap::AdaptiveThreshold::init(); + Internal::Bitmap::AddNoise::init(); + Internal::Bitmap::Blur::init(); + Internal::Bitmap::Channel::init(); + Internal::Bitmap::Charcoal::init(); + Internal::Bitmap::Colorize::init(); + Internal::Bitmap::Contrast::init(); + Internal::Bitmap::Crop::init(); + Internal::Bitmap::CycleColormap::init(); + Internal::Bitmap::Edge::init(); + Internal::Bitmap::Despeckle::init(); + Internal::Bitmap::Emboss::init(); + Internal::Bitmap::Enhance::init(); + Internal::Bitmap::Equalize::init(); + Internal::Bitmap::GaussianBlur::init(); + Internal::Bitmap::Implode::init(); + Internal::Bitmap::Level::init(); + Internal::Bitmap::LevelChannel::init(); + Internal::Bitmap::MedianFilter::init(); + Internal::Bitmap::Modulate::init(); + Internal::Bitmap::Negate::init(); + Internal::Bitmap::Normalize::init(); + Internal::Bitmap::OilPaint::init(); + Internal::Bitmap::Opacity::init(); + Internal::Bitmap::Raise::init(); + Internal::Bitmap::ReduceNoise::init(); + Internal::Bitmap::Sample::init(); + Internal::Bitmap::Shade::init(); + Internal::Bitmap::Sharpen::init(); + Internal::Bitmap::Solarize::init(); + Internal::Bitmap::Spread::init(); + Internal::Bitmap::Swirl::init(); + //Internal::Bitmap::Threshold::init(); + Internal::Bitmap::Unsharpmask::init(); + Internal::Bitmap::Wave::init(); +#endif /* WITH_MAGICK */ + + Internal::Filter::Filter::filters_all(); + + for(auto &filename: get_filenames(EXTENSIONS, {SP_MODULE_EXTENSION})) { + build_from_file(filename.c_str()); + } + + /* this is at the very end because it has several catch-alls + * that are possibly over-ridden by other extensions (such as + * svgz) + */ + Internal::GdkpixbufInput::init(); + + /* now we need to check and make sure everyone is happy */ + check_extensions(); + + /* This is a hack to deal with updating saved outdated module + * names in the prefs... + */ + update_pref("/dialogs/save_as/default", + SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE + // Inkscape::Extension::db.get_output_list() + ); +} + +static void +check_extensions_internal(Extension *in_plug, gpointer in_data) +{ + int *count = (int *)in_data; + + if (in_plug == nullptr) return; + if (!in_plug->deactivated() && !in_plug->check()) { + in_plug->deactivate(); + (*count)++; + } +} + +static void check_extensions() +{ + int count = 1; + + Inkscape::Extension::Extension::error_file_open(); + while (count != 0) { + count = 0; + db.foreach(check_extensions_internal, (gpointer)&count); + } + Inkscape::Extension::Extension::error_file_close(); +} + +} } /* namespace Inkscape::Extension */ + + +/* + 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/extension/init.h b/src/extension/init.h new file mode 100644 index 0000000..328ee89 --- /dev/null +++ b/src/extension/init.h @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is what gets executed to initialize all of the modules. For + * the internal modules this invovles executing their initialization + * functions, for external ones it involves reading their .spmodule + * files and bringing them into Sodipodi. + * + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_INIT_H__ +#define INKSCAPE_EXTENSION_INIT_H__ + +namespace Inkscape { +namespace Extension { + +void init (); + +} } /* namespace Inkscape::Extension */ + +#endif /* INKSCAPE_EXTENSION_INIT_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/extension/input.cpp b/src/extension/input.cpp new file mode 100644 index 0000000..cf02c27 --- /dev/null +++ b/src/extension/input.cpp @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "input.h" + +#include "timer.h" + +#include "implementation/implementation.h" + +#include "prefdialog/prefdialog.h" + +#include "xml/repr.h" + + +/* Inkscape::Extension::Input */ + +namespace Inkscape { +namespace Extension { + +/** + \return None + \brief Builds a SPModuleInput object from a XML description + \param module The module to be initialized + \param repr The XML description in a Inkscape::XML::Node tree + + Okay, so you want to build a SPModuleInput object. + + This function first takes and does the build of the parent class, + which is SPModule. Then, it looks for the <input> section of the + XML description. Under there should be several fields which + describe the input module to excruciating detail. Those are parsed, + copied, and put into the structure that is passed in as module. + Overall, there are many levels of indentation, just to handle the + levels of indentation in the XML file. +*/ +Input::Input (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory) + : Extension(in_repr, in_imp, base_directory) +{ + mimetype = nullptr; + extension = nullptr; + filetypename = nullptr; + filetypetooltip = nullptr; + output_extension = nullptr; + + if (repr != nullptr) { + Inkscape::XML::Node * child_repr; + + child_repr = repr->firstChild(); + + while (child_repr != nullptr) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "input")) { + child_repr = child_repr->firstChild(); + while (child_repr != nullptr) { + char const * chname = child_repr->name(); + if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + chname += strlen(INKSCAPE_EXTENSION_NS); + } + if (chname[0] == '_') /* Allow _ for translation of tags */ + chname++; + if (!strcmp(chname, "extension")) { + g_free (extension); + extension = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "mimetype")) { + g_free (mimetype); + mimetype = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "filetypename")) { + g_free (filetypename); + filetypename = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "filetypetooltip")) { + g_free (filetypetooltip); + filetypetooltip = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "output_extension")) { + g_free (output_extension); + output_extension = g_strdup(child_repr->firstChild()->content()); + } + + child_repr = child_repr->next(); + } + + break; + } + + child_repr = child_repr->next(); + } + + } + + return; +} + +/** + \return None + \brief Destroys an Input extension +*/ +Input::~Input () +{ + g_free(mimetype); + g_free(extension); + g_free(filetypename); + g_free(filetypetooltip); + g_free(output_extension); + return; +} + +/** + \return Whether this extension checks out + \brief Validate this extension + + This function checks to make sure that the input extension has + a filename extension and a MIME type. Then it calls the parent + class' check function which also checks out the implementation. +*/ +bool +Input::check () +{ + if (extension == nullptr) + return FALSE; + if (mimetype == nullptr) + return FALSE; + + return Extension::check(); +} + +/** + \return A new document + \brief This function creates a document from a file + \param uri The filename to create the document from + + This function acts as the first step in creating a new document + from a file. The first thing that this does is make sure that the + file actually exists. If it doesn't, a NULL is returned. If the + file exits, then it is opened using the implementation of this extension. +*/ +SPDocument * +Input::open (const gchar *uri) +{ + if (!loaded()) { + set_state(Extension::STATE_LOADED); + } + if (!loaded()) { + return nullptr; + } + timer->touch(); + + SPDocument *const doc = imp->open(this, uri); + + return doc; +} + +/** + \return IETF mime-type for the extension + \brief Get the mime-type that describes this extension +*/ +gchar * +Input::get_mimetype() +{ + return mimetype; +} + +/** + \return Filename extension for the extension + \brief Get the filename extension for this extension +*/ +gchar * +Input::get_extension() +{ + return extension; +} + +/** + \return The name of the filetype supported + \brief Get the name of the filetype supported +*/ +const char * +Input::get_filetypename(bool translated) +{ + const char *name; + + if (filetypename) + name = filetypename; + else + name = get_name(); + + if (name && translated) { + return get_translation(name); + } else { + return name; + } +} + +/** + \return Tooltip giving more information on the filetype + \brief Get the tooltip for more information on the filetype +*/ +const char * +Input::get_filetypetooltip(bool translated) +{ + if (filetypetooltip && translated) { + return get_translation(filetypetooltip); + } else { + return filetypetooltip; + } +} + +/** + \return A dialog to get settings for this extension + \brief Create a dialog for preference for this extension + + Calls the implementation to get the preferences. +*/ +bool +Input::prefs (const gchar *uri) +{ + if (!loaded()) { + set_state(Extension::STATE_LOADED); + } + if (!loaded()) { + return false; + } + + Gtk::Widget * controls; + controls = imp->prefs_input(this, uri); + if (controls == nullptr) { + // std::cout << "No preferences for Input" << std::endl; + return true; + } + + Glib::ustring name = get_translation(this->get_name()); + PrefDialog *dialog = new PrefDialog(name, controls); + int response = dialog->run(); + dialog->hide(); + + delete dialog; + + return (response == Gtk::RESPONSE_OK); +} + +} } /* namespace Inkscape, Extension */ + +/* + 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/extension/input.h b/src/extension/input.h new file mode 100644 index 0000000..5952c3c --- /dev/null +++ b/src/extension/input.h @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +#ifndef INKSCAPE_EXTENSION_INPUT_H__ +#define INKSCAPE_EXTENSION_INPUT_H__ + +#include <exception> +#include <glib.h> +#include "extension.h" + +class SPDocument; + +namespace Inkscape { +namespace Extension { + +class Input : public Extension { + gchar *mimetype; /**< What is the mime type this inputs? */ + gchar *extension; /**< The extension of the input files */ + gchar *filetypename; /**< A userfriendly name for the file type */ + gchar *filetypetooltip; /**< A more detailed description of the filetype */ + +public: /* this is a hack for this release, this will be private shortly */ + gchar *output_extension; /**< Setting of what output extension should be used */ + +public: + struct open_failed : public std::exception { + ~open_failed() noexcept override = default; + const char *what() const noexcept override { return "Open failed"; } + }; + struct no_extension_found : public std::exception { + ~no_extension_found() noexcept override = default; + const char *what() const noexcept override { return "No suitable input extension found"; } + }; + struct open_cancelled : public std::exception { + ~open_cancelled() noexcept override = default; + const char *what() const noexcept override { return "Open was cancelled"; } + }; + + Input(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory); + ~Input() override; + + bool check() override; + + SPDocument * open (gchar const *uri); + gchar * get_mimetype (); + gchar * get_extension (); + const char * get_filetypename (bool translated=false); + const char * get_filetypetooltip (bool translated=false); + bool prefs (gchar const *uri); +}; + +} } /* namespace Inkscape, Extension */ +#endif /* INKSCAPE_EXTENSION_INPUT_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/extension/internal/bitmap/adaptiveThreshold.cpp b/src/extension/internal/bitmap/adaptiveThreshold.cpp new file mode 100644 index 0000000..ce4d78a --- /dev/null +++ b/src/extension/internal/bitmap/adaptiveThreshold.cpp @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "adaptiveThreshold.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +AdaptiveThreshold::applyEffect(Magick::Image *image) { + image->adaptiveThreshold(_width, _height); +} + +void +AdaptiveThreshold::refreshParameters(Inkscape::Extension::Effect *module) { + _width = module->get_param_int("width"); + _height = module->get_param_int("height"); + _offset = module->get_param_int("offset"); +} + +#include "../clear-n_.h" + +void +AdaptiveThreshold::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Adaptive Threshold") "</name>\n" + "<id>org.inkscape.effect.bitmap.adaptiveThreshold</id>\n" + "<param name=\"width\" gui-text=\"" N_("Width:") "\" type=\"int\" min=\"-100\" max=\"100\">5</param>\n" + "<param name=\"height\" gui-text=\"" N_("Height:") "\" type=\"int\" min=\"-100\" max=\"100\">5</param>\n" + "<param name=\"offset\" gui-text=\"" N_("Offset:") "\" type=\"int\" min=\"0\" max=\"100\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Apply adaptive thresholding to selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new AdaptiveThreshold()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/adaptiveThreshold.h b/src/extension/internal/bitmap/adaptiveThreshold.h new file mode 100644 index 0000000..066f13b --- /dev/null +++ b/src/extension/internal/bitmap/adaptiveThreshold.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class AdaptiveThreshold : public ImageMagick +{ +private: + unsigned int _width; + unsigned int _height; + unsigned _offset; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/addNoise.cpp b/src/extension/internal/bitmap/addNoise.cpp new file mode 100644 index 0000000..26b050f --- /dev/null +++ b/src/extension/internal/bitmap/addNoise.cpp @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "addNoise.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +AddNoise::applyEffect(Magick::Image *image) { + Magick::NoiseType noiseType = Magick::UniformNoise; + if (!strcmp(_noiseTypeName, "Uniform Noise")) noiseType = Magick::UniformNoise; + else if (!strcmp(_noiseTypeName, "Gaussian Noise")) noiseType = Magick::GaussianNoise; + else if (!strcmp(_noiseTypeName, "Multiplicative Gaussian Noise")) noiseType = Magick::MultiplicativeGaussianNoise; + else if (!strcmp(_noiseTypeName, "Impulse Noise")) noiseType = Magick::ImpulseNoise; + else if (!strcmp(_noiseTypeName, "Laplacian Noise")) noiseType = Magick::LaplacianNoise; + else if (!strcmp(_noiseTypeName, "Poisson Noise")) noiseType = Magick::PoissonNoise; + + image->addNoise(noiseType); +} + +void +AddNoise::refreshParameters(Inkscape::Extension::Effect *module) { + _noiseTypeName = module->get_param_optiongroup("noiseType"); +} + +#include "../clear-n_.h" + +void +AddNoise::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Add Noise") "</name>\n" + "<id>org.inkscape.effect.bitmap.addNoise</id>\n" + "<param name=\"noiseType\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value='Uniform Noise'>" N_("Uniform Noise") "</option>\n" + "<option value='Gaussian Noise'>" N_("Gaussian Noise") "</option>\n" + "<option value='Multiplicative Gaussian Noise'>" N_("Multiplicative Gaussian Noise") "</option>\n" + "<option value='Impulse Noise'>" N_("Impulse Noise") "</option>\n" + "<option value='Laplacian Noise'>" N_("Laplacian Noise") "</option>\n" + "<option value='Poisson Noise'>" N_("Poisson Noise") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Add random noise to selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new AddNoise()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/addNoise.h b/src/extension/internal/bitmap/addNoise.h new file mode 100644 index 0000000..06ce8c3 --- /dev/null +++ b/src/extension/internal/bitmap/addNoise.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class AddNoise : public ImageMagick +{ +private: + const gchar* _noiseTypeName; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/blur.cpp b/src/extension/internal/bitmap/blur.cpp new file mode 100644 index 0000000..b50511c --- /dev/null +++ b/src/extension/internal/bitmap/blur.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "blur.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Blur::applyEffect(Magick::Image *image) { + image->blur(_radius, _sigma); +} + +void +Blur::refreshParameters(Inkscape::Extension::Effect *module) { + _radius = module->get_param_float("radius"); + _sigma = module->get_param_float("sigma"); +} + +#include "../clear-n_.h" + +void +Blur::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Blur") "</name>\n" + "<id>org.inkscape.effect.bitmap.blur</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">1</param>\n" + "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"100\">0.5</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Blur selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Blur()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/blur.h b/src/extension/internal/bitmap/blur.h new file mode 100644 index 0000000..da93a42 --- /dev/null +++ b/src/extension/internal/bitmap/blur.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Blur : public ImageMagick +{ +private: + float _radius; + float _sigma; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/channel.cpp b/src/extension/internal/bitmap/channel.cpp new file mode 100644 index 0000000..f26a9f9 --- /dev/null +++ b/src/extension/internal/bitmap/channel.cpp @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "channel.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Channel::applyEffect(Magick::Image *image) { + Magick::ChannelType layer = Magick::UndefinedChannel; + if (!strcmp(_layerName, "Red Channel")) layer = Magick::RedChannel; + else if (!strcmp(_layerName, "Green Channel")) layer = Magick::GreenChannel; + else if (!strcmp(_layerName, "Blue Channel")) layer = Magick::BlueChannel; + else if (!strcmp(_layerName, "Cyan Channel")) layer = Magick::CyanChannel; + else if (!strcmp(_layerName, "Magenta Channel")) layer = Magick::MagentaChannel; + else if (!strcmp(_layerName, "Yellow Channel")) layer = Magick::YellowChannel; + else if (!strcmp(_layerName, "Black Channel")) layer = Magick::BlackChannel; + else if (!strcmp(_layerName, "Opacity Channel")) layer = Magick::OpacityChannel; + else if (!strcmp(_layerName, "Matte Channel")) layer = Magick::MatteChannel; + + image->channel(layer); +} + +void +Channel::refreshParameters(Inkscape::Extension::Effect *module) { + _layerName = module->get_param_optiongroup("layer"); +} + +#include "../clear-n_.h" + +void +Channel::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Channel") "</name>\n" + "<id>org.inkscape.effect.bitmap.channel</id>\n" + "<param name=\"layer\" gui-text=\"" N_("Layer:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value='Red Channel'>" N_("Red Channel") "</option>\n" + "<option value='Green Channel'>" N_("Green Channel") "</option>\n" + "<option value='Blue Channel'>" N_("Blue Channel") "</option>\n" + "<option value='Cyan Channel'>" N_("Cyan Channel") "</option>\n" + "<option value='Magenta Channel'>" N_("Magenta Channel") "</option>\n" + "<option value='Yellow Channel'>" N_("Yellow Channel") "</option>\n" + "<option value='Black Channel'>" N_("Black Channel") "</option>\n" + "<option value='Opacity Channel'>" N_("Opacity Channel") "</option>\n" + "<option value='Matte Channel'>" N_("Matte Channel") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Extract specific channel from image") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Channel()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/channel.h b/src/extension/internal/bitmap/channel.h new file mode 100644 index 0000000..e215344 --- /dev/null +++ b/src/extension/internal/bitmap/channel.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Channel : public ImageMagick { + +private: + const gchar * _layerName; + +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/charcoal.cpp b/src/extension/internal/bitmap/charcoal.cpp new file mode 100644 index 0000000..4da2cba --- /dev/null +++ b/src/extension/internal/bitmap/charcoal.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "charcoal.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Charcoal::applyEffect(Magick::Image* image) { + image->charcoal(_radius, _sigma); +} + +void +Charcoal::refreshParameters(Inkscape::Extension::Effect* module) { + _radius = module->get_param_float("radius"); + _sigma = module->get_param_float("sigma"); +} + +#include "../clear-n_.h" + +void +Charcoal::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Charcoal") "</name>\n" + "<id>org.inkscape.effect.bitmap.charcoal</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">1</param>\n" + "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"100\">0.5</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Apply charcoal stylization to selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Charcoal()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/charcoal.h b/src/extension/internal/bitmap/charcoal.h new file mode 100644 index 0000000..c225746 --- /dev/null +++ b/src/extension/internal/bitmap/charcoal.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Charcoal : public ImageMagick +{ +private: + float _radius; + float _sigma; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/colorize.cpp b/src/extension/internal/bitmap/colorize.cpp new file mode 100644 index 0000000..2650909 --- /dev/null +++ b/src/extension/internal/bitmap/colorize.cpp @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "colorize.h" + +#include "color.h" + +#include <iostream> +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Colorize::applyEffect(Magick::Image *image) { + float r = ((_color >> 24) & 0xff) / 255.0F; + float g = ((_color >> 16) & 0xff) / 255.0F; + float b = ((_color >> 8) & 0xff) / 255.0F; + float a = ((_color ) & 0xff) / 255.0F; + + Magick::ColorRGB mc(r,g,b); + + image->colorize(a * 100, mc); +} + +void +Colorize::refreshParameters(Inkscape::Extension::Effect *module) { + _color = module->get_param_color("color"); +} + +#include "../clear-n_.h" + +void +Colorize::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Colorize") "</name>\n" + "<id>org.inkscape.effect.bitmap.colorize</id>\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Colorize selected bitmap(s) with specified color, using given opacity") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Colorize()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/colorize.h b/src/extension/internal/bitmap/colorize.h new file mode 100644 index 0000000..7c16ef5 --- /dev/null +++ b/src/extension/internal/bitmap/colorize.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Colorize : public ImageMagick { +private: + unsigned int _opacity; + guint32 _color; + +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/contrast.cpp b/src/extension/internal/bitmap/contrast.cpp new file mode 100644 index 0000000..64d2167 --- /dev/null +++ b/src/extension/internal/bitmap/contrast.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "contrast.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Contrast::applyEffect(Magick::Image *image) { + // the contrast method's argument seems to be binary, so we perform it multiple times + // to get the desired level of effect + for (unsigned int i = 0; i < _sharpen; i ++) + image->contrast(1); +} + +void +Contrast::refreshParameters(Inkscape::Extension::Effect *module) { + _sharpen = module->get_param_int("sharpen"); +} + +#include "../clear-n_.h" + +void +Contrast::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Contrast") "</name>\n" + "<id>org.inkscape.effect.bitmap.contrast</id>\n" + "<param name=\"sharpen\" gui-text=\"" N_("Adjust:") "\" type=\"int\" min=\"0\" max=\"10\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Increase or decrease contrast in bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Contrast()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/contrast.h b/src/extension/internal/bitmap/contrast.h new file mode 100644 index 0000000..c0e95e4 --- /dev/null +++ b/src/extension/internal/bitmap/contrast.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Contrast : public ImageMagick +{ +private: + unsigned int _sharpen; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/crop.cpp b/src/extension/internal/bitmap/crop.cpp new file mode 100644 index 0000000..c69b402 --- /dev/null +++ b/src/extension/internal/bitmap/crop.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 Authors: + * Nicolas Dufour <nicoduf@yahoo.fr> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "2geom/transforms.h" +#include "extension/effect.h" +#include "extension/system.h" + +#include "crop.h" +#include "selection-chemistry.h" +#include "object/sp-item.h" +#include "object/sp-item-transform.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Crop::applyEffect(Magick::Image *image) { + int width = image->baseColumns() - (_left + _right); + int height = image->baseRows() - (_top + _bottom); + if (width > 0 and height > 0) { + image->crop(Magick::Geometry(width, height, _left, _top, false, false)); + image->page("+0+0"); + } +} + +void +Crop::postEffect(Magick::Image *image, SPItem *item) { + + // Scale bbox + Geom::Scale scale (0,0); + scale = Geom::Scale(image->columns() / (double) image->baseColumns(), + image->rows() / (double) image->baseRows()); + item->scale_rel(scale); + + // Translate proportionaly to the image/bbox ratio + Geom::OptRect bbox(item->desktopGeometricBounds()); + //g_warning("bbox. W:%f, H:%f, X:%f, Y:%f", bbox->dimensions()[Geom::X], bbox->dimensions()[Geom::Y], bbox->min()[Geom::X], bbox->min()[Geom::Y]); + + Geom::Translate translate (0,0); + translate = Geom::Translate(((_left - _right) / 2.0) * (bbox->dimensions()[Geom::X] / (double) image->columns()), + ((_bottom - _top) / 2.0) * (bbox->dimensions()[Geom::Y] / (double) image->rows())); + item->move_rel(translate); +} + +void +Crop::refreshParameters(Inkscape::Extension::Effect *module) { + _top = module->get_param_int("top"); + _bottom = module->get_param_int("bottom"); + _left = module->get_param_int("left"); + _right = module->get_param_int("right"); +} + +#include "../clear-n_.h" + +void +Crop::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Crop") "</name>\n" + "<id>org.inkscape.effect.bitmap.crop</id>\n" + "<param name=\"top\" gui-text=\"" N_("Top (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n" + "<param name=\"bottom\" gui-text=\"" N_("Bottom (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n" + "<param name=\"left\" gui-text=\"" N_("Left (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n" + "<param name=\"right\" gui-text=\"" N_("Right (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Crop selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Crop()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/crop.h b/src/extension/internal/bitmap/crop.h new file mode 100644 index 0000000..da53878 --- /dev/null +++ b/src/extension/internal/bitmap/crop.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2010 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * Nicolas Dufour <nicoduf@yahoo.fr> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Crop : public ImageMagick +{ +private: + int _top; + int _bottom; + int _left; + int _right; +public: + void applyEffect(Magick::Image *image) override; + void postEffect(Magick::Image *image, SPItem *item) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/cycleColormap.cpp b/src/extension/internal/bitmap/cycleColormap.cpp new file mode 100644 index 0000000..04741f9 --- /dev/null +++ b/src/extension/internal/bitmap/cycleColormap.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "cycleColormap.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +CycleColormap::applyEffect(Magick::Image *image) { + image->cycleColormap(_amount); +} + +void +CycleColormap::refreshParameters(Inkscape::Extension::Effect *module) { + _amount = module->get_param_int("amount"); +} + +#include "../clear-n_.h" + +void +CycleColormap::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Cycle Colormap") "</name>\n" + "<id>org.inkscape.effect.bitmap.cycleColormap</id>\n" + "<param name=\"amount\" gui-text=\"" N_("Amount:") "\" type=\"int\" min=\"0\" max=\"360\">180</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Cycle colormap(s) of selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new CycleColormap()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/cycleColormap.h b/src/extension/internal/bitmap/cycleColormap.h new file mode 100644 index 0000000..0d66b15 --- /dev/null +++ b/src/extension/internal/bitmap/cycleColormap.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class CycleColormap : public ImageMagick { +private: + int _amount; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/despeckle.cpp b/src/extension/internal/bitmap/despeckle.cpp new file mode 100644 index 0000000..7a3069d --- /dev/null +++ b/src/extension/internal/bitmap/despeckle.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "despeckle.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Despeckle::applyEffect(Magick::Image *image) { + image->despeckle(); +} + +void +Despeckle::refreshParameters(Inkscape::Extension::Effect */*module*/) { +} + +#include "../clear-n_.h" + +void +Despeckle::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Despeckle") "</name>\n" + "<id>org.inkscape.effect.bitmap.despeckle</id>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Reduce speckle noise of selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Despeckle()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/despeckle.h b/src/extension/internal/bitmap/despeckle.h new file mode 100644 index 0000000..0c731ee --- /dev/null +++ b/src/extension/internal/bitmap/despeckle.h @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Despeckle : public ImageMagick { +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/edge.cpp b/src/extension/internal/bitmap/edge.cpp new file mode 100644 index 0000000..58bceec --- /dev/null +++ b/src/extension/internal/bitmap/edge.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "edge.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Edge::applyEffect(Magick::Image *image) { + image->edge(_radius); +} + +void +Edge::refreshParameters(Inkscape::Extension::Effect *module) { + _radius = module->get_param_int("radius"); +} + +#include "../clear-n_.h" + +void +Edge::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Edge") "</name>\n" + "<id>org.inkscape.effect.bitmap.edge</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"int\" min=\"0\" max=\"100\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Highlight edges of selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Edge()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/edge.h b/src/extension/internal/bitmap/edge.h new file mode 100644 index 0000000..2c5fe14 --- /dev/null +++ b/src/extension/internal/bitmap/edge.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Edge : public ImageMagick { +private: + unsigned int _radius; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/emboss.cpp b/src/extension/internal/bitmap/emboss.cpp new file mode 100644 index 0000000..b7f2235 --- /dev/null +++ b/src/extension/internal/bitmap/emboss.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "emboss.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Emboss::applyEffect(Magick::Image *image) { + image->emboss(_radius, _sigma); +} + +void +Emboss::refreshParameters(Inkscape::Extension::Effect *module) { + _radius = module->get_param_float("radius"); + _sigma = module->get_param_float("sigma"); +} + +#include "../clear-n_.h" + +void +Emboss::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Emboss") "</name>\n" + "<id>org.inkscape.effect.bitmap.emboss</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">1.0</param>\n" + "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"-50\" max=\"50\">0.5</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Emboss selected bitmap(s); highlight edges with 3D effect") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Emboss()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/emboss.h b/src/extension/internal/bitmap/emboss.h new file mode 100644 index 0000000..18b91bb --- /dev/null +++ b/src/extension/internal/bitmap/emboss.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Emboss : public ImageMagick +{ +private: + float _radius; + float _sigma; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/enhance.cpp b/src/extension/internal/bitmap/enhance.cpp new file mode 100644 index 0000000..aff9faf --- /dev/null +++ b/src/extension/internal/bitmap/enhance.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "enhance.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Enhance::applyEffect(Magick::Image *image) { + image->enhance(); +} + +void +Enhance::refreshParameters(Inkscape::Extension::Effect */*module*/) { } + +#include "../clear-n_.h" + +void +Enhance::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Enhance") "</name>\n" + "<id>org.inkscape.effect.bitmap.enhance</id>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Enhance selected bitmap(s); minimize noise") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Enhance()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/enhance.h b/src/extension/internal/bitmap/enhance.h new file mode 100644 index 0000000..dd3d9ff --- /dev/null +++ b/src/extension/internal/bitmap/enhance.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Enhance : public ImageMagick +{ +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/equalize.cpp b/src/extension/internal/bitmap/equalize.cpp new file mode 100644 index 0000000..423b88b --- /dev/null +++ b/src/extension/internal/bitmap/equalize.cpp @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "equalize.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Equalize::applyEffect(Magick::Image *image) { + image->equalize(); +} + +void +Equalize::refreshParameters(Inkscape::Extension::Effect */*module*/) { } + +#include "../clear-n_.h" + +void +Equalize::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Equalize") "</name>\n" + "<id>org.inkscape.effect.bitmap.equalize</id>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Equalize selected bitmap(s); histogram equalization") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Equalize()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/equalize.h b/src/extension/internal/bitmap/equalize.h new file mode 100644 index 0000000..8259ffb --- /dev/null +++ b/src/extension/internal/bitmap/equalize.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Equalize : public ImageMagick +{ +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init (); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/gaussianBlur.cpp b/src/extension/internal/bitmap/gaussianBlur.cpp new file mode 100644 index 0000000..405ee66 --- /dev/null +++ b/src/extension/internal/bitmap/gaussianBlur.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "gaussianBlur.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +GaussianBlur::applyEffect(Magick::Image* image) { + image->gaussianBlur(_width, _sigma); +} + +void +GaussianBlur::refreshParameters(Inkscape::Extension::Effect* module) { + _width = module->get_param_float("width"); + _sigma = module->get_param_float("sigma"); +} + +#include "../clear-n_.h" + +void +GaussianBlur::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Gaussian Blur") "</name>\n" + "<id>org.inkscape.effect.bitmap.gaussianBlur</id>\n" + "<param name=\"width\" gui-text=\"" N_("Factor:") "\" type=\"float\" min=\"0\" max=\"100\">5.0</param>\n" + "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"100\">5.0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Gaussian blur selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new GaussianBlur()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/gaussianBlur.h b/src/extension/internal/bitmap/gaussianBlur.h new file mode 100644 index 0000000..3cd3fb8 --- /dev/null +++ b/src/extension/internal/bitmap/gaussianBlur.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class GaussianBlur : public ImageMagick +{ +private: + float _width; + float _sigma; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/imagemagick.cpp b/src/extension/internal/bitmap/imagemagick.cpp new file mode 100644 index 0000000..47940a5 --- /dev/null +++ b/src/extension/internal/bitmap/imagemagick.cpp @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <libintl.h> + +#include <gtkmm/box.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/spinbutton.h> + +#include <glib/gstdio.h> + +#include "desktop.h" + +#include "selection.h" + +#include "extension/effect.h" +#include "extension/system.h" + +#include "imagemagick.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class ImageMagickDocCache: public Inkscape::Extension::Implementation::ImplementationDocumentCache { + friend class ImageMagick; +private: + void readImage(char const *xlink, char const *id, Magick::Image *image); +protected: + Inkscape::XML::Node** _nodes; + + Magick::Image** _images; + int _imageCount; + char** _caches; + unsigned* _cacheLengths; + const char** _originals; + SPItem** _imageItems; +public: + ImageMagickDocCache(Inkscape::UI::View::View * view); + ~ImageMagickDocCache ( ) override; +}; + +ImageMagickDocCache::ImageMagickDocCache(Inkscape::UI::View::View * view) : + Inkscape::Extension::Implementation::ImplementationDocumentCache(view), + _nodes(NULL), + _images(NULL), + _imageCount(0), + _caches(NULL), + _cacheLengths(NULL), + _originals(NULL), + _imageItems(NULL) +{ + SPDesktop *desktop = (SPDesktop*)view; + auto selectedItemList = desktop->selection->items(); + int selectCount = (int) boost::distance(selectedItemList); + + // Init the data-holders + _nodes = new Inkscape::XML::Node*[selectCount]; + _originals = new const char*[selectCount]; + _caches = new char*[selectCount]; + _cacheLengths = new unsigned int[selectCount]; + _images = new Magick::Image*[selectCount]; + _imageCount = 0; + _imageItems = new SPItem*[selectCount]; + + // Loop through selected items + for (auto i = selectedItemList.begin(); i != selectedItemList.end(); ++i) { + SPItem *item = *i; + Inkscape::XML::Node *node = reinterpret_cast<Inkscape::XML::Node *>(item->getRepr()); + if (!strcmp(node->name(), "image") || !strcmp(node->name(), "svg:image")) + { + _nodes[_imageCount] = node; + char const *xlink = node->attribute("xlink:href"); + char const *id = node->attribute("id"); + _originals[_imageCount] = xlink; + _caches[_imageCount] = (char*)""; + _cacheLengths[_imageCount] = 0; + _images[_imageCount] = new Magick::Image(); + readImage(xlink, id, _images[_imageCount]); + _imageItems[_imageCount] = item; + _imageCount++; + } + } +} + +ImageMagickDocCache::~ImageMagickDocCache ( ) { + if (_nodes) + delete _nodes; + if (_originals) + delete _originals; + if (_caches) + delete _caches; + if (_cacheLengths) + delete _cacheLengths; + if (_images) + delete _images; + if (_imageItems) + delete _imageItems; + return; +} + +void +ImageMagickDocCache::readImage(const char *xlink, const char *id, Magick::Image *image) +{ + // Find if the xlink:href is base64 data, i.e. if the image is embedded + gchar *search = g_strndup(xlink, 30); + if (strstr(search, "base64") != (char*)NULL) { + // 7 = strlen("base64") + strlen(",") + const char* pureBase64 = strstr(xlink, "base64") + 7; + Magick::Blob blob; + blob.base64(pureBase64); + try { + image->read(blob); + } catch (Magick::Exception &error_) { + g_warning("ImageMagick could not read '%s'\nDetails: %s", id, error_.what()); + } + } else { + gchar *path; + if (strncmp (xlink,"file:", 5) == 0) { + path = g_filename_from_uri(xlink, NULL, NULL); + } else { + path = g_strdup(xlink); + } + try { + image->read(path); + } catch (Magick::Exception &error_) { + g_warning("ImageMagick could not read '%s' from '%s'\nDetails: %s", id, path, error_.what()); + } + g_free(path); + } + g_free(search); +} + +bool +ImageMagick::load(Inkscape::Extension::Extension */*module*/) +{ + return true; +} + +Inkscape::Extension::Implementation::ImplementationDocumentCache * +ImageMagick::newDocCache (Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view) { + return new ImageMagickDocCache(view); +} + +void +ImageMagick::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) +{ + refreshParameters(module); + + if (docCache == NULL) { // should never happen + docCache = newDocCache(module, document); + } + ImageMagickDocCache * dc = dynamic_cast<ImageMagickDocCache *>(docCache); + if (dc == NULL) { // should really never happen + printf("AHHHHHHHHH!!!!!"); + exit(1); + } + + for (int i = 0; i < dc->_imageCount; i++) + { + try + { + Magick::Image effectedImage = *dc->_images[i]; // make a copy + + applyEffect(&effectedImage); + + // postEffect can be used to change things on the item itself + // e.g. resize the image element, after the effecti is applied + postEffect(&effectedImage, dc->_imageItems[i]); + +// dc->_nodes[i]->setAttribute("xlink:href", dc->_caches[i], true); + + Magick::Blob *blob = new Magick::Blob(); + effectedImage.write(blob); + + std::string raw_string = blob->base64(); + const int raw_len = raw_string.length(); + const char *raw_i = raw_string.c_str(); + + unsigned new_len = (int)(raw_len * (77.0 / 76.0) + 100); + if (new_len > dc->_cacheLengths[i]) { + dc->_cacheLengths[i] = (int)(new_len * 1.2); + dc->_caches[i] = new char[dc->_cacheLengths[i]]; + } + char *formatted_i = dc->_caches[i]; + const char *src; + + for (src = "data:image/"; *src; ) + *formatted_i++ = *src++; + for (src = effectedImage.magick().c_str(); *src ; ) + *formatted_i++ = *src++; + for (src = ";base64, \n" ; *src; ) + *formatted_i++ = *src++; + + int col = 0; + while (*raw_i) { + *formatted_i++ = *raw_i++; + if (col++ > 76) { + *formatted_i++ = '\n'; + col = 0; + } + } + if (col) { + *formatted_i++ = '\n'; + } + *formatted_i = '\0'; + + dc->_nodes[i]->setAttribute("xlink:href", dc->_caches[i], true); + dc->_nodes[i]->removeAttribute("sodipodi:absref"); + delete blob; + } + catch (Magick::Exception &error_) { + printf("Caught exception: %s \n", error_.what()); + } + + //while(Gtk::Main::events_pending()) { + // Gtk::Main::iteration(); + //} + } +} + +/** \brief A function to get the prefences for the grid + \param moudule Module which holds the params + \param view Unused today - may get style information in the future. + + Uses AutoGUI for creating the GUI. +*/ +Gtk::Widget * +ImageMagick::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + SPDocument * current_document = view->doc(); + + auto selected = ((SPDesktop *) view)->getSelection()->items(); + Inkscape::XML::Node * first_select = NULL; + if (!selected.empty()) { + first_select = (selected.front())->getRepr(); + } + + return module->autogui(current_document, first_select, changeSignal); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/imagemagick.h b/src/extension/internal/bitmap/imagemagick.h new file mode 100644 index 0000000..754e195 --- /dev/null +++ b/src/extension/internal/bitmap/imagemagick.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INKSCAPE_EXTENSION_INTERNAL_BITMAP_IMAGEMAGICK_H +#define INKSCAPE_EXTENSION_INTERNAL_BITMAP_IMAGEMAGICK_H + +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/implementation/implementation.h" + +class SPItem; + +namespace Magick { +class Image; +} + +namespace Inkscape { +namespace Extension { + +class Effect; +class Extension; + +namespace Internal { +namespace Bitmap { + +class ImageMagick : public Inkscape::Extension::Implementation::Implementation { +public: + /* Functions to be implemented by subclasses */ + virtual void applyEffect(Magick::Image */*image*/) { }; + virtual void refreshParameters(Inkscape::Extension::Effect */*module*/) { }; + virtual void postEffect(Magick::Image */*image*/, SPItem */*item*/) { }; + + /* Functions implemented from ::Implementation */ + bool load(Inkscape::Extension::Extension *module) override; + Inkscape::Extension::Implementation::ImplementationDocumentCache * newDocCache (Inkscape::Extension::Extension * ext, Inkscape::UI::View::View * doc) override; + void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + Gtk::Widget* prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +#endif // INKSCAPE_EXTENSION_INTERNAL_BITMAP_IMAGEMAGICK_H diff --git a/src/extension/internal/bitmap/implode.cpp b/src/extension/internal/bitmap/implode.cpp new file mode 100644 index 0000000..c6c7a5f --- /dev/null +++ b/src/extension/internal/bitmap/implode.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "implode.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Implode::applyEffect(Magick::Image* image) { + image->implode(_factor); +} + +void +Implode::refreshParameters(Inkscape::Extension::Effect* module) { + _factor = module->get_param_float("factor"); +} + +#include "../clear-n_.h" + +void +Implode::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Implode") "</name>\n" + "<id>org.inkscape.effect.bitmap.implode</id>\n" + "<param name=\"factor\" gui-text=\"" N_("Factor:") "\" type=\"float\" min=\"0\" max=\"100\">10</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Implode selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Implode()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/implode.h b/src/extension/internal/bitmap/implode.h new file mode 100644 index 0000000..d9c5adb --- /dev/null +++ b/src/extension/internal/bitmap/implode.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Implode : public ImageMagick +{ +private: + float _factor; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/level.cpp b/src/extension/internal/bitmap/level.cpp new file mode 100644 index 0000000..aaf3168 --- /dev/null +++ b/src/extension/internal/bitmap/level.cpp @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "level.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Level::applyEffect(Magick::Image* image) { + Magick::Quantum black_point = Magick::Color::scaleDoubleToQuantum(_black_point / 100.0); + Magick::Quantum white_point = Magick::Color::scaleDoubleToQuantum(_white_point / 100.0); + image->level(black_point, white_point, _mid_point); +} + +void +Level::refreshParameters(Inkscape::Extension::Effect* module) { + _black_point = module->get_param_float("blackPoint"); + _white_point = module->get_param_float("whitePoint"); + _mid_point = module->get_param_float("midPoint"); +} + +#include "../clear-n_.h" + +void +Level::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Level") "</name>\n" + "<id>org.inkscape.effect.bitmap.level</id>\n" + "<param name=\"blackPoint\" gui-text=\"" N_("Black Point:") "\" type=\"float\" min=\"0\" max=\"100\">0</param>\n" + "<param name=\"whitePoint\" gui-text=\"" N_("White Point:") "\" type=\"float\" min=\"0\" max=\"100\">100</param>\n" + "<param name=\"midPoint\" gui-text=\"" N_("Gamma Correction:") "\" type=\"float\" min=\"0\" max=\"10\">1</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Level selected bitmap(s) by scaling values falling between the given ranges to the full color range") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Level()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/level.h b/src/extension/internal/bitmap/level.h new file mode 100644 index 0000000..d372745 --- /dev/null +++ b/src/extension/internal/bitmap/level.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Level : public ImageMagick +{ +private: + float _black_point; + float _white_point; + float _mid_point; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/levelChannel.cpp b/src/extension/internal/bitmap/levelChannel.cpp new file mode 100644 index 0000000..497d6ce --- /dev/null +++ b/src/extension/internal/bitmap/levelChannel.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "levelChannel.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +LevelChannel::applyEffect(Magick::Image* image) { + Magick::ChannelType channel = Magick::UndefinedChannel; + if (!strcmp(_channelName, "Red Channel")) channel = Magick::RedChannel; + else if (!strcmp(_channelName, "Green Channel")) channel = Magick::GreenChannel; + else if (!strcmp(_channelName, "Blue Channel")) channel = Magick::BlueChannel; + else if (!strcmp(_channelName, "Cyan Channel")) channel = Magick::CyanChannel; + else if (!strcmp(_channelName, "Magenta Channel")) channel = Magick::MagentaChannel; + else if (!strcmp(_channelName, "Yellow Channel")) channel = Magick::YellowChannel; + else if (!strcmp(_channelName, "Black Channel")) channel = Magick::BlackChannel; + else if (!strcmp(_channelName, "Opacity Channel")) channel = Magick::OpacityChannel; + else if (!strcmp(_channelName, "Matte Channel")) channel = Magick::MatteChannel; + Magick::Quantum black_point = Magick::Color::scaleDoubleToQuantum(_black_point / 100.0); + Magick::Quantum white_point = Magick::Color::scaleDoubleToQuantum(_white_point / 100.0); + image->levelChannel(channel, black_point, white_point, _mid_point); +} + +void +LevelChannel::refreshParameters(Inkscape::Extension::Effect* module) { + _channelName = module->get_param_optiongroup("channel"); + _black_point = module->get_param_float("blackPoint"); + _white_point = module->get_param_float("whitePoint"); + _mid_point = module->get_param_float("midPoint"); +} + +#include "../clear-n_.h" + +void +LevelChannel::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Level (with Channel)") "</name>\n" + "<id>org.inkscape.effect.bitmap.levelChannel</id>\n" + "<param name=\"channel\" gui-text=\"" N_("Channel:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value='Red Channel'>" N_("Red Channel") "</option>\n" + "<option value='Green Channel'>" N_("Green Channel") "</option>\n" + "<option value='Blue Channel'>" N_("Blue Channel") "</option>\n" + "<option value='Cyan Channel'>" N_("Cyan Channel") "</option>\n" + "<option value='Magenta Channel'>" N_("Magenta Channel") "</option>\n" + "<option value='Yellow Channel'>" N_("Yellow Channel") "</option>\n" + "<option value='Black Channel'>" N_("Black Channel") "</option>\n" + "<option value='Opacity Channel'>" N_("Opacity Channel") "</option>\n" + "<option value='Matte Channel'>" N_("Matte Channel") "</option>\n" + "</param>\n" + "<param name=\"blackPoint\" gui-text=\"" N_("Black Point:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">0.0</param>\n" + "<param name=\"whitePoint\" gui-text=\"" N_("White Point:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">100.0</param>\n" + "<param name=\"midPoint\" gui-text=\"" N_("Gamma Correction:") "\" type=\"float\" min=\"0.0\" max=\"10.0\">1.0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Level the specified channel of selected bitmap(s) by scaling values falling between the given ranges to the full color range") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new LevelChannel()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/levelChannel.h b/src/extension/internal/bitmap/levelChannel.h new file mode 100644 index 0000000..a619939 --- /dev/null +++ b/src/extension/internal/bitmap/levelChannel.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class LevelChannel : public ImageMagick +{ +private: + float _black_point; + float _white_point; + float _mid_point; + const gchar * _channelName; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/medianFilter.cpp b/src/extension/internal/bitmap/medianFilter.cpp new file mode 100644 index 0000000..cc3dabd --- /dev/null +++ b/src/extension/internal/bitmap/medianFilter.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "medianFilter.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +MedianFilter::applyEffect(Magick::Image* image) { + image->medianFilter(_radius); +} + +void +MedianFilter::refreshParameters(Inkscape::Extension::Effect* module) { + _radius = module->get_param_float("radius"); +} + +#include "../clear-n_.h" + +void +MedianFilter::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Median") "</name>\n" + "<id>org.inkscape.effect.bitmap.medianFilter</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Replace each pixel component with the median color in a circular neighborhood") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new MedianFilter()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/medianFilter.h b/src/extension/internal/bitmap/medianFilter.h new file mode 100644 index 0000000..60ac31a --- /dev/null +++ b/src/extension/internal/bitmap/medianFilter.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class MedianFilter : public ImageMagick +{ +private: + float _radius; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/modulate.cpp b/src/extension/internal/bitmap/modulate.cpp new file mode 100644 index 0000000..f561d4c --- /dev/null +++ b/src/extension/internal/bitmap/modulate.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "modulate.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Modulate::applyEffect(Magick::Image* image) { + float hue = (_hue * 200 / 360.0) + 100; + image->modulate(_brightness, _saturation, hue); +} + +void +Modulate::refreshParameters(Inkscape::Extension::Effect* module) { + _brightness = module->get_param_float("brightness"); + _saturation = module->get_param_float("saturation"); + _hue = module->get_param_float("hue"); +} + +#include "../clear-n_.h" + +void +Modulate::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("HSB Adjust") "</name>\n" + "<id>org.inkscape.effect.bitmap.modulate</id>\n" + "<param name=\"hue\" gui-text=\"" N_("Hue:") "\" type=\"float\" min=\"-360\" max=\"360\">0</param>\n" + "<param name=\"saturation\" gui-text=\"" N_("Saturation:") "\" type=\"float\" min=\"0\" max=\"200\">100</param>\n" + "<param name=\"brightness\" gui-text=\"" N_("Brightness:") "\" type=\"float\" min=\"0\" max=\"200\">100</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Adjust the amount of hue, saturation, and brightness in selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Modulate()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/modulate.h b/src/extension/internal/bitmap/modulate.h new file mode 100644 index 0000000..f0aaf1b --- /dev/null +++ b/src/extension/internal/bitmap/modulate.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Modulate : public ImageMagick +{ +private: + float _brightness; + float _saturation; + float _hue; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/negate.cpp b/src/extension/internal/bitmap/negate.cpp new file mode 100644 index 0000000..fcec5c0 --- /dev/null +++ b/src/extension/internal/bitmap/negate.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "negate.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Negate::applyEffect(Magick::Image* image) { + image->negate(); +} + +void +Negate::refreshParameters(Inkscape::Extension::Effect* /*module*/) { +} + +#include "../clear-n_.h" + +void +Negate::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Negate") "</name>\n" + "<id>org.inkscape.effect.bitmap.negate</id>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Negate (take inverse) selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Negate()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/negate.h b/src/extension/internal/bitmap/negate.h new file mode 100644 index 0000000..4cde402 --- /dev/null +++ b/src/extension/internal/bitmap/negate.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Negate : public ImageMagick +{ +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/normalize.cpp b/src/extension/internal/bitmap/normalize.cpp new file mode 100644 index 0000000..f6cf053 --- /dev/null +++ b/src/extension/internal/bitmap/normalize.cpp @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "normalize.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Normalize::applyEffect(Magick::Image* image) { + image->normalize(); +} + +void +Normalize::refreshParameters(Inkscape::Extension::Effect* /*module*/) { +} + +#include "../clear-n_.h" + +void +Normalize::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Normalize") "</name>\n" + "<id>org.inkscape.effect.bitmap.normalize</id>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Normalize selected bitmap(s), expanding color range to the full possible range of color") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Normalize()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/normalize.h b/src/extension/internal/bitmap/normalize.h new file mode 100644 index 0000000..2d4a9c2 --- /dev/null +++ b/src/extension/internal/bitmap/normalize.h @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Normalize : public ImageMagick +{ +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/oilPaint.cpp b/src/extension/internal/bitmap/oilPaint.cpp new file mode 100644 index 0000000..a5373eb --- /dev/null +++ b/src/extension/internal/bitmap/oilPaint.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "oilPaint.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +OilPaint::applyEffect(Magick::Image* image) { + image->oilPaint(_radius); +} + +void +OilPaint::refreshParameters(Inkscape::Extension::Effect* module) { + _radius = module->get_param_int("radius"); +} + +#include "../clear-n_.h" + +void +OilPaint::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Oil Paint") "</name>\n" + "<id>org.inkscape.effect.bitmap.oilPaint</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"int\" min=\"0\" max=\"50\">3</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Stylize selected bitmap(s) so that they appear to be painted with oils") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new OilPaint()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/oilPaint.h b/src/extension/internal/bitmap/oilPaint.h new file mode 100644 index 0000000..d3957bd --- /dev/null +++ b/src/extension/internal/bitmap/oilPaint.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class OilPaint : public ImageMagick +{ +private: + float _radius; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/opacity.cpp b/src/extension/internal/bitmap/opacity.cpp new file mode 100644 index 0000000..cac8b65 --- /dev/null +++ b/src/extension/internal/bitmap/opacity.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "opacity.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Opacity::applyEffect(Magick::Image* image) { + Magick::Quantum opacity = Magick::Color::scaleDoubleToQuantum((100 - _opacity) / 100.0); + image->opacity(opacity); +} + +void +Opacity::refreshParameters(Inkscape::Extension::Effect* module) { + _opacity = module->get_param_float("opacity"); +} + +#include "../clear-n_.h" + +void +Opacity::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Opacity") "</name>\n" + "<id>org.inkscape.effect.bitmap.opacity</id>\n" + "<param name=\"opacity\" gui-text=\"" N_("Opacity:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">80.0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Modify opacity channel(s) of selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Opacity()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/opacity.h b/src/extension/internal/bitmap/opacity.h new file mode 100644 index 0000000..df6e6ac --- /dev/null +++ b/src/extension/internal/bitmap/opacity.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Opacity : public ImageMagick +{ +private: + float _opacity; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/raise.cpp b/src/extension/internal/bitmap/raise.cpp new file mode 100644 index 0000000..8b3598a --- /dev/null +++ b/src/extension/internal/bitmap/raise.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "raise.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Raise::applyEffect(Magick::Image* image) { + Magick::Geometry geometry(_width, _height, 0, 0); + image->raise(geometry, _raisedFlag); +} + +void +Raise::refreshParameters(Inkscape::Extension::Effect* module) { + _width = module->get_param_int("width"); + _height = module->get_param_int("height"); + _raisedFlag = module->get_param_bool("raisedFlag"); +} + +#include "../clear-n_.h" + +void +Raise::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Raise") "</name>\n" + "<id>org.inkscape.effect.bitmap.raise</id>\n" + "<param name=\"width\" gui-text=\"" N_("Width:") "\" type=\"int\" min=\"0\" max=\"800\">6</param>\n" + "<param name=\"height\" gui-text=\"" N_("Height:") "\" type=\"int\" min=\"0\" max=\"800\">6</param>\n" + "<param name=\"raisedFlag\" gui-text=\"" N_("Raised") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Alter lightness the edges of selected bitmap(s) to create a raised appearance") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Raise()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/raise.h b/src/extension/internal/bitmap/raise.h new file mode 100644 index 0000000..90023fb --- /dev/null +++ b/src/extension/internal/bitmap/raise.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Raise : public ImageMagick +{ +private: + int _width; + int _height; + bool _raisedFlag; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/reduceNoise.cpp b/src/extension/internal/bitmap/reduceNoise.cpp new file mode 100644 index 0000000..809c693 --- /dev/null +++ b/src/extension/internal/bitmap/reduceNoise.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "reduceNoise.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +ReduceNoise::applyEffect(Magick::Image* image) { + if (_order > -1) + image->reduceNoise(_order); + else + image->reduceNoise(); +} + +void +ReduceNoise::refreshParameters(Inkscape::Extension::Effect* module) { + _order = module->get_param_int("order"); +} + +#include "../clear-n_.h" + +void +ReduceNoise::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Reduce Noise") "</name>\n" + "<id>org.inkscape.effect.bitmap.reduceNoise</id>\n" + "<param name=\"order\" gui-text=\"" N_("Order:") "\" type=\"int\" min=\"-1\" max=\"100\">-1</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Reduce noise in selected bitmap(s) using a noise peak elimination filter") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ReduceNoise()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/reduceNoise.h b/src/extension/internal/bitmap/reduceNoise.h new file mode 100644 index 0000000..3c9d2d6 --- /dev/null +++ b/src/extension/internal/bitmap/reduceNoise.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class ReduceNoise : public ImageMagick +{ +private: + int _order; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/sample.cpp b/src/extension/internal/bitmap/sample.cpp new file mode 100644 index 0000000..e59e1af --- /dev/null +++ b/src/extension/internal/bitmap/sample.cpp @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "sample.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Sample::applyEffect(Magick::Image* image) { + Magick::Geometry geometry(_width, _height, 0, 0); + image->sample(geometry); +} + +void +Sample::refreshParameters(Inkscape::Extension::Effect* module) { + _width = module->get_param_int("width"); + _height = module->get_param_int("height"); +} + +#include "../clear-n_.h" + +void +Sample::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Resample") "</name>\n" + "<id>org.inkscape.effect.bitmap.sample</id>\n" + "<param name=\"width\" gui-text=\"" N_("Width:") "\" type=\"int\" min=\"0\" max=\"6400\">100</param>\n" + "<param name=\"height\" gui-text=\"" N_("Height:") "\" type=\"int\" min=\"0\" max=\"6400\">100</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Alter the resolution of selected image by resizing it to the given pixel size") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Sample()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/sample.h b/src/extension/internal/bitmap/sample.h new file mode 100644 index 0000000..c93ab0a --- /dev/null +++ b/src/extension/internal/bitmap/sample.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Sample : public ImageMagick +{ +private: + int _width; + int _height; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/shade.cpp b/src/extension/internal/bitmap/shade.cpp new file mode 100644 index 0000000..f6b2f25 --- /dev/null +++ b/src/extension/internal/bitmap/shade.cpp @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "shade.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Shade::applyEffect(Magick::Image* image) { + image->shade(_azimuth, _elevation, !_colorShading); + // I don't know why, but I have to invert colorShading here +} + +void +Shade::refreshParameters(Inkscape::Extension::Effect* module) { + _azimuth = module->get_param_float("azimuth"); + _elevation = module->get_param_float("elevation"); + _colorShading = module->get_param_bool("colorShading"); +} + +#include "../clear-n_.h" + +void +Shade::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Shade") "</name>\n" + "<id>org.inkscape.effect.bitmap.shade</id>\n" + "<param name=\"azimuth\" gui-text=\"" N_("Azimuth:") "\" type=\"float\" min=\"-180\" max=\"180\">30</param>\n" + "<param name=\"elevation\" gui-text=\"" N_("Elevation:") "\" type=\"float\" min=\"-180\" max=\"180\">30</param>\n" + "<param name=\"colorShading\" gui-text=\"" N_("Colored Shading") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Shade selected bitmap(s) simulating distant light source") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Shade()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/shade.h b/src/extension/internal/bitmap/shade.h new file mode 100644 index 0000000..71ed3db --- /dev/null +++ b/src/extension/internal/bitmap/shade.h @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Shade : public ImageMagick +{ +private: + float _azimuth; + float _elevation; + bool _colorShading; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/sharpen.cpp b/src/extension/internal/bitmap/sharpen.cpp new file mode 100644 index 0000000..aa24ac3 --- /dev/null +++ b/src/extension/internal/bitmap/sharpen.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "sharpen.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Sharpen::applyEffect(Magick::Image* image) { + image->sharpen(_radius, _sigma); +} + +void +Sharpen::refreshParameters(Inkscape::Extension::Effect* module) { + _radius = module->get_param_float("radius"); + _sigma = module->get_param_float("sigma"); +} + +#include "../clear-n_.h" + +void +Sharpen::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Sharpen") "</name>\n" + "<id>org.inkscape.effect.bitmap.sharpen</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"50\">1.0</param>\n" + "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"10\">0.5</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Sharpen selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Sharpen()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/sharpen.h b/src/extension/internal/bitmap/sharpen.h new file mode 100644 index 0000000..35067ed --- /dev/null +++ b/src/extension/internal/bitmap/sharpen.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Sharpen : public ImageMagick +{ +private: + float _radius; + float _sigma; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/solarize.cpp b/src/extension/internal/bitmap/solarize.cpp new file mode 100644 index 0000000..69a1e18 --- /dev/null +++ b/src/extension/internal/bitmap/solarize.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "solarize.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Solarize::applyEffect(Magick::Image* image) { + // Image Magick Quantum depth = 16 + // 655.35 = (2^16 - 1) / 100 + image->solarize(_factor * 655.35); +} + +void +Solarize::refreshParameters(Inkscape::Extension::Effect* module) { + _factor = module->get_param_float("factor"); +} + +#include "../clear-n_.h" + +void +Solarize::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Solarize") "</name>\n" + "<id>org.inkscape.effect.bitmap.solarize</id>\n" + "<param name=\"factor\" gui-text=\"" N_("Factor:") "\" type=\"float\" min=\"0\" max=\"100\">50</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Solarize selected bitmap(s), like overexposing photographic film") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Solarize()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/solarize.h b/src/extension/internal/bitmap/solarize.h new file mode 100644 index 0000000..2e4a51b --- /dev/null +++ b/src/extension/internal/bitmap/solarize.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Solarize : public ImageMagick +{ +private: + float _factor; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/spread.cpp b/src/extension/internal/bitmap/spread.cpp new file mode 100644 index 0000000..86b4432 --- /dev/null +++ b/src/extension/internal/bitmap/spread.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "spread.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Spread::applyEffect(Magick::Image* image) { + image->spread(_amount); +} + +void +Spread::refreshParameters(Inkscape::Extension::Effect* module) { + _amount = module->get_param_int("amount"); +} + +#include "../clear-n_.h" + +void +Spread::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Dither") "</name>\n" + "<id>org.inkscape.effect.bitmap.spread</id>\n" + "<param name=\"amount\" gui-text=\"" N_("Amount:") "\" type=\"int\" min=\"0\" max=\"100\">3</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Randomly scatter pixels in selected bitmap(s), within the given radius of the original position") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Spread()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/spread.h b/src/extension/internal/bitmap/spread.h new file mode 100644 index 0000000..ad77544 --- /dev/null +++ b/src/extension/internal/bitmap/spread.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Spread : public ImageMagick +{ +private: + int _amount; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/swirl.cpp b/src/extension/internal/bitmap/swirl.cpp new file mode 100644 index 0000000..a7681a3 --- /dev/null +++ b/src/extension/internal/bitmap/swirl.cpp @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "swirl.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Swirl::applyEffect(Magick::Image* image) { + image->swirl(_degrees); +} + +void +Swirl::refreshParameters(Inkscape::Extension::Effect* module) { + _degrees = module->get_param_int("degrees"); +} + +#include "../clear-n_.h" + +void +Swirl::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Swirl") "</name>\n" + "<id>org.inkscape.effect.bitmap.swirl</id>\n" + "<param name=\"degrees\" gui-text=\"" N_("Degrees:") "\" type=\"int\" min=\"-360\" max=\"360\">30</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Swirl selected bitmap(s) around center point") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Swirl()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/swirl.h b/src/extension/internal/bitmap/swirl.h new file mode 100644 index 0000000..b9ca348 --- /dev/null +++ b/src/extension/internal/bitmap/swirl.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Swirl : public ImageMagick +{ +private: + float _degrees; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/threshold.cpp b/src/extension/internal/bitmap/threshold.cpp new file mode 100644 index 0000000..4f9dec5 --- /dev/null +++ b/src/extension/internal/bitmap/threshold.cpp @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "threshold.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Threshold::applyEffect(Magick::Image* image) { + image->threshold(_threshold); +} + +void +Threshold::refreshParameters(Inkscape::Extension::Effect* module) { + _threshold = module->get_param_float("threshold"); +} + +#include "../clear-n_.h" + +void +Threshold::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" +// TRANSLATORS: see http://docs.gimp.org/en/gimp-tool-threshold.html + "<name>" N_("Threshold") "</name>\n" + "<id>org.inkscape.effect.bitmap.threshold</id>\n" + "<param name=\"threshold\" gui-text=\"" N_("Threshold:") "\" type=\"float\" min=\"-100.0\" max=\"100.0\"></param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Threshold selected bitmap(s)") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Threshold()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/threshold.h b/src/extension/internal/bitmap/threshold.h new file mode 100644 index 0000000..93e15bc --- /dev/null +++ b/src/extension/internal/bitmap/threshold.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Threshold : public ImageMagick +{ +private: + double _threshold; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/unsharpmask.cpp b/src/extension/internal/bitmap/unsharpmask.cpp new file mode 100644 index 0000000..9240803 --- /dev/null +++ b/src/extension/internal/bitmap/unsharpmask.cpp @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "unsharpmask.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Unsharpmask::applyEffect(Magick::Image* image) { + float amount = _amount / 100.0; + image->unsharpmask(_radius, _sigma, amount, _threshold); +} + +void +Unsharpmask::refreshParameters(Inkscape::Extension::Effect* module) { + _radius = module->get_param_float("radius"); + _sigma = module->get_param_float("sigma"); + _amount = module->get_param_float("amount"); + _threshold = module->get_param_float("threshold"); +} + +#include "../clear-n_.h" + +void +Unsharpmask::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Unsharp Mask") "</name>\n" + "<id>org.inkscape.effect.bitmap.unsharpmask</id>\n" + "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0.0\" max=\"50.0\">5.0</param>\n" + "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0.0\" max=\"50.0\">5.0</param>\n" + "<param name=\"amount\" gui-text=\"" N_("Amount:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">50.0</param>\n" + "<param name=\"threshold\" gui-text=\"" N_("Threshold:") "\" type=\"float\" min=\"0.0\" max=\"50.0\">5.0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Sharpen selected bitmap(s) using unsharp mask algorithms") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Unsharpmask()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/unsharpmask.h b/src/extension/internal/bitmap/unsharpmask.h new file mode 100644 index 0000000..34fbfeb --- /dev/null +++ b/src/extension/internal/bitmap/unsharpmask.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Unsharpmask : public ImageMagick +{ +private: + float _radius; + float _sigma; + float _amount; + float _threshold; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/wave.cpp b/src/extension/internal/bitmap/wave.cpp new file mode 100644 index 0000000..5a67574 --- /dev/null +++ b/src/extension/internal/bitmap/wave.cpp @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/effect.h" +#include "extension/system.h" + +#include "wave.h" +#include <Magick++.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +void +Wave::applyEffect(Magick::Image* image) { + image->wave(_amplitude, _wavelength); +} + +void +Wave::refreshParameters(Inkscape::Extension::Effect* module) { + _amplitude = module->get_param_float("amplitude"); + _wavelength = module->get_param_float("wavelength"); +} + +#include "../clear-n_.h" + +void +Wave::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Wave") "</name>\n" + "<id>org.inkscape.effect.bitmap.wave</id>\n" + "<param name=\"amplitude\" gui-text=\"" N_("Amplitude:") "\" type=\"float\" min=\"-720.0\" max=\"720.0\">25</param>\n" + "<param name=\"wavelength\" gui-text=\"" N_("Wavelength:") "\" type=\"float\" min=\"-720.0\" max=\"720.0\">150</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Raster") "\" />\n" + "</effects-menu>\n" + "<menu-tip>" N_("Alter selected bitmap(s) along sine wave") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Wave()); +} + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bitmap/wave.h b/src/extension/internal/bitmap/wave.h new file mode 100644 index 0000000..f524c17 --- /dev/null +++ b/src/extension/internal/bitmap/wave.h @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2007 Authors: + * Christopher Brown <audiere@gmail.com> + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "imagemagick.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Bitmap { + +class Wave : public ImageMagick +{ +private: + float _amplitude; + float _wavelength; +public: + void applyEffect(Magick::Image *image) override; + void refreshParameters(Inkscape::Extension::Effect *module) override; + static void init(); +}; + +}; /* namespace Bitmap */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/bluredge.cpp b/src/extension/internal/bluredge.cpp new file mode 100644 index 0000000..80fa4f2 --- /dev/null +++ b/src/extension/internal/bluredge.cpp @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + \file bluredge.cpp + + A plug-in to add an effect to blur the edges of an object. +*/ +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <vector> +#include "desktop.h" +#include "document.h" +#include "selection.h" +#include "helper/action.h" +#include "helper/action-context.h" +#include "preferences.h" +#include "path-chemistry.h" +#include "object/sp-item.h" + +#include "extension/effect.h" +#include "extension/system.h" + + +#include "bluredge.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + + +/** + \brief A function to allocated anything -- just an example here + \param module Unused + \return Whether the load was successful +*/ +bool +BlurEdge::load (Inkscape::Extension::Extension */*module*/) +{ + // std::cout << "Hey, I'm Blur Edge, I'm loading!" << std::endl; + return TRUE; +} + +/** + \brief This actually blurs the edge. + \param module The effect that was called (unused) + \param desktop What should be edited. +*/ +void +BlurEdge::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *desktop, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + Inkscape::Selection * selection = static_cast<SPDesktop *>(desktop)->selection; + + float width = module->get_param_float("blur-width"); + int steps = module->get_param_int("num-steps"); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + double old_offset = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px"); + + // TODO need to properly refcount the items, at least + std::vector<SPItem*> items(selection->items().begin(), selection->items().end()); + selection->clear(); + + for(auto spitem : items) { + std::vector<Inkscape::XML::Node *> new_items(steps); + Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc(); + Inkscape::XML::Node * new_group = xml_doc->createElement("svg:g"); + spitem->getRepr()->parent()->appendChild(new_group); + + double orig_opacity = sp_repr_css_double_property(sp_repr_css_attr(spitem->getRepr(), "style"), "opacity", 1.0); + char opacity_string[64]; + g_ascii_formatd(opacity_string, sizeof(opacity_string), "%f", + orig_opacity / (steps)); + + for (int i = 0; i < steps; i++) { + double offset = (width / (float)(steps - 1) * (float)i) - (width / 2.0); + + new_items[i] = spitem->getRepr()->duplicate(xml_doc); + + SPCSSAttr * css = sp_repr_css_attr(new_items[i], "style"); + sp_repr_css_set_property(css, "opacity", opacity_string); + sp_repr_css_change(new_items[i], css, "style"); + + new_group->appendChild(new_items[i]); + selection->add(new_items[i]); + selection->toCurves(); + + if (offset < 0.0) { + /* Doing an inset here folks */ + offset *= -1.0; + prefs->setDoubleUnit("/options/defaultoffsetwidth/value", offset, "px"); + sp_action_perform(Inkscape::Verb::get(SP_VERB_SELECTION_INSET)->get_action(Inkscape::ActionContext(desktop)), nullptr); + } else if (offset > 0.0) { + prefs->setDoubleUnit("/options/defaultoffsetwidth/value", offset, "px"); + sp_action_perform(Inkscape::Verb::get(SP_VERB_SELECTION_OFFSET)->get_action(Inkscape::ActionContext(desktop)), nullptr); + } + + selection->clear(); + } + + Inkscape::GC::release(new_group); + } + + prefs->setDoubleUnit("/options/defaultoffsetwidth/value", old_offset, "px"); + + selection->clear(); + selection->add(items.begin(), items.end()); + + return; +} + +Gtk::Widget * +BlurEdge::prefs_effect(Inkscape::Extension::Effect * module, Inkscape::UI::View::View * /*view*/, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + return module->autogui(nullptr, nullptr, changeSignal); +} + +#include "clear-n_.h" + +void +BlurEdge::init () +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Inset/Outset Halo") "</name>\n" + "<id>org.inkscape.effect.bluredge</id>\n" + "<param name=\"blur-width\" gui-text=\"" N_("Width:") "\" gui-description=\"" N_("Width in px of the halo") "\" type=\"float\" min=\"1.0\" max=\"50.0\">1.0</param>\n" + "<param name=\"num-steps\" gui-text=\"" N_("Number of steps:") "\" gui-description=\"" N_("Number of inset/outset copies of the object to make") "\" type=\"int\" min=\"5\" max=\"100\">11</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Generate from Path") "\" />\n" + "</effects-menu>\n" + "</effect>\n" + "</inkscape-extension>\n" , new BlurEdge()); + return; +} + +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* 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/extension/internal/bluredge.h b/src/extension/internal/bluredge.h new file mode 100644 index 0000000..b74b753 --- /dev/null +++ b/src/extension/internal/bluredge.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/implementation/implementation.h" + +namespace Inkscape { +namespace Extension { + +class Effect; +class Extension; + +namespace Internal { + +/** \brief Implementation class of the GIMP gradient plugin. This mostly + just creates a namespace for the GIMP gradient plugin today. +*/ +class BlurEdge : public Inkscape::Extension::Implementation::Implementation { + +public: + bool load(Inkscape::Extension::Extension *module) override; + void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + Gtk::Widget * prefs_effect(Inkscape::Extension::Effect * module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + + static void init (); +}; + +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* 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/extension/internal/cairo-ps-out.cpp b/src/extension/internal/cairo-ps-out.cpp new file mode 100644 index 0000000..e003c3d --- /dev/null +++ b/src/extension/internal/cairo-ps-out.cpp @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A quick hack to use the Cairo renderer to write out a file. This + * then makes 'save as...' PS. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Ulf Erikson <ulferikson@users.sf.net> + * Adib Taraben <theAdib@gmail.com> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2004-2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cairo.h> +#ifdef CAIRO_HAS_PS_SURFACE + +#include "cairo-ps.h" +#include "cairo-ps-out.h" +#include "cairo-render-context.h" +#include "cairo-renderer.h" +#include "latex-text-renderer.h" +#include <print.h> +#include "extension/system.h" +#include "extension/print.h" +#include "extension/db.h" +#include "extension/output.h" +#include "display/drawing.h" + +#include "display/curve.h" +#include "display/canvas-bpath.h" +#include "object/sp-item.h" +#include "object/sp-root.h" + +#include "io/sys.h" +#include "document.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +bool CairoPsOutput::check (Inkscape::Extension::Extension * /*module*/) +{ + if (nullptr == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_PS)) { + return FALSE; + } else { + return TRUE; + } +} + +bool CairoEpsOutput::check (Inkscape::Extension::Extension * /*module*/) +{ + if (nullptr == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_EPS)) { + return FALSE; + } else { + return TRUE; + } +} + +static bool +ps_print_document_to_file(SPDocument *doc, gchar const *filename, unsigned int level, bool texttopath, bool omittext, + bool filtertobitmap, int resolution, const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px, bool eps = false) +{ + doc->ensureUpToDate(); + + SPRoot *root = doc->getRoot(); + SPItem *base = nullptr; + + bool pageBoundingBox = TRUE; + if (exportId && strcmp(exportId, "")) { + // we want to export the given item only + base = SP_ITEM(doc->getObjectById(exportId)); + if (!base) { + throw Inkscape::Extension::Output::export_id_not_found(exportId); + } + root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only) + pageBoundingBox = exportCanvas; + } + else { + // we want to export the entire document from root + base = root; + pageBoundingBox = !exportDrawing; + } + + if (!base) + return false; + + Inkscape::Drawing drawing; + unsigned dkey = SPItem::display_key_new(1); + root->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY); + + /* Create renderer and context */ + CairoRenderer *renderer = new CairoRenderer(); + CairoRenderContext *ctx = renderer->createContext(); + ctx->setPSLevel(level); + ctx->setEPS(eps); + ctx->setTextToPath(texttopath); + ctx->setOmitText(omittext); + ctx->setFilterToBitmap(filtertobitmap); + ctx->setBitmapResolution(resolution); + + bool ret = ctx->setPsTarget(filename); + if(ret) { + /* Render document */ + ret = renderer->setupDocument(ctx, doc, pageBoundingBox, bleedmargin_px, base); + if (ret) { + renderer->renderItem(ctx, root); + ret = ctx->finish(); + } + } + + root->invoke_hide(dkey); + + renderer->destroyContext(ctx); + delete renderer; + + return ret; +} + + +/** + \brief This function calls the output module with the filename + \param mod unused + \param doc Document to be saved + \param filename Filename to save to (probably will end in .ps) +*/ +void +CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) +{ + Inkscape::Extension::Extension * ext; + unsigned int ret; + + ext = Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_PS); + if (ext == nullptr) + return; + + int level = CAIRO_PS_LEVEL_2; + try { + const gchar *new_level = mod->get_param_optiongroup("PSlevel"); + if((new_level != nullptr) && (g_ascii_strcasecmp("PS3", new_level) == 0)) { + level = CAIRO_PS_LEVEL_3; + } + } catch(...) {} + + bool new_textToPath = FALSE; + try { + new_textToPath = (strcmp(mod->get_param_optiongroup("textToPath"), "paths") == 0); + } catch(...) {} + + bool new_textToLaTeX = FALSE; + try { + new_textToLaTeX = (strcmp(mod->get_param_optiongroup("textToPath"), "LaTeX") == 0); + } + catch(...) { + g_warning("Parameter <textToLaTeX> might not exist"); + } + + bool new_blurToBitmap = FALSE; + try { + new_blurToBitmap = mod->get_param_bool("blurToBitmap"); + } catch(...) {} + + int new_bitmapResolution = 72; + try { + new_bitmapResolution = mod->get_param_int("resolution"); + } catch(...) {} + + bool new_areaPage = true; + try { + new_areaPage = (strcmp(mod->get_param_optiongroup("area"), "page") == 0); + } catch(...) {} + + bool new_areaDrawing = !new_areaPage; + + float bleedmargin_px = 0.; + try { + bleedmargin_px = mod->get_param_float("bleed"); + } catch(...) {} + + const gchar *new_exportId = nullptr; + try { + new_exportId = mod->get_param_string("exportId"); + } catch(...) {} + + // Create PS + { + gchar * final_name; + final_name = g_strdup_printf("> %s", filename); + ret = ps_print_document_to_file(doc, final_name, level, new_textToPath, + new_textToLaTeX, new_blurToBitmap, + new_bitmapResolution, new_exportId, + new_areaDrawing, new_areaPage, + bleedmargin_px); + g_free(final_name); + + if (!ret) + throw Inkscape::Extension::Output::save_failed(); + } + + // Create LaTeX file (if requested) + if (new_textToLaTeX) { + ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_areaDrawing, new_areaPage, 0., false); + + if (!ret) + throw Inkscape::Extension::Output::save_failed(); + } +} + + +/** + \brief This function calls the output module with the filename + \param mod unused + \param doc Document to be saved + \param filename Filename to save to (probably will end in .ps) +*/ +void +CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) +{ + Inkscape::Extension::Extension * ext; + unsigned int ret; + + ext = Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_EPS); + if (ext == nullptr) + return; + + int level = CAIRO_PS_LEVEL_2; + try { + const gchar *new_level = mod->get_param_optiongroup("PSlevel"); + if((new_level != nullptr) && (g_ascii_strcasecmp("PS3", new_level) == 0)) { + level = CAIRO_PS_LEVEL_3; + } + } catch(...) {} + + bool new_textToPath = FALSE; + try { + new_textToPath = (strcmp(mod->get_param_optiongroup("textToPath"), "paths") == 0); + } catch(...) {} + + bool new_textToLaTeX = FALSE; + try { + new_textToLaTeX = (strcmp(mod->get_param_optiongroup("textToPath"), "LaTeX") == 0); + } + catch(...) { + g_warning("Parameter <textToLaTeX> might not exist"); + } + + bool new_blurToBitmap = FALSE; + try { + new_blurToBitmap = mod->get_param_bool("blurToBitmap"); + } catch(...) {} + + int new_bitmapResolution = 72; + try { + new_bitmapResolution = mod->get_param_int("resolution"); + } catch(...) {} + + bool new_areaPage = true; + try { + new_areaPage = (strcmp(mod->get_param_optiongroup("area"), "page") == 0); + } catch(...) {} + + bool new_areaDrawing = !new_areaPage; + + float bleedmargin_px = 0.; + try { + bleedmargin_px = mod->get_param_float("bleed"); + } catch(...) {} + + const gchar *new_exportId = nullptr; + try { + new_exportId = mod->get_param_string("exportId"); + } catch(...) {} + + // Create EPS + { + gchar * final_name; + final_name = g_strdup_printf("> %s", filename); + ret = ps_print_document_to_file(doc, final_name, level, new_textToPath, + new_textToLaTeX, new_blurToBitmap, + new_bitmapResolution, new_exportId, + new_areaDrawing, new_areaPage, + bleedmargin_px, true); + g_free(final_name); + + if (!ret) + throw Inkscape::Extension::Output::save_failed(); + } + + // Create LaTeX file (if requested) + if (new_textToLaTeX) { + ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_areaDrawing, new_areaPage, 0., false); + + if (!ret) + throw Inkscape::Extension::Output::save_failed(); + } +} + + +bool +CairoPsOutput::textToPath(Inkscape::Extension::Print * ext) +{ + return ext->get_param_bool("textToPath"); +} + +bool +CairoEpsOutput::textToPath(Inkscape::Extension::Print * ext) +{ + return ext->get_param_bool("textToPath"); +} + +#include "clear-n_.h" + +/** + \brief A function allocate a copy of this function. + + This is the definition of Cairo PS out. This function just + calls the extension system with the memory allocated XML that + describes the data. +*/ +void +CairoPsOutput::init () +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("PostScript") "</name>\n" + "<id>" SP_MODULE_KEY_PRINT_CAIRO_PS "</id>\n" + "<param name=\"PSlevel\" gui-text=\"" N_("Restrict to PS level:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value='PS3'>" N_("PostScript level 3") "</option>\n" + "<option value='PS2'>" N_("PostScript level 2") "</option>\n" + "</param>\n" + "<param name=\"textToPath\" gui-text=\"" N_("Text output options:") "\" type=\"optiongroup\" appearance=\"radio\">\n" + "<option value=\"embed\">" N_("Embed fonts") "</option>\n" + "<option value=\"paths\">" N_("Convert text to paths") "</option>\n" + "<option value=\"LaTeX\">" N_("Omit text in PDF and create LaTeX file") "</option>\n" + "</param>\n" + "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"bool\">true</param>\n" + "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">96</param>\n" + "<param name=\"area\" gui-text=\"" N_("Output page size") "\" type=\"optiongroup\" appearance=\"radio\" >\n" + "<option value=\"page\">" N_("Use document's page size") "</option>" + "<option value=\"drawing\">" N_("Use exported object's size") "</option>" + "</param>" + "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm):") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n" + "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n" + "<output>\n" + "<extension>.ps</extension>\n" + "<mimetype>image/x-postscript</mimetype>\n" + "<filetypename>" N_("PostScript (*.ps)") "</filetypename>\n" + "<filetypetooltip>" N_("PostScript File") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new CairoPsOutput()); + + return; +} + +/** + \brief A function allocate a copy of this function. + + This is the definition of Cairo EPS out. This function just + calls the extension system with the memory allocated XML that + describes the data. +*/ +void +CairoEpsOutput::init () +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Encapsulated PostScript") "</name>\n" + "<id>" SP_MODULE_KEY_PRINT_CAIRO_EPS "</id>\n" + "<param name=\"PSlevel\" gui-text=\"" N_("Restrict to PS level:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value='PS3'>" N_("PostScript level 3") "</option>\n" + "<option value='PS2'>" N_("PostScript level 2") "</option>\n" + "</param>\n" + "<param name=\"textToPath\" gui-text=\"" N_("Text output options:") "\" type=\"optiongroup\" appearance=\"radio\">\n" + "<option value=\"embed\">" N_("Embed fonts") "</option>\n" + "<option value=\"paths\">" N_("Convert text to paths") "</option>\n" + "<option value=\"LaTeX\">" N_("Omit text in PDF and create LaTeX file") "</option>\n" + "</param>\n" + "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"bool\">true</param>\n" + "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">96</param>\n" + "<param name=\"area\" gui-text=\"" N_("Output page size") "\" type=\"optiongroup\" appearance=\"radio\" >\n" + "<option value=\"page\">" N_("Use document's page size") "</option>" + "<option value=\"drawing\">" N_("Use exported object's size") "</option>" + "</param>" + "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm)") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n" + "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n" + "<output>\n" + "<extension>.eps</extension>\n" + "<mimetype>image/x-e-postscript</mimetype>\n" + "<filetypename>" N_("Encapsulated PostScript (*.eps)") "</filetypename>\n" + "<filetypetooltip>" N_("Encapsulated PostScript File") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new CairoEpsOutput()); + + return; +} + +} } } /* namespace Inkscape, Extension, Implementation */ + +#endif /* HAVE_CAIRO_PDF */ diff --git a/src/extension/internal/cairo-ps-out.h b/src/extension/internal/cairo-ps-out.h new file mode 100644 index 0000000..ee58179 --- /dev/null +++ b/src/extension/internal/cairo-ps-out.h @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A quick hack to use the print output to write out a file. This + * then makes 'save as...' PS. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Ulf Erikson <ulferikson@users.sf.net> + * Adib Taraben <theAdib@gmail.com> + * Abhishek Sharma + * + * Copyright (C) 2004-2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef EXTENSION_INTERNAL_CAIRO_PS_OUT_H +#define EXTENSION_INTERNAL_CAIRO_PS_OUT_H + +#include "extension/implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class CairoPsOutput : Inkscape::Extension::Implementation::Implementation { + +public: + bool check(Inkscape::Extension::Extension *module) override; + void save(Inkscape::Extension::Output *mod, + SPDocument *doc, + gchar const *filename) override; + static void init(); + bool textToPath(Inkscape::Extension::Print *ext) override; + +}; + +class CairoEpsOutput : Inkscape::Extension::Implementation::Implementation { + +public: + bool check(Inkscape::Extension::Extension *module) override; + void save(Inkscape::Extension::Output *mod, + SPDocument *doc, + gchar const *uri) override; + static void init(); + bool textToPath(Inkscape::Extension::Print *ext) override; + +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + +#endif /* !EXTENSION_INTERNAL_CAIRO_PS_OUT_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/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp new file mode 100644 index 0000000..66690d5 --- /dev/null +++ b/src/extension/internal/cairo-render-context.cpp @@ -0,0 +1,1985 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Rendering with Cairo. + */ +/* + * Author: + * Miklos Erdelyi <erdelyim@gmail.com> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2006 Miklos Erdelyi + * + * 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 + +#ifndef PANGO_ENABLE_BACKEND +#define PANGO_ENABLE_BACKEND +#endif + +#ifndef PANGO_ENABLE_ENGINE +#define PANGO_ENABLE_ENGINE +#endif + + +#include <csignal> +#include <cerrno> +#include <2geom/pathvector.h> + +#include <glib.h> + +#include <glibmm/i18n.h> +#include "display/drawing.h" +#include "display/curve.h" +#include "display/canvas-bpath.h" +#include "display/cairo-utils.h" +#include "object/sp-item.h" +#include "object/sp-item-group.h" +#include "object/sp-hatch.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-mesh-gradient.h" +#include "object/sp-pattern.h" +#include "object/sp-mask.h" +#include "object/sp-clippath.h" + +#include "util/units.h" +#ifdef _WIN32 +#include "libnrtype/FontFactory.h" // USE_PANGO_WIN32 +#endif + +#include "cairo-render-context.h" +#include "cairo-renderer.h" +#include "extension/system.h" + +#include "io/sys.h" + +#include "svg/stringstream.h" + +#include <cairo.h> + +// include support for only the compiled-in surface types +#ifdef CAIRO_HAS_PDF_SURFACE +#include <cairo-pdf.h> +#endif +#ifdef CAIRO_HAS_PS_SURFACE +#include <cairo-ps.h> +#endif + + +#ifdef CAIRO_HAS_FT_FONT +#include <cairo-ft.h> +#endif +#ifdef CAIRO_HAS_WIN32_FONT +#include <pango/pangowin32.h> +#include <cairo-win32.h> +#endif + +#include <pango/pangofc-fontmap.h> + +//#define TRACE(_args) g_printf _args +//#define TRACE(_args) g_message _args +#define TRACE(_args) +//#define TEST(_args) _args +#define TEST(_args) + +// FIXME: expose these from sp-clippath/mask.cpp +/*struct SPClipPathView { + SPClipPathView *next; + unsigned int key; + Inkscape::DrawingItem *arenaitem; + Geom::OptRect bbox; +}; + +struct SPMaskView { + SPMaskView *next; + unsigned int key; + Inkscape::DrawingItem *arenaitem; + Geom::OptRect bbox; +};*/ + +namespace Inkscape { +namespace Extension { +namespace Internal { + +static cairo_status_t _write_callback(void *closure, const unsigned char *data, unsigned int length); + +CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : + _dpi(72), + _pdf_level(1), + _ps_level(1), + _eps(false), + _is_texttopath(FALSE), + _is_omittext(FALSE), + _is_filtertobitmap(FALSE), + _bitmapresolution(72), + _stream(nullptr), + _is_valid(FALSE), + _vector_based_target(FALSE), + _cr(nullptr), // Cairo context + _surface(nullptr), + _target(CAIRO_SURFACE_TYPE_IMAGE), + _target_format(CAIRO_FORMAT_ARGB32), + _layout(nullptr), + _state(nullptr), + _renderer(parent), + _render_mode(RENDER_MODE_NORMAL), + _clip_mode(CLIP_MODE_MASK), + _omittext_state(EMPTY) +{ +} + +CairoRenderContext::~CairoRenderContext() +{ + for (std::map<gpointer, cairo_font_face_t *>::const_iterator iter = font_table.begin(); iter != font_table.end(); ++iter) + font_data_free(iter->second); + + if (_cr) cairo_destroy(_cr); + if (_surface) cairo_surface_destroy(_surface); + if (_layout) g_object_unref(_layout); +} +void CairoRenderContext::font_data_free(gpointer data) +{ + cairo_font_face_t *font_face = (cairo_font_face_t *)data; + if (font_face) { + cairo_font_face_destroy(font_face); + } +} + +CairoRenderer* CairoRenderContext::getRenderer() const +{ + return _renderer; +} + +CairoRenderState* CairoRenderContext::getCurrentState() const +{ + return _state; +} + +CairoRenderState* CairoRenderContext::getParentState() const +{ + // if this is the root node just return it + if (_state_stack.size() == 1) { + return _state; + } else { + return _state_stack[_state_stack.size()-2]; + } +} + +void CairoRenderContext::setStateForStyle(SPStyle const *style) +{ + // only opacity & overflow is stored for now + _state->opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); + _state->has_overflow = (style->overflow.set && style->overflow.value != SP_CSS_OVERFLOW_VISIBLE); + _state->has_filtereffect = (style->filter.set != 0) ? TRUE : FALSE; + + if (style->fill.isPaintserver() || style->stroke.isPaintserver()) + _state->merge_opacity = FALSE; + + // disable rendering of opacity if there's a stroke on the fill + if (_state->merge_opacity + && !style->fill.isNone() + && !style->stroke.isNone()) + _state->merge_opacity = FALSE; +} + +/** + * \brief Creates a new render context which will be compatible with the given context's Cairo surface + * + * \param width width of the surface to be created + * \param height height of the surface to be created + */ +CairoRenderContext* +CairoRenderContext::cloneMe(double width, double height) const +{ + g_assert( _is_valid ); + g_assert( width > 0.0 && height > 0.0 ); + + CairoRenderContext *new_context = _renderer->createContext(); + cairo_surface_t *surface = cairo_surface_create_similar(cairo_get_target(_cr), CAIRO_CONTENT_COLOR_ALPHA, + (int)ceil(width), (int)ceil(height)); + new_context->_cr = cairo_create(surface); + new_context->_surface = surface; + new_context->_width = width; + new_context->_height = height; + new_context->_is_valid = TRUE; + + return new_context; +} + +CairoRenderContext* CairoRenderContext::cloneMe() const +{ + g_assert( _is_valid ); + + return cloneMe(_width, _height); +} + +bool CairoRenderContext::setImageTarget(cairo_format_t format) +{ + // format cannot be set on an already initialized surface + if (_is_valid) + return false; + + switch (format) { + case CAIRO_FORMAT_ARGB32: + case CAIRO_FORMAT_RGB24: + case CAIRO_FORMAT_A8: + case CAIRO_FORMAT_A1: + _target_format = format; + _target = CAIRO_SURFACE_TYPE_IMAGE; + return true; + break; + default: + break; + } + + return false; +} + +bool CairoRenderContext::setPdfTarget(gchar const *utf8_fn) +{ +#ifndef CAIRO_HAS_PDF_SURFACE + return false; +#else + _target = CAIRO_SURFACE_TYPE_PDF; + _vector_based_target = TRUE; +#endif + + FILE *osf = nullptr; + FILE *osp = nullptr; + + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = nullptr; + gchar *local_fn = g_filename_from_utf8(utf8_fn, + -1, &bytesRead, &bytesWritten, &error); + gchar const *fn = local_fn; + + /* TODO: Replace the below fprintf's with something that does the right thing whether in + * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of + * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the + * return code. + */ + if (fn != nullptr) { + if (*fn == '|') { + fn += 1; + while (isspace(*fn)) fn += 1; +#ifndef _WIN32 + osp = popen(fn, "w"); +#else + osp = _popen(fn, "w"); +#endif + if (!osp) { + fprintf(stderr, "inkscape: popen(%s): %s\n", + fn, strerror(errno)); + return false; + } + _stream = osp; + } else if (*fn == '>') { + fn += 1; + while (isspace(*fn)) fn += 1; + Inkscape::IO::dump_fopen_call(fn, "K"); + osf = Inkscape::IO::fopen_utf8name(fn, "w+"); + if (!osf) { + fprintf(stderr, "inkscape: fopen(%s): %s\n", + fn, strerror(errno)); + return false; + } + _stream = osf; + } else { + /* put cwd stuff in here */ + gchar *qn = ( *fn + ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */ + : g_strdup("lpr") ); +#ifndef _WIN32 + osp = popen(qn, "w"); +#else + osp = _popen(qn, "w"); +#endif + if (!osp) { + fprintf(stderr, "inkscape: popen(%s): %s\n", + qn, strerror(errno)); + return false; + } + g_free(qn); + _stream = osp; + } + } + + g_free(local_fn); + + if (_stream) { + /* fixme: this is kinda icky */ +#if !defined(_WIN32) && !defined(__WIN32__) + (void) signal(SIGPIPE, SIG_IGN); +#endif + } + + return true; +} + +bool CairoRenderContext::setPsTarget(gchar const *utf8_fn) +{ +#ifndef CAIRO_HAS_PS_SURFACE + return false; +#else + _target = CAIRO_SURFACE_TYPE_PS; + _vector_based_target = TRUE; +#endif + + FILE *osf = nullptr; + FILE *osp = nullptr; + + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = nullptr; + gchar *local_fn = g_filename_from_utf8(utf8_fn, + -1, &bytesRead, &bytesWritten, &error); + gchar const *fn = local_fn; + + /* TODO: Replace the below fprintf's with something that does the right thing whether in + * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of + * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the + * return code. + */ + if (fn != nullptr) { + if (*fn == '|') { + fn += 1; + while (isspace(*fn)) fn += 1; +#ifndef _WIN32 + osp = popen(fn, "w"); +#else + osp = _popen(fn, "w"); +#endif + if (!osp) { + fprintf(stderr, "inkscape: popen(%s): %s\n", + fn, strerror(errno)); + return false; + } + _stream = osp; + } else if (*fn == '>') { + fn += 1; + while (isspace(*fn)) fn += 1; + Inkscape::IO::dump_fopen_call(fn, "K"); + osf = Inkscape::IO::fopen_utf8name(fn, "w+"); + if (!osf) { + fprintf(stderr, "inkscape: fopen(%s): %s\n", + fn, strerror(errno)); + return false; + } + _stream = osf; + } else { + /* put cwd stuff in here */ + gchar *qn = ( *fn + ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */ + : g_strdup("lpr") ); +#ifndef _WIN32 + osp = popen(qn, "w"); +#else + osp = _popen(qn, "w"); +#endif + if (!osp) { + fprintf(stderr, "inkscape: popen(%s): %s\n", + qn, strerror(errno)); + return false; + } + g_free(qn); + _stream = osp; + } + } + + g_free(local_fn); + + if (_stream) { + /* fixme: this is kinda icky */ +#if !defined(_WIN32) && !defined(__WIN32__) + (void) signal(SIGPIPE, SIG_IGN); +#endif + } + + return true; +} + +void CairoRenderContext::setPSLevel(unsigned int level) +{ + _ps_level = level; +} + +void CairoRenderContext::setEPS(bool eps) +{ + _eps = eps; +} + +unsigned int CairoRenderContext::getPSLevel() +{ + return _ps_level; +} + +void CairoRenderContext::setPDFLevel(unsigned int level) +{ + _pdf_level = level; +} + +void CairoRenderContext::setTextToPath(bool texttopath) +{ + _is_texttopath = texttopath; +} + +void CairoRenderContext::setOmitText(bool omittext) +{ + _is_omittext = omittext; +} + +bool CairoRenderContext::getOmitText() +{ + return _is_omittext; +} + +void CairoRenderContext::setFilterToBitmap(bool filtertobitmap) +{ + _is_filtertobitmap = filtertobitmap; +} + +bool CairoRenderContext::getFilterToBitmap() +{ + return _is_filtertobitmap; +} + +void CairoRenderContext::setBitmapResolution(int resolution) +{ + _bitmapresolution = resolution; +} + +int CairoRenderContext::getBitmapResolution() +{ + return _bitmapresolution; +} + +cairo_surface_t* +CairoRenderContext::getSurface() +{ + g_assert( _is_valid ); + + return _surface; +} + +bool +CairoRenderContext::saveAsPng(const char *file_name) +{ + cairo_status_t status = cairo_surface_write_to_png(_surface, file_name); + if (status) + return false; + else + return true; +} + +void +CairoRenderContext::setRenderMode(CairoRenderMode mode) +{ + switch (mode) { + case RENDER_MODE_NORMAL: + case RENDER_MODE_CLIP: + _render_mode = mode; + break; + default: + _render_mode = RENDER_MODE_NORMAL; + break; + } +} + +CairoRenderContext::CairoRenderMode +CairoRenderContext::getRenderMode() const +{ + return _render_mode; +} + +void +CairoRenderContext::setClipMode(CairoClipMode mode) +{ + switch (mode) { + case CLIP_MODE_PATH: // Clip is rendered as a path for vector output + case CLIP_MODE_MASK: // Clip is rendered as a bitmap for raster output. + _clip_mode = mode; + break; + default: + _clip_mode = CLIP_MODE_PATH; + break; + } +} + +CairoRenderContext::CairoClipMode +CairoRenderContext::getClipMode() const +{ + return _clip_mode; +} + +CairoRenderState* CairoRenderContext::_createState() +{ + CairoRenderState *state = static_cast<CairoRenderState*>(g_try_malloc(sizeof(CairoRenderState))); + g_assert( state != nullptr ); + + state->has_filtereffect = FALSE; + state->merge_opacity = TRUE; + state->opacity = 1.0; + state->need_layer = FALSE; + state->has_overflow = FALSE; + state->parent_has_userspace = FALSE; + state->clip_path = nullptr; + state->mask = nullptr; + + return state; +} + +void CairoRenderContext::pushLayer() +{ + g_assert( _is_valid ); + + TRACE(("--pushLayer\n")); + cairo_push_group(_cr); + + // clear buffer + if (!_vector_based_target) { + cairo_save(_cr); + cairo_set_operator(_cr, CAIRO_OPERATOR_CLEAR); + cairo_paint(_cr); + cairo_restore(_cr); + } +} + +void +CairoRenderContext::popLayer(cairo_operator_t composite) +{ + g_assert( _is_valid ); + + float opacity = _state->opacity; + TRACE(("--popLayer w/ opacity %f\n", opacity)); + + /* + At this point, the Cairo source is ready. A Cairo mask must be created if required. + Care must be taken of transformatons as Cairo, like PS and PDF, treats clip paths and + masks independently of the objects they effect while in SVG the clip paths and masks + are defined relative to the objects they are attached to. + Notes: + 1. An SVG object may have both a clip path and a mask! + 2. An SVG clip path can be composed of an object with a clip path. This is not handled properly. + 3. An SVG clipped or masked object may be first drawn off the page and then translated onto + the page (document). This is also not handled properly. + 4. The code converts all SVG masks to bitmaps. This shouldn't be necessary. + 5. Cairo expects a mask to use only the alpha channel. SVG masks combine the RGB luminance with + alpha. This is handled here by doing a pixel by pixel conversion. + */ + + SPClipPath *clip_path = _state->clip_path; + SPMask *mask = _state->mask; + if (clip_path || mask) { + + CairoRenderContext *clip_ctx = nullptr; + cairo_surface_t *clip_mask = nullptr; + + // Apply any clip path first + if (clip_path) { + TRACE((" Applying clip\n")); + if (_render_mode == RENDER_MODE_CLIP) + mask = nullptr; // disable mask when performing nested clipping + + if (_vector_based_target) { + setClipMode(CLIP_MODE_PATH); // Vector + if (!mask) { + cairo_pop_group_to_source(_cr); + _renderer->applyClipPath(this, clip_path); // Uses cairo_clip() + if (opacity == 1.0) + cairo_paint(_cr); + else + cairo_paint_with_alpha(_cr, opacity); + + } else { + // the clipPath will be applied before masking + } + } else { + + // setup a new rendering context + clip_ctx = _renderer->createContext(); + clip_ctx->setImageTarget(CAIRO_FORMAT_A8); + clip_ctx->setClipMode(CLIP_MODE_MASK); // Raster + // This code ties the clipping to the document coordinates. It doesn't allow + // for a clipped object initially drawn off the page and then translated onto + // the page. + if (!clip_ctx->setupSurface(_width, _height)) { + TRACE(("clip: setupSurface failed\n")); + _renderer->destroyContext(clip_ctx); + return; + } + + // clear buffer + cairo_save(clip_ctx->_cr); + cairo_set_operator(clip_ctx->_cr, CAIRO_OPERATOR_CLEAR); + cairo_paint(clip_ctx->_cr); + cairo_restore(clip_ctx->_cr); + + // If a mask won't be applied set opacity too. (The clip is represented by a solid Cairo mask.) + if (!mask) + cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, opacity); + else + cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, 1.0); + + // copy over the correct CTM + // It must be stored in item_transform of current state after pushState. + Geom::Affine item_transform; + if (_state->parent_has_userspace) + item_transform = getParentState()->transform * _state->item_transform; + else + item_transform = _state->item_transform; + + // apply the clip path + clip_ctx->pushState(); + clip_ctx->getCurrentState()->item_transform = item_transform; + _renderer->applyClipPath(clip_ctx, clip_path); + clip_ctx->popState(); + + clip_mask = clip_ctx->getSurface(); + TEST(clip_ctx->saveAsPng("clip_mask.png")); + + if (!mask) { + cairo_pop_group_to_source(_cr); + if (composite != CAIRO_OPERATOR_CLEAR){ + cairo_set_operator(_cr, composite); + } + cairo_mask_surface(_cr, clip_mask, 0, 0); + _renderer->destroyContext(clip_ctx); + } + } + } + + // Apply any mask second + if (mask) { + TRACE((" Applying mask\n")); + // create rendering context for mask + CairoRenderContext *mask_ctx = _renderer->createContext(); + + // Fix Me: This is a kludge. PDF and PS output is set to 72 dpi but the + // Cairo surface is expecting the mask to be 96 dpi. + float surface_width = _width; + float surface_height = _height; + if( _vector_based_target ) { + surface_width *= 4.0/3.0; + surface_height *= 4.0/3.0; + } + if (!mask_ctx->setupSurface( surface_width, surface_height )) { + TRACE(("mask: setupSurface failed\n")); + _renderer->destroyContext(mask_ctx); + return; + } + TRACE(("mask surface: %f x %f at %i dpi\n", surface_width, surface_height, _dpi )); + + // Mask should start black, but it is created white. + cairo_set_source_rgba(mask_ctx->_cr, 0.0, 0.0, 0.0, 1.0); + cairo_rectangle(mask_ctx->_cr, 0, 0, surface_width, surface_height); + cairo_fill(mask_ctx->_cr); + + // set rendering mode to normal + setRenderMode(RENDER_MODE_NORMAL); + + // copy the correct CTM to mask context + /* + if (_state->parent_has_userspace) + mask_ctx->setTransform(getParentState()->transform); + else + mask_ctx->setTransform(_state->transform); + */ + // This is probably not correct... but it seems to do the trick. + mask_ctx->setTransform(_state->item_transform); + + // render mask contents to mask_ctx + _renderer->applyMask(mask_ctx, mask); + + TEST(mask_ctx->saveAsPng("mask.png")); + + // composite with clip mask + if (clip_path && _clip_mode == CLIP_MODE_MASK) { + cairo_mask_surface(mask_ctx->_cr, clip_mask, 0, 0); + _renderer->destroyContext(clip_ctx); + } + + cairo_surface_t *mask_image = mask_ctx->getSurface(); + int width = cairo_image_surface_get_width(mask_image); + int height = cairo_image_surface_get_height(mask_image); + int stride = cairo_image_surface_get_stride(mask_image); + unsigned char *pixels = cairo_image_surface_get_data(mask_image); + + // In SVG, the rgb channels as well as the alpha channel is used in masking. + // In Cairo, only the alpha channel is used thus requiring this conversion. + // SVG specifies that RGB be converted to alpha using luminance-to-alpha. + // Notes: This calculation assumes linear RGB values. VERIFY COLOR SPACE! + // The incoming pixel values already include alpha, fill-opacity, etc., + // however, opacity must still be applied. + TRACE(("premul w/ %f\n", opacity)); + const float coeff_r = 0.2125 / 255.0; + const float coeff_g = 0.7154 / 255.0; + const float coeff_b = 0.0721 / 255.0; + for (int row = 0 ; row < height; row++) { + unsigned char *row_data = pixels + (row * stride); + for (int i = 0 ; i < width; i++) { + guint32 *pixel = reinterpret_cast<guint32 *>(row_data) + i; + float lum_alpha = (((*pixel & 0x00ff0000) >> 16) * coeff_r + + ((*pixel & 0x0000ff00) >> 8) * coeff_g + + ((*pixel & 0x000000ff) ) * coeff_b ); + // lum_alpha can be slightly greater than 1 due to rounding errors... + // but this should be OK since it doesn't matter what the lower + // six hexadecimal numbers of *pixel are. + *pixel = (guint32)(0xff000000 * lum_alpha * opacity); + } + } + + cairo_pop_group_to_source(_cr); + if (composite != CAIRO_OPERATOR_CLEAR){ + cairo_set_operator(_cr, composite); + } + if (_clip_mode == CLIP_MODE_PATH) { + // we have to do the clipping after cairo_pop_group_to_source + _renderer->applyClipPath(this, clip_path); + } + // apply the mask onto the layer + cairo_mask_surface(_cr, mask_image, 0, 0); + _renderer->destroyContext(mask_ctx); + } + } else { + // No clip path or mask + cairo_pop_group_to_source(_cr); + if (composite != CAIRO_OPERATOR_CLEAR){ + cairo_set_operator(_cr, composite); + } + if (opacity == 1.0) + cairo_paint(_cr); + else + cairo_paint_with_alpha(_cr, opacity); + } +} +void CairoRenderContext::tagBegin(const char* l){ +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4) + char* link = g_strdup_printf("uri='%s'", l); + cairo_tag_begin(_cr, CAIRO_TAG_LINK, link); + g_free(link); +#endif +} + +void CairoRenderContext::tagEnd(){ +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4) + cairo_tag_end(_cr, CAIRO_TAG_LINK); +#endif +} + + +void +CairoRenderContext::addClipPath(Geom::PathVector const &pv, SPIEnum<SPWindRule> const *fill_rule) +{ + g_assert( _is_valid ); + + // here it should be checked whether the current clip winding changed + // so we could switch back to masked clipping + if (fill_rule->value == SP_WIND_RULE_EVENODD) { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); + } else { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); + } + addPathVector(pv); +} + +void +CairoRenderContext::addClippingRect(double x, double y, double width, double height) +{ + g_assert( _is_valid ); + + cairo_rectangle(_cr, x, y, width, height); + cairo_clip(_cr); +} + +bool +CairoRenderContext::setupSurface(double width, double height) +{ + // Is the surface already set up? + if (_is_valid) + return true; + + if (_vector_based_target && _stream == nullptr) + return false; + + _width = width; + _height = height; + + cairo_surface_t *surface = nullptr; + cairo_matrix_t ctm; + cairo_matrix_init_identity (&ctm); + switch (_target) { + case CAIRO_SURFACE_TYPE_IMAGE: + surface = cairo_image_surface_create(_target_format, (int)ceil(width), (int)ceil(height)); + break; +#ifdef CAIRO_HAS_PDF_SURFACE + case CAIRO_SURFACE_TYPE_PDF: + surface = cairo_pdf_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height); + cairo_pdf_surface_restrict_to_version(surface, (cairo_pdf_version_t)_pdf_level); + break; +#endif +#ifdef CAIRO_HAS_PS_SURFACE + case CAIRO_SURFACE_TYPE_PS: + surface = cairo_ps_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height); + if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { + return FALSE; + } + cairo_ps_surface_restrict_to_level(surface, (cairo_ps_level_t)_ps_level); + cairo_ps_surface_set_eps(surface, (cairo_bool_t) _eps); + break; +#endif + default: + return false; + break; + } + + _setSurfaceMetadata(surface); + + return _finishSurfaceSetup (surface, &ctm); +} + +bool +CairoRenderContext::setSurfaceTarget(cairo_surface_t *surface, bool is_vector, cairo_matrix_t *ctm) +{ + if (_is_valid || !surface) + return false; + + _vector_based_target = is_vector; + bool ret = _finishSurfaceSetup (surface, ctm); + if (ret) + cairo_surface_reference (surface); + return ret; +} + +bool +CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t *ctm) +{ + if(surface == nullptr) { + return false; + } + if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { + return false; + } + + _cr = cairo_create(surface); + if(CAIRO_STATUS_SUCCESS != cairo_status(_cr)) { + return false; + } + if (ctm) + cairo_set_matrix(_cr, ctm); + _surface = surface; + + if (_vector_based_target) { + cairo_scale(_cr, Inkscape::Util::Quantity::convert(1, "px", "pt"), Inkscape::Util::Quantity::convert(1, "px", "pt")); + } else if (cairo_surface_get_content(_surface) != CAIRO_CONTENT_ALPHA) { + // set background color on non-alpha surfaces + // TODO: bgcolor should be derived from SPDocument (see IconImpl) + cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0); + cairo_rectangle(_cr, 0, 0, _width, _height); + cairo_fill(_cr); + } + + _is_valid = TRUE; + + return true; +} + +void +CairoRenderContext::_setSurfaceMetadata(cairo_surface_t *surface) +{ + switch (_target) { +#if defined CAIRO_HAS_PDF_SURFACE && CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4) + case CAIRO_SURFACE_TYPE_PDF: + if (!_metadata.title.empty()) { + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_TITLE, _metadata.title.c_str()); + } + if (!_metadata.author.empty()) { + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_AUTHOR, _metadata.author.c_str()); + } + if (!_metadata.subject.empty()) { + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_SUBJECT, _metadata.subject.c_str()); + } + if (!_metadata.keywords.empty()) { + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_KEYWORDS, _metadata.keywords.c_str()); + } + if (!_metadata.creator.empty()) { + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATOR, _metadata.creator.c_str()); + } + if (!_metadata.cdate.empty()) { + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATE_DATE, _metadata.cdate.c_str()); + } + if (!_metadata.mdate.empty()) { + cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_MOD_DATE, _metadata.mdate.c_str()); + } + break; +#endif +#if defined CAIRO_HAS_PS_SURFACE + case CAIRO_SURFACE_TYPE_PS: + if (!_metadata.title.empty()) { + cairo_ps_surface_dsc_comment(surface, (Glib::ustring("%%Title: ") + _metadata.title).c_str()); + } + if (!_metadata.copyright.empty()) { + cairo_ps_surface_dsc_comment(surface, (Glib::ustring("%%Copyright: ") + _metadata.copyright).c_str()); + } + break; +#endif + default: + g_warning("unsupported target %d\n", _target); + } +} + +bool +CairoRenderContext::finish(bool finish_surface) +{ + g_assert( _is_valid ); + + if (_vector_based_target && finish_surface) + cairo_show_page(_cr); + + cairo_status_t status = cairo_status(_cr); + if (status != CAIRO_STATUS_SUCCESS) + g_critical("error while rendering output: %s", cairo_status_to_string(status)); + + cairo_destroy(_cr); + _cr = nullptr; + + if (finish_surface) + cairo_surface_finish(_surface); + status = cairo_surface_status(_surface); + cairo_surface_destroy(_surface); + _surface = nullptr; + + if (_layout) + g_object_unref(_layout); + + _is_valid = FALSE; + + if (_vector_based_target && _stream) { + /* Flush stream to be sure. */ + (void) fflush(_stream); + + fclose(_stream); + _stream = nullptr; + } + + if (status == CAIRO_STATUS_SUCCESS) + return true; + else + return false; +} + +void +CairoRenderContext::transform(Geom::Affine const &transform) +{ + g_assert( _is_valid ); + + cairo_matrix_t matrix; + _initCairoMatrix(&matrix, transform); + cairo_transform(_cr, &matrix); + + // store new CTM + _state->transform = getTransform(); +} + +void +CairoRenderContext::setTransform(Geom::Affine const &transform) +{ + g_assert( _is_valid ); + + cairo_matrix_t matrix; + _initCairoMatrix(&matrix, transform); + cairo_set_matrix(_cr, &matrix); + _state->transform = transform; +} + +Geom::Affine CairoRenderContext::getTransform() const +{ + g_assert( _is_valid ); + + cairo_matrix_t ctm; + cairo_get_matrix(_cr, &ctm); + Geom::Affine ret; + ret[0] = ctm.xx; + ret[1] = ctm.yx; + ret[2] = ctm.xy; + ret[3] = ctm.yy; + ret[4] = ctm.x0; + ret[5] = ctm.y0; + return ret; +} + +Geom::Affine CairoRenderContext::getParentTransform() const +{ + g_assert( _is_valid ); + + CairoRenderState *parent_state = getParentState(); + return parent_state->transform; +} + +void CairoRenderContext::pushState() +{ + g_assert( _is_valid ); + + cairo_save(_cr); + + CairoRenderState *new_state = _createState(); + // copy current state's transform + new_state->transform = _state->transform; + _state_stack.push_back(new_state); + _state = new_state; +} + +void CairoRenderContext::popState() +{ + g_assert( _is_valid ); + + cairo_restore(_cr); + + g_free(_state_stack.back()); + _state_stack.pop_back(); + + g_assert( !_state_stack.empty()); + _state = _state_stack.back(); +} + +static bool pattern_hasItemChildren(SPPattern *pat) +{ + for (auto& child: pat->children) { + if (SP_IS_ITEM (&child)) { + return true; + } + } + return false; +} + +cairo_pattern_t* +CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox) +{ + g_assert( SP_IS_PATTERN(paintserver) ); + + SPPattern *pat = SP_PATTERN (paintserver); + + Geom::Affine ps2user, pcs2dev; + ps2user = Geom::identity(); + pcs2dev = Geom::identity(); + + double x = pat->x(); + double y = pat->y(); + double width = pat->width(); + double height = pat->height(); + double bbox_width_scaler; + double bbox_height_scaler; + + TRACE(("%f x %f pattern\n", width, height)); + + if (pbox && pat->patternUnits() == SPPattern::UNITS_OBJECTBOUNDINGBOX) { + bbox_width_scaler = pbox->width(); + bbox_height_scaler = pbox->height(); + ps2user[4] = x * bbox_width_scaler + pbox->left(); + ps2user[5] = y * bbox_height_scaler + pbox->top(); + } else { + bbox_width_scaler = 1.0; + bbox_height_scaler = 1.0; + ps2user[4] = x; + ps2user[5] = y; + } + + // apply pattern transformation + Geom::Affine pattern_transform(pat->getTransform()); + ps2user *= pattern_transform; + Geom::Point ori (ps2user[4], ps2user[5]); + + // create pattern contents coordinate system + if (pat->viewBox_set) { + Geom::Rect view_box = *pat->viewbox(); + + double x, y, w, h; + x = 0; + y = 0; + w = width * bbox_width_scaler; + h = height * bbox_height_scaler; + + //calculatePreserveAspectRatio(pat->aspect_align, pat->aspect_clip, view_width, view_height, &x, &y, &w, &h); + pcs2dev[0] = w / view_box.width(); + pcs2dev[3] = h / view_box.height(); + pcs2dev[4] = x - view_box.left() * pcs2dev[0]; + pcs2dev[5] = y - view_box.top() * pcs2dev[3]; + } else if (pbox && pat->patternContentUnits() == SPPattern::UNITS_OBJECTBOUNDINGBOX) { + pcs2dev[0] = pbox->width(); + pcs2dev[3] = pbox->height(); + } + + // Calculate the size of the surface which has to be created +#define SUBPIX_SCALE 100 + // Cairo requires an integer pattern surface width/height. + // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel. + // Multiply by SUBPIX_SCALE to allow for less than a pixel precision + double surface_width = MAX(ceil(SUBPIX_SCALE * bbox_width_scaler * width - 0.5), 1); + double surface_height = MAX(ceil(SUBPIX_SCALE * bbox_height_scaler * height - 0.5), 1); + TRACE(("pattern surface size: %f x %f\n", surface_width, surface_height)); + // create new rendering context + CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height); + + // adjust the size of the painted pattern to fit exactly the created surface + // this has to be done because of the rounding to obtain an integer pattern surface width/height + double scale_width = surface_width / (bbox_width_scaler * width); + double scale_height = surface_height / (bbox_height_scaler * height); + if (scale_width != 1.0 || scale_height != 1.0 || _vector_based_target) { + TRACE(("needed to scale with %f %f\n", scale_width, scale_height)); + pcs2dev *= Geom::Scale(SUBPIX_SCALE,SUBPIX_SCALE); + ps2user *= Geom::Scale(1.0/SUBPIX_SCALE,1.0/SUBPIX_SCALE); + } + + // despite scaling up/down by subpixel scaler, the origin point of the pattern must be the same + ps2user[4] = ori[Geom::X]; + ps2user[5] = ori[Geom::Y]; + + pattern_ctx->setTransform(pcs2dev); + pattern_ctx->pushState(); + + // create drawing and group + Inkscape::Drawing drawing; + unsigned dkey = SPItem::display_key_new(1); + + // show items and render them + for (SPPattern *pat_i = pat; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) { + if (pat_i && SP_IS_OBJECT(pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children + for (auto& child: pat_i->children) { + if (SP_IS_ITEM(&child)) { + SP_ITEM(&child)->invoke_show(drawing, dkey, SP_ITEM_REFERENCE_FLAGS); + _renderer->renderItem(pattern_ctx, SP_ITEM(&child)); + } + } + break; // do not go further up the chain if children are found + } + } + + pattern_ctx->popState(); + + // setup a cairo_pattern_t + cairo_surface_t *pattern_surface = pattern_ctx->getSurface(); + TEST(pattern_ctx->saveAsPng("pattern.png")); + cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface); + cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT); + + // set pattern transformation + cairo_matrix_t pattern_matrix; + _initCairoMatrix(&pattern_matrix, ps2user); + cairo_matrix_invert(&pattern_matrix); + cairo_pattern_set_matrix(result, &pattern_matrix); + + delete pattern_ctx; + + // hide all items + for (SPPattern *pat_i = pat; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) { + if (pat_i && SP_IS_OBJECT(pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children + for (auto& child: pat_i->children) { + if (SP_IS_ITEM(&child)) { + SP_ITEM(&child)->invoke_hide(dkey); + } + } + break; // do not go further up the chain if children are found + } + } + + return result; +} + +cairo_pattern_t* +CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox) { + SPHatch const *hatch = dynamic_cast<SPHatch const *>(paintserver); + g_assert( hatch ); + + g_assert(hatch->pitch() > 0); + + // create drawing and group + Inkscape::Drawing drawing; + unsigned dkey = SPItem::display_key_new(1); + + // TODO need to refactor 'evil' referenced code for const correctness. + SPHatch *evil = const_cast<SPHatch *>(hatch); + evil->show(drawing, dkey, pbox); + + SPHatch::RenderInfo render_info = hatch->calculateRenderInfo(dkey); + Geom::Rect tile_rect = render_info.tile_rect; + + // Cairo requires an integer pattern surface width/height. + // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel. + // Multiply by SUBPIX_SCALE to allow for less than a pixel precision + const int subpix_scale = 10; + double surface_width = MAX(ceil(subpix_scale * tile_rect.width() - 0.5), 1); + double surface_height = MAX(ceil(subpix_scale * tile_rect.height() - 0.5), 1); + Geom::Affine drawing_scale = Geom::Scale(surface_width / tile_rect.width(), surface_height / tile_rect.height()); + Geom::Affine drawing_transform = Geom::Translate(-tile_rect.min()) * drawing_scale; + + Geom::Affine child_transform = render_info.child_transform; + child_transform *= drawing_transform; + + //The rendering of hatch overflow is implemented by repeated drawing + //of hatch paths over one strip. Within each iteration paths are moved by pitch value. + //The movement progresses from right to left. This gives the same result + //as drawing whole strips in left-to-right order. + gdouble overflow_right_strip = 0.0; + int overflow_steps = 1; + Geom::Affine overflow_transform; + if (hatch->style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) { + Geom::Interval bounds = hatch->bounds(); + overflow_right_strip = floor(bounds.max() / hatch->pitch()) * hatch->pitch(); + overflow_steps = ceil((overflow_right_strip - bounds.min()) / hatch->pitch()) + 1; + overflow_transform = Geom::Translate(hatch->pitch(), 0.0); + } + + CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height); + pattern_ctx->setTransform(child_transform); + pattern_ctx->transform(Geom::Translate(-overflow_right_strip, 0.0)); + pattern_ctx->pushState(); + + std::vector<SPHatchPath *> children(evil->hatchPaths()); + + for (int i = 0; i < overflow_steps; i++) { + for (auto path : children) { + _renderer->renderHatchPath(pattern_ctx, *path, dkey); + } + pattern_ctx->transform(overflow_transform); + } + + pattern_ctx->popState(); + + // setup a cairo_pattern_t + cairo_surface_t *pattern_surface = pattern_ctx->getSurface(); + TEST(pattern_ctx->saveAsPng("hatch.png")); + cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface); + cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT); + + Geom::Affine pattern_transform; + pattern_transform = render_info.pattern_to_user_transform.inverse() * drawing_transform; + ink_cairo_pattern_set_matrix(result, pattern_transform); + + evil->hide(dkey); + + delete pattern_ctx; + return result; +} + +cairo_pattern_t* +CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const paintserver, + Geom::OptRect const &pbox, float alpha) +{ + cairo_pattern_t *pattern = nullptr; + bool apply_bbox2user = FALSE; + + if (SP_IS_LINEARGRADIENT (paintserver)) { + + SPLinearGradient *lg=SP_LINEARGRADIENT(paintserver); + + SP_GRADIENT(lg)->ensureVector(); // when exporting from commandline, vector is not built + + Geom::Point p1 (lg->x1.computed, lg->y1.computed); + Geom::Point p2 (lg->x2.computed, lg->y2.computed); + if (pbox && SP_GRADIENT(lg)->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) { + // convert to userspace + Geom::Affine bbox2user(pbox->width(), 0, 0, pbox->height(), pbox->left(), pbox->top()); + p1 *= bbox2user; + p2 *= bbox2user; + } + + // create linear gradient pattern + pattern = cairo_pattern_create_linear(p1[Geom::X], p1[Geom::Y], p2[Geom::X], p2[Geom::Y]); + + // add stops + for (gint i = 0; unsigned(i) < lg->vector.stops.size(); i++) { + float rgb[3]; + lg->vector.stops[i].color.get_rgb_floatv(rgb); + cairo_pattern_add_color_stop_rgba(pattern, lg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], lg->vector.stops[i].opacity * alpha); + } + } else if (SP_IS_RADIALGRADIENT (paintserver)) { + + SPRadialGradient *rg=SP_RADIALGRADIENT(paintserver); + + SP_GRADIENT(rg)->ensureVector(); // when exporting from commandline, vector is not built + + Geom::Point c (rg->cx.computed, rg->cy.computed); + Geom::Point f (rg->fx.computed, rg->fy.computed); + double r = rg->r.computed; + double fr = rg->fr.computed; + if (pbox && SP_GRADIENT(rg)->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) + apply_bbox2user = true; + + // create radial gradient pattern + pattern = cairo_pattern_create_radial(f[Geom::X], f[Geom::Y], fr, c[Geom::X], c[Geom::Y], r); + + // add stops + for (gint i = 0; unsigned(i) < rg->vector.stops.size(); i++) { + float rgb[3]; + rg->vector.stops[i].color.get_rgb_floatv(rgb); + cairo_pattern_add_color_stop_rgba(pattern, rg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], rg->vector.stops[i].opacity * alpha); + } + } else if (SP_IS_MESHGRADIENT (paintserver)) { + SPMeshGradient *mg = SP_MESHGRADIENT(paintserver); + + pattern = mg->pattern_new(_cr, pbox, 1.0); + } else if (SP_IS_PATTERN (paintserver)) { + pattern = _createPatternPainter(paintserver, pbox); + } else if ( dynamic_cast<SPHatch const *>(paintserver) ) { + pattern = _createHatchPainter(paintserver, pbox); + } else { + return nullptr; + } + + if (pattern && SP_IS_GRADIENT(paintserver)) { + SPGradient *g = SP_GRADIENT(paintserver); + + // set extend type + SPGradientSpread spread = g->fetchSpread(); + switch (spread) { + case SP_GRADIENT_SPREAD_REPEAT: { + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); + break; + } + case SP_GRADIENT_SPREAD_REFLECT: { // not supported by cairo-pdf yet + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REFLECT); + break; + } + case SP_GRADIENT_SPREAD_PAD: { // not supported by cairo-pdf yet + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); + break; + } + default: { + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_NONE); + break; + } + } + + cairo_matrix_t pattern_matrix; + if (g->gradientTransform_set) { + // apply gradient transformation + cairo_matrix_init(&pattern_matrix, + g->gradientTransform[0], g->gradientTransform[1], + g->gradientTransform[2], g->gradientTransform[3], + g->gradientTransform[4], g->gradientTransform[5]); + } else { + cairo_matrix_init_identity (&pattern_matrix); + } + + if (apply_bbox2user) { + // convert to userspace + cairo_matrix_t bbox2user; + cairo_matrix_init (&bbox2user, pbox->width(), 0, 0, pbox->height(), pbox->left(), pbox->top()); + cairo_matrix_multiply (&pattern_matrix, &bbox2user, &pattern_matrix); + } + cairo_matrix_invert(&pattern_matrix); // because Cairo expects a userspace->patternspace matrix + cairo_pattern_set_matrix(pattern, &pattern_matrix); + } + + return pattern; +} + +void +CairoRenderContext::_setFillStyle(SPStyle const *const style, Geom::OptRect const &pbox) +{ + g_return_if_fail( !style->fill.set + || style->fill.isColor() + || style->fill.isPaintserver() ); + + float alpha = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + if (_state->merge_opacity) { + alpha *= _state->opacity; + TRACE(("merged op=%f\n", alpha)); + } + + SPPaintServer const *paint_server = style->getFillPaintServer(); + if (paint_server && paint_server->isValid()) { + + g_assert(SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style)) + || SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style)) + || dynamic_cast<SPHatch *>(SP_STYLE_FILL_SERVER(style))); + + cairo_pattern_t *pattern = _createPatternForPaintServer(paint_server, pbox, alpha); + if (pattern) { + cairo_set_source(_cr, pattern); + cairo_pattern_destroy(pattern); + } + } else if (style->fill.colorSet) { + float rgb[3]; + style->fill.value.color.get_rgb_floatv(rgb); + + cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); + + } else { // unset fill is black + g_assert(!style->fill.set + || (paint_server && !paint_server->isValid())); + + cairo_set_source_rgba(_cr, 0, 0, 0, alpha); + } +} + +void +CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect const &pbox) +{ + float alpha = SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); + if (_state->merge_opacity) + alpha *= _state->opacity; + + if (style->stroke.isColor() || (style->stroke.isPaintserver() && !style->getStrokePaintServer()->isValid())) { + float rgb[3]; + style->stroke.value.color.get_rgb_floatv(rgb); + + cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha); + } else { + g_assert( style->stroke.isPaintserver() + || SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style)) + || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style)) + || dynamic_cast<SPHatch *>(SP_STYLE_STROKE_SERVER(style))); + + cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_STROKE_SERVER(style), pbox, alpha); + + if (pattern) { + cairo_set_source(_cr, pattern); + cairo_pattern_destroy(pattern); + } + } + + if (!style->stroke_dasharray.values.empty()) + { + size_t ndashes = style->stroke_dasharray.values.size(); + double* dashes =(double*)malloc(ndashes*sizeof(double)); + for( unsigned i = 0; i < ndashes; ++i ) { + dashes[i] = style->stroke_dasharray.values[i].value; + } + cairo_set_dash(_cr, dashes, ndashes, style->stroke_dashoffset.value); + free(dashes); + } else { + cairo_set_dash(_cr, nullptr, 0, 0.0); // disable dashing + } + + cairo_set_line_width(_cr, style->stroke_width.computed); + + // set line join type + cairo_line_join_t join = CAIRO_LINE_JOIN_MITER; + switch (style->stroke_linejoin.computed) { + case SP_STROKE_LINEJOIN_MITER: + join = CAIRO_LINE_JOIN_MITER; + break; + case SP_STROKE_LINEJOIN_ROUND: + join = CAIRO_LINE_JOIN_ROUND; + break; + case SP_STROKE_LINEJOIN_BEVEL: + join = CAIRO_LINE_JOIN_BEVEL; + break; + } + cairo_set_line_join(_cr, join); + + // set line cap type + cairo_line_cap_t cap = CAIRO_LINE_CAP_BUTT; + switch (style->stroke_linecap.computed) { + case SP_STROKE_LINECAP_BUTT: + cap = CAIRO_LINE_CAP_BUTT; + break; + case SP_STROKE_LINECAP_ROUND: + cap = CAIRO_LINE_CAP_ROUND; + break; + case SP_STROKE_LINECAP_SQUARE: + cap = CAIRO_LINE_CAP_SQUARE; + break; + } + cairo_set_line_cap(_cr, cap); + cairo_set_miter_limit(_cr, MAX(1, style->stroke_miterlimit.value)); +} + +void +CairoRenderContext::_prepareRenderGraphic() +{ + // Only PDFLaTeX supports importing a single page of a graphics file, + // so only PDF backend gets interleaved text/graphics + if (_is_omittext && _target == CAIRO_SURFACE_TYPE_PDF && _render_mode != RENDER_MODE_CLIP) { + if (_omittext_state == NEW_PAGE_ON_GRAPHIC) { + // better set this immediately (not sure if masks applied during "popLayer" could call + // this function, too, triggering the same code again in error + _omittext_state = GRAPHIC_ON_TOP; + + // As we can not emit the page in the middle of a layer (aka group) - it will not be fully painted yet! - + // the following basically mirrors the calls in CairoRenderer::renderItem (but in reversed order) + // - first traverse all saved states in reversed order (i.e. from deepest nesting to the top) + // and apply clipping / masking to layers on the way (this is done in popLayer) + // - then emit the page using cairo_show_page() + // - finally restore the previous state with proper transforms and appropriate layers again + // + // TODO: While this appears to be an ugly hack it seems to work + // Somebody with a more intimate understanding of cairo and the renderer implementation might + // be able to implement this in a cleaner way, though. + int stack_size = _state_stack.size(); + for (int i = stack_size-1; i > 0; i--) { + if (_state_stack[i]->need_layer) + popLayer(); + cairo_restore(_cr); + _state = _state_stack[i-1]; + } + + cairo_show_page(_cr); + + for (int i = 1; i < stack_size; i++) { + cairo_save(_cr); + _state = _state_stack[i]; + if (_state->need_layer) + pushLayer(); + setTransform(_state->transform); + } + } + _omittext_state = GRAPHIC_ON_TOP; + } +} + +void +CairoRenderContext::_prepareRenderText() +{ + // Only PDFLaTeX supports importing a single page of a graphics file, + // so only PDF backend gets interleaved text/graphics + if (_is_omittext && _target == CAIRO_SURFACE_TYPE_PDF) { + if (_omittext_state == GRAPHIC_ON_TOP) + _omittext_state = NEW_PAGE_ON_GRAPHIC; + } +} + +/* We need CairoPaintOrder as markers are rendered in a separate step and may be rendered + * in between fill and stroke. + */ +bool +CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle const *style, Geom::OptRect const &pbox, CairoPaintOrder order) +{ + g_assert( _is_valid ); + + _prepareRenderGraphic(); + + if (_render_mode == RENDER_MODE_CLIP) { + if (_clip_mode == CLIP_MODE_PATH) { + addClipPath(pathv, &style->fill_rule); + } else { + setPathVector(pathv); + if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); + } else { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); + } + if (style->mix_blend_mode.set && style->mix_blend_mode.value) { + cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); + } + cairo_fill(_cr); + TEST(cairo_surface_write_to_png (_surface, "pathmask.png")); + } + return true; + } + + bool no_fill = style->fill.isNone() || style->fill_opacity.value == 0 || + order == STROKE_ONLY; + bool no_stroke = style->stroke.isNone() || style->stroke_width.computed < 1e-9 || + style->stroke_opacity.value == 0 || order == FILL_ONLY; + + if (no_fill && no_stroke) + return true; + + bool need_layer = ( !_state->merge_opacity && !_state->need_layer && + ( _state->opacity != 1.0 || _state->clip_path != nullptr || _state->mask != nullptr ) ); + bool blend = false; + if (style->mix_blend_mode.set && style->mix_blend_mode.value != SP_CSS_BLEND_NORMAL) { + need_layer = true; + blend = true; + } + if (!need_layer) + cairo_save(_cr); + else + pushLayer(); + + if (!no_fill) { + if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); + } else { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); + } + } + + setPathVector(pathv); + + if (!no_fill && (order == STROKE_OVER_FILL || order == FILL_ONLY)) { + _setFillStyle(style, pbox); + + if (no_stroke) + cairo_fill(_cr); + else + cairo_fill_preserve(_cr); + } + + if (!no_stroke) { + _setStrokeStyle(style, pbox); + + if (no_fill || order == STROKE_OVER_FILL) + cairo_stroke(_cr); + else + cairo_stroke_preserve(_cr); + } + + if (!no_fill && order == FILL_OVER_STROKE) { + _setFillStyle(style, pbox); + + cairo_fill(_cr); + } + + if (need_layer) { + if (blend) { + popLayer(ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); + } else { + popLayer(); + } + } else { + cairo_restore(_cr); + } + + return true; +} + +bool CairoRenderContext::renderImage(Inkscape::Pixbuf *pb, + Geom::Affine const &image_transform, SPStyle const *style) +{ + g_assert( _is_valid ); + + if (_render_mode == RENDER_MODE_CLIP) { + return true; + } + + _prepareRenderGraphic(); + + int w = pb->width(); + int h = pb->height(); + + // TODO: reenable merge_opacity if useful + float opacity = _state->opacity; + + cairo_surface_t *image_surface = pb->getSurfaceRaw(); + if (cairo_surface_status(image_surface)) { + TRACE(("Image surface creation failed:\n%s\n", cairo_status_to_string(cairo_surface_status(image_surface)))); + return false; + } + + cairo_save(_cr); + + // scaling by width & height is not needed because it will be done by Cairo + transform(image_transform); + + cairo_set_source_surface(_cr, image_surface, 0.0, 0.0); + + // set clip region so that the pattern will not be repeated (bug in Cairo-PDF) + if (_vector_based_target) { + cairo_new_path(_cr); + cairo_rectangle(_cr, 0, 0, w, h); + cairo_clip(_cr); + } + + // Cairo filter method will be mapped to PS/PDF 'interpolate' true/false). + // See cairo-pdf-surface.c + if (style) { + // See: http://www.w3.org/TR/SVG/painting.html#ImageRenderingProperty + // https://drafts.csswg.org/css-images-3/#the-image-rendering + // style.h/style.cpp, drawing-image.cpp + // + // CSS 3 defines: + // 'optimizeSpeed' as alias for "pixelated" + // 'optimizeQuality' as alias for "smooth" + switch (style->image_rendering.computed) { + case SP_CSS_IMAGE_RENDERING_OPTIMIZESPEED: + case SP_CSS_IMAGE_RENDERING_PIXELATED: + // we don't have an implementation for crisp-edges, but it should *not* smooth or blur + case SP_CSS_IMAGE_RENDERING_CRISPEDGES: + cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_NEAREST); + break; + case SP_CSS_IMAGE_RENDERING_OPTIMIZEQUALITY: + case SP_CSS_IMAGE_RENDERING_AUTO: + default: + cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_BEST); + break; + } + } + + if (style->mix_blend_mode.set && style->mix_blend_mode.value) { + cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); + } + + cairo_paint(_cr); + + cairo_restore(_cr); + return true; +} + +#define GLYPH_ARRAY_SIZE 64 + +// TODO investigate why the font is being ignored: +unsigned int CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont * /*font*/, std::vector<CairoGlyphInfo> const &glyphtext, bool path) +{ + cairo_glyph_t glyph_array[GLYPH_ARRAY_SIZE]; + cairo_glyph_t *glyphs = glyph_array; + unsigned int num_glyphs = glyphtext.size(); + if (num_glyphs > GLYPH_ARRAY_SIZE) { + glyphs = (cairo_glyph_t*)g_try_malloc(sizeof(cairo_glyph_t) * num_glyphs); + if(glyphs == nullptr) { + g_warning("CairorenderContext::_showGlyphs: can not allocate memory for %d glyphs.", num_glyphs); + return 0; + } + } + + unsigned int num_invalid_glyphs = 0; + unsigned int i = 0; // is a counter for indexing the glyphs array, only counts the valid glyphs + for (const auto & it_info : glyphtext) { + // skip glyphs which are PANGO_GLYPH_EMPTY (0x0FFFFFFF) + // or have the PANGO_GLYPH_UNKNOWN_FLAG (0x10000000) set + if (it_info.index == 0x0FFFFFFF || it_info.index & 0x10000000) { + TRACE(("INVALID GLYPH found\n")); + g_message("Invalid glyph found, continuing..."); + num_invalid_glyphs++; + continue; + } + glyphs[i].index = it_info.index; + glyphs[i].x = it_info.x; + glyphs[i].y = it_info.y; + i++; + } + + if (path) { + cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs); + } else { + cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs); + } + + if (num_glyphs > GLYPH_ARRAY_SIZE) { + g_free(glyphs); + } + + return num_glyphs - num_invalid_glyphs; +} + +bool +CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Affine const &font_matrix, + std::vector<CairoGlyphInfo> const &glyphtext, SPStyle const *style) +{ + + _prepareRenderText(); + if (_is_omittext) + return true; + + // create a cairo_font_face from PangoFont + // double size = style->font_size.computed; /// \fixme why is this variable never used? + gpointer fonthash = (gpointer)font; + cairo_font_face_t *font_face = nullptr; + if(font_table.find(fonthash)!=font_table.end()) + font_face = font_table[fonthash]; + + FcPattern *fc_pattern = nullptr; + +#ifdef USE_PANGO_WIN32 +# ifdef CAIRO_HAS_WIN32_FONT + LOGFONTA *lfa = pango_win32_font_logfont(font); + LOGFONTW lfw; + + ZeroMemory(&lfw, sizeof(LOGFONTW)); + memcpy(&lfw, lfa, sizeof(LOGFONTA)); + MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, lfa->lfFaceName, LF_FACESIZE, lfw.lfFaceName, LF_FACESIZE); + + if(font_face == NULL) { + font_face = cairo_win32_font_face_create_for_logfontw(&lfw); + font_table[fonthash] = font_face; + } +# endif +#else +# ifdef CAIRO_HAS_FT_FONT + PangoFcFont *fc_font = PANGO_FC_FONT(font); + fc_pattern = fc_font->font_pattern; + if(font_face == nullptr) { + font_face = cairo_ft_font_face_create_for_pattern(fc_pattern); + font_table[fonthash] = font_face; + } +# endif +#endif + + cairo_save(_cr); + cairo_set_font_face(_cr, font_face); + + //if (fc_pattern && FcPatternGetDouble(fc_pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch) + // size = 12.0; + + // set the given font matrix + cairo_matrix_t matrix; + _initCairoMatrix(&matrix, font_matrix); + cairo_set_font_matrix(_cr, &matrix); + + if (_render_mode == RENDER_MODE_CLIP) { + if (_clip_mode == CLIP_MODE_MASK) { + if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD); + } else { + cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING); + } + _showGlyphs(_cr, font, glyphtext, FALSE); + } else { + // just add the glyph paths to the current context + _showGlyphs(_cr, font, glyphtext, TRUE); + } + } else { + + bool fill = false; + if (style->fill.isColor() || style->fill.isPaintserver()) { + fill = true; + } + + bool stroke = false; + if (style->stroke.isColor() || style->stroke.isPaintserver()) { + stroke = true; + } + + if (style->mix_blend_mode.set && style->mix_blend_mode.value) { + cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); + } + + // Text never has markers + bool stroke_over_fill = true; + if ( (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE && + style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL) || + + (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE && + style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL) || + + (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE && + style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL) ) { + stroke_over_fill = false; + } + + bool have_path = false; + if (fill && stroke_over_fill) { + _setFillStyle(style, Geom::OptRect()); + if (_is_texttopath) { + _showGlyphs(_cr, font, glyphtext, true); + if (stroke) { + cairo_fill_preserve(_cr); + have_path = true; + } else { + cairo_fill(_cr); + } + } else { + _showGlyphs(_cr, font, glyphtext, false); + } + } + + if (stroke) { + _setStrokeStyle(style, Geom::OptRect()); + if (!have_path) { + _showGlyphs(_cr, font, glyphtext, true); + } + if (fill && _is_texttopath && !stroke_over_fill) { + cairo_stroke_preserve(_cr); + have_path = true; + } else { + cairo_stroke(_cr); + } + } + + if (fill && !stroke_over_fill) { + _setFillStyle(style, Geom::OptRect()); + if (_is_texttopath) { + if (!have_path) { + // Could happen if both 'stroke' and 'stroke_over_fill' are false + _showGlyphs(_cr, font, glyphtext, true); + } + cairo_fill(_cr); + } else { + _showGlyphs(_cr, font, glyphtext, false); + } + } + + } + + cairo_restore(_cr); + +// if (font_face) +// cairo_font_face_destroy(font_face); + + return true; +} + +/* Helper functions */ + +void +CairoRenderContext::setPathVector(Geom::PathVector const &pv) +{ + cairo_new_path(_cr); + addPathVector(pv); +} + +void +CairoRenderContext::addPathVector(Geom::PathVector const &pv) +{ + feed_pathvector_to_cairo(_cr, pv); +} + +void +CairoRenderContext::_concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0) +{ + cairo_matrix_t matrix; + + cairo_matrix_init(&matrix, xx, yx, xy, yy, x0, y0); + cairo_transform(cr, &matrix); +} + +void +CairoRenderContext::_initCairoMatrix(cairo_matrix_t *matrix, Geom::Affine const &transform) +{ + matrix->xx = transform[0]; + matrix->yx = transform[1]; + matrix->xy = transform[2]; + matrix->yy = transform[3]; + matrix->x0 = transform[4]; + matrix->y0 = transform[5]; +} + +void +CairoRenderContext::_concatTransform(cairo_t *cr, Geom::Affine const &transform) +{ + _concatTransform(cr, transform[0], transform[1], + transform[2], transform[3], + transform[4], transform[5]); +} + +static cairo_status_t +_write_callback(void *closure, const unsigned char *data, unsigned int length) +{ + size_t written; + FILE *file = (FILE*)closure; + + written = fwrite (data, 1, length, file); + + if (written == length) + return CAIRO_STATUS_SUCCESS; + else + return CAIRO_STATUS_WRITE_ERROR; +} + +#include "clear-n_.h" + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + +#undef TRACE +#undef TEST + + +/* + 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/extension/internal/cairo-render-context.h b/src/extension/internal/cairo-render-context.h new file mode 100644 index 0000000..f91a3f9 --- /dev/null +++ b/src/extension/internal/cairo-render-context.h @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef EXTENSION_INTERNAL_CAIRO_RENDER_CONTEXT_H_SEEN +#define EXTENSION_INTERNAL_CAIRO_RENDER_CONTEXT_H_SEEN + +/** \file + * Declaration of CairoRenderContext, a class used for rendering with Cairo. + */ +/* + * Authors: + * Miklos Erdelyi <erdelyim@gmail.com> + * + * Copyright (C) 2006 Miklos Erdelyi + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/extension.h" +#include <set> +#include <string> + +#include <2geom/forward.h> +#include <2geom/affine.h> + +#include "style-internal.h" // SPIEnum + +#include <cairo.h> + +class SPClipPath; +class SPMask; + +typedef struct _PangoFont PangoFont; +typedef struct _PangoLayout PangoLayout; + +namespace Inkscape { +class Pixbuf; + +namespace Extension { +namespace Internal { + +class CairoRenderer; +class CairoRenderContext; +struct CairoRenderState; +struct CairoGlyphInfo; + +// Holds info for rendering a glyph +struct CairoGlyphInfo { + unsigned long index; + double x; + double y; +}; + +struct CairoRenderState { + unsigned int merge_opacity : 1; // whether fill/stroke opacity can be mul'd with item opacity + unsigned int need_layer : 1; // whether object is masked, clipped, and/or has a non-zero opacity + unsigned int has_overflow : 1; + unsigned int parent_has_userspace : 1; // whether the parent's ctm should be applied + float opacity; + bool has_filtereffect; + Geom::Affine item_transform; // this item's item->transform, for correct clipping + + SPClipPath *clip_path; + SPMask* mask; + + Geom::Affine transform; // the CTM +}; + +// Metadata to set on the cairo surface (if the surface supports it) +struct CairoRenderContextMetadata { + Glib::ustring title = ""; + Glib::ustring author = ""; + Glib::ustring subject = ""; + Glib::ustring keywords = ""; + Glib::ustring copyright = ""; + Glib::ustring creator = ""; + Glib::ustring cdate = ""; // currently unused + Glib::ustring mdate = ""; // currently unused +}; + +class CairoRenderContext { + friend class CairoRenderer; +public: + CairoRenderContext *cloneMe() const; + CairoRenderContext *cloneMe(double width, double height) const; + bool finish(bool finish_surface = true); + + CairoRenderer *getRenderer() const; + cairo_t *getCairoContext() const; + + enum CairoRenderMode { + RENDER_MODE_NORMAL, + RENDER_MODE_CLIP + }; + + enum CairoClipMode { + CLIP_MODE_PATH, + CLIP_MODE_MASK + }; + + bool setImageTarget(cairo_format_t format); + bool setPdfTarget(gchar const *utf8_fn); + bool setPsTarget(gchar const *utf8_fn); + /** Set the cairo_surface_t from an external source */ + bool setSurfaceTarget(cairo_surface_t *surface, bool is_vector, cairo_matrix_t *ctm=nullptr); + + void setPSLevel(unsigned int level); + void setEPS(bool eps); + unsigned int getPSLevel(); + void setPDFLevel(unsigned int level); + void setTextToPath(bool texttopath); + bool getTextToPath(); + void setOmitText(bool omittext); + bool getOmitText(); + void setFilterToBitmap(bool filtertobitmap); + bool getFilterToBitmap(); + void setBitmapResolution(int resolution); + int getBitmapResolution(); + + /** Creates the cairo_surface_t for the context with the + given width, height and with the currently set target + surface type. Also sets supported metadata on the surface. */ + bool setupSurface(double width, double height); + + cairo_surface_t *getSurface(); + + /** Saves the contents of the context to a PNG file. */ + bool saveAsPng(const char *file_name); + + /** On targets supporting multiple pages, sends subsequent rendering to a new page*/ + void newPage(); + + /* Render/clip mode setting/query */ + void setRenderMode(CairoRenderMode mode); + CairoRenderMode getRenderMode() const; + void setClipMode(CairoClipMode mode); + CairoClipMode getClipMode() const; + + void addPathVector(Geom::PathVector const &pv); + void setPathVector(Geom::PathVector const &pv); + + void pushLayer(); + void popLayer(cairo_operator_t composite = CAIRO_OPERATOR_CLEAR); + + void tagBegin(const char* link); + void tagEnd(); + + /* Graphics state manipulation */ + void pushState(); + void popState(); + CairoRenderState *getCurrentState() const; + CairoRenderState *getParentState() const; + void setStateForStyle(SPStyle const *style); + + void transform(Geom::Affine const &transform); + void setTransform(Geom::Affine const &transform); + Geom::Affine getTransform() const; + Geom::Affine getParentTransform() const; + + /* Clipping methods */ + void addClipPath(Geom::PathVector const &pv, SPIEnum<SPWindRule> const *fill_rule); + void addClippingRect(double x, double y, double width, double height); + + /* Rendering methods */ + enum CairoPaintOrder { + STROKE_OVER_FILL, + FILL_OVER_STROKE, + FILL_ONLY, + STROKE_ONLY + }; + + bool renderPathVector(Geom::PathVector const &pathv, SPStyle const *style, Geom::OptRect const &pbox, CairoPaintOrder order = STROKE_OVER_FILL); + bool renderImage(Inkscape::Pixbuf *pb, + Geom::Affine const &image_transform, SPStyle const *style); + bool renderGlyphtext(PangoFont *font, Geom::Affine const &font_matrix, + std::vector<CairoGlyphInfo> const &glyphtext, SPStyle const *style); + + /* More general rendering methods will have to be added (like fill, stroke) */ + +protected: + CairoRenderContext(CairoRenderer *renderer); + virtual ~CairoRenderContext(); + + enum CairoOmitTextPageState { + EMPTY, + GRAPHIC_ON_TOP, + NEW_PAGE_ON_GRAPHIC + }; + + float _width; + float _height; + unsigned short _dpi; + unsigned int _pdf_level; + unsigned int _ps_level; + bool _eps; + bool _is_texttopath; + bool _is_omittext; + bool _is_filtertobitmap; + int _bitmapresolution; + + FILE *_stream; + + unsigned int _is_valid : 1; + unsigned int _vector_based_target : 1; + + cairo_t *_cr; // Cairo context + cairo_surface_t *_surface; + cairo_surface_type_t _target; + cairo_format_t _target_format; + PangoLayout *_layout; + + unsigned int _clip_rule : 8; + unsigned int _clip_winding_failed : 1; + + std::vector<CairoRenderState *> _state_stack; + CairoRenderState *_state; // the current state + + CairoRenderer *_renderer; + + CairoRenderMode _render_mode; + CairoClipMode _clip_mode; + + CairoOmitTextPageState _omittext_state; + + CairoRenderContextMetadata _metadata; + + cairo_pattern_t *_createPatternForPaintServer(SPPaintServer const *const paintserver, + Geom::OptRect const &pbox, float alpha); + cairo_pattern_t *_createPatternPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox); + cairo_pattern_t *_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox); + + unsigned int _showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoGlyphInfo> const &glyphtext, bool is_stroke); + + bool _finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t *ctm = nullptr); + void _setSurfaceMetadata(cairo_surface_t *surface); + + void _setFillStyle(SPStyle const *style, Geom::OptRect const &pbox); + void _setStrokeStyle(SPStyle const *style, Geom::OptRect const &pbox); + + void _initCairoMatrix(cairo_matrix_t *matrix, Geom::Affine const &transform); + void _concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0); + void _concatTransform(cairo_t *cr, Geom::Affine const &transform); + + void _prepareRenderGraphic(); + void _prepareRenderText(); + + std::map<gpointer, cairo_font_face_t *> font_table; + static void font_data_free(gpointer data); + + CairoRenderState *_createState(); +}; + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* !EXTENSION_INTERNAL_CAIRO_RENDER_CONTEXT_H_SEEN */ + +/* + 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/extension/internal/cairo-renderer-pdf-out.cpp b/src/extension/internal/cairo-renderer-pdf-out.cpp new file mode 100644 index 0000000..655c76c --- /dev/null +++ b/src/extension/internal/cairo-renderer-pdf-out.cpp @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A quick hack to use the Cairo renderer to write out a file. This + * then makes 'save as...' PDF. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Ulf Erikson <ulferikson@users.sf.net> + * Johan Engelen <goejendaagh@zonnet.nl> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2004-2010 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cairo.h> +#ifdef CAIRO_HAS_PDF_SURFACE + +#include "cairo-renderer-pdf-out.h" +#include "cairo-render-context.h" +#include "cairo-renderer.h" +#include "latex-text-renderer.h" +#include <print.h> +#include "extension/system.h" +#include "extension/print.h" +#include "extension/db.h" +#include "extension/output.h" +#include "display/drawing.h" + +#include "display/curve.h" +#include "display/canvas-bpath.h" +#include "object/sp-item.h" +#include "object/sp-root.h" + +#include <2geom/affine.h> +#include "document.h" + +#include "util/units.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +bool CairoRendererPdfOutput::check(Inkscape::Extension::Extension * /*module*/) +{ + bool result = true; + + if (nullptr == Inkscape::Extension::db.get("org.inkscape.output.pdf.cairorenderer")) { + result = false; + } + + return result; +} + +static bool +pdf_render_document_to_file(SPDocument *doc, gchar const *filename, unsigned int level, + bool texttopath, bool omittext, bool filtertobitmap, int resolution, + const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px) +{ + doc->ensureUpToDate(); + + SPRoot *root = doc->getRoot(); + SPItem *base = nullptr; + + bool pageBoundingBox = TRUE; + if (exportId && strcmp(exportId, "")) { + // we want to export the given item only + base = SP_ITEM(doc->getObjectById(exportId)); + if (!base) { + throw Inkscape::Extension::Output::export_id_not_found(exportId); + } + root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only) + pageBoundingBox = exportCanvas; + } + else { + // we want to export the entire document from root + base = root; + pageBoundingBox = !exportDrawing; + } + + if (!base) { + return false; + } + + /* Create new arena */ + Inkscape::Drawing drawing; + drawing.setExact(true); + unsigned dkey = SPItem::display_key_new(1); + root->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY); + + /* Create renderer and context */ + CairoRenderer *renderer = new CairoRenderer(); + CairoRenderContext *ctx = renderer->createContext(); + ctx->setPDFLevel(level); + ctx->setTextToPath(texttopath); + ctx->setOmitText(omittext); + ctx->setFilterToBitmap(filtertobitmap); + ctx->setBitmapResolution(resolution); + + bool ret = ctx->setPdfTarget (filename); + if(ret) { + /* Render document */ + ret = renderer->setupDocument(ctx, doc, pageBoundingBox, bleedmargin_px, base); + if (ret) { + renderer->renderItem(ctx, root); + ret = ctx->finish(); + } + } + + root->invoke_hide(dkey); + + renderer->destroyContext(ctx); + delete renderer; + + return ret; +} + +/** + \brief This function calls the output module with the filename + \param mod unused + \param doc Document to be saved + \param filename Filename to save to (probably will end in .pdf) + + The most interesting thing that this function does is just attach + an '>' on the front of the filename. This is the syntax used to + tell the printing system to save to file. +*/ +void +CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) +{ + Inkscape::Extension::Extension * ext; + unsigned int ret; + + ext = Inkscape::Extension::db.get("org.inkscape.output.pdf.cairorenderer"); + if (ext == nullptr) + return; + + int level = 0; + try { + const gchar *new_level = mod->get_param_optiongroup("PDFversion"); + if((new_level != nullptr) && (g_ascii_strcasecmp("PDF-1.5", new_level) == 0)) { + level = 1; + } + } + catch(...) { + g_warning("Parameter <PDFversion> might not exist"); + } + + bool new_textToPath = FALSE; + try { + new_textToPath = (strcmp(mod->get_param_optiongroup("textToPath"), "paths") == 0); + } + catch(...) { + g_warning("Parameter <textToPath> might not exist"); + } + + bool new_textToLaTeX = FALSE; + try { + new_textToLaTeX = (strcmp(mod->get_param_optiongroup("textToPath"), "LaTeX") == 0); + } + catch(...) { + g_warning("Parameter <textToLaTeX> might not exist"); + } + + bool new_blurToBitmap = FALSE; + try { + new_blurToBitmap = mod->get_param_bool("blurToBitmap"); + } + catch(...) { + g_warning("Parameter <blurToBitmap> might not exist"); + } + + int new_bitmapResolution = 72; + try { + new_bitmapResolution = mod->get_param_int("resolution"); + } + catch(...) { + g_warning("Parameter <resolution> might not exist"); + } + + const gchar *new_exportId = nullptr; + try { + new_exportId = mod->get_param_string("exportId"); + } + catch(...) { + g_warning("Parameter <exportId> might not exist"); + } + + bool new_exportCanvas = true; + try { + new_exportCanvas = (strcmp(ext->get_param_optiongroup("area"), "page") == 0); + } catch(...) { + g_warning("Parameter <area> might not exist"); + } + bool new_exportDrawing = !new_exportCanvas; + + float new_bleedmargin_px = 0.; + try { + new_bleedmargin_px = Inkscape::Util::Quantity::convert(mod->get_param_float("bleed"), "mm", "px"); + } + catch(...) { + g_warning("Parameter <bleed> might not exist"); + } + + // Create PDF file + { + gchar * final_name; + final_name = g_strdup_printf("> %s", filename); + ret = pdf_render_document_to_file(doc, final_name, level, + new_textToPath, new_textToLaTeX, new_blurToBitmap, new_bitmapResolution, + new_exportId, new_exportDrawing, new_exportCanvas, new_bleedmargin_px); + g_free(final_name); + + if (!ret) + throw Inkscape::Extension::Output::save_failed(); + } + + // Create LaTeX file (if requested) + if (new_textToLaTeX) { + ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_exportDrawing, new_exportCanvas, new_bleedmargin_px, true); + + if (!ret) + throw Inkscape::Extension::Output::save_failed(); + } +} + +#include "clear-n_.h" + +/** + \brief A function allocate a copy of this function. + + This is the definition of Cairo PDF out. This function just + calls the extension system with the memory allocated XML that + describes the data. +*/ +void +CairoRendererPdfOutput::init () +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>Portable Document Format</name>\n" + "<id>org.inkscape.output.pdf.cairorenderer</id>\n" + "<param name=\"PDFversion\" gui-text=\"" N_("Restrict to PDF version:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value='PDF-1.5'>" N_("PDF 1.5") "</option>\n" + "<option value='PDF-1.4'>" N_("PDF 1.4") "</option>\n" + "</param>\n" + "<param name=\"textToPath\" gui-text=\"" N_("Text output options:") "\" type=\"optiongroup\" appearance=\"radio\">\n" + "<option value=\"embed\">" N_("Embed fonts") "</option>\n" + "<option value=\"paths\">" N_("Convert text to paths") "</option>\n" + "<option value=\"LaTeX\">" N_("Omit text in PDF and create LaTeX file") "</option>\n" + "</param>\n" + "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"bool\">true</param>\n" + "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">96</param>\n" + "<param name=\"area\" gui-text=\"" N_("Output page size:") "\" type=\"optiongroup\" appearance=\"radio\" >\n" + "<option value=\"page\">" N_("Use document's page size") "</option>" + "<option value=\"drawing\">" N_("Use exported object's size") "</option>" + "</param>" + "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm):") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n" + "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n" + "<output>\n" + "<extension>.pdf</extension>\n" + "<mimetype>application/pdf</mimetype>\n" + "<filetypename>Portable Document Format (*.pdf)</filetypename>\n" + "<filetypetooltip>PDF File</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new CairoRendererPdfOutput()); + + return; +} + +} } } /* namespace Inkscape, Extension, Internal */ + +#endif /* HAVE_CAIRO_PDF */ diff --git a/src/extension/internal/cairo-renderer-pdf-out.h b/src/extension/internal/cairo-renderer-pdf-out.h new file mode 100644 index 0000000..9f99012 --- /dev/null +++ b/src/extension/internal/cairo-renderer-pdf-out.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A quick hack to use the Cairo renderer to write out a file. This + * then makes 'save as...' PDF. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Ulf Erikson <ulferikson@users.sf.net> + * + * Copyright (C) 2004-2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef EXTENSION_INTERNAL_CAIRO_RENDERER_PDF_OUT_H +#define EXTENSION_INTERNAL_CAIRO_RENDERER_PDF_OUT_H + +#include "extension/implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class CairoRendererPdfOutput : Inkscape::Extension::Implementation::Implementation { + +public: + bool check(Inkscape::Extension::Extension *module) override; + void save(Inkscape::Extension::Output *mod, + SPDocument *doc, + gchar const *filename) override; + static void init(); +}; + +} } } /* namespace Inkscape, Extension, Internal */ + +#endif /* !EXTENSION_INTERNAL_CAIRO_RENDERER_PDF_OUT_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/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp new file mode 100644 index 0000000..faecb24 --- /dev/null +++ b/src/extension/internal/cairo-renderer.cpp @@ -0,0 +1,985 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Rendering with Cairo. + */ +/* + * Author: + * Miklos Erdelyi <erdelyim@gmail.com> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2006 Miklos Erdelyi + * + * 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 + +#ifndef PANGO_ENABLE_BACKEND +#define PANGO_ENABLE_BACKEND +#endif + +#ifndef PANGO_ENABLE_ENGINE +#define PANGO_ENABLE_ENGINE +#endif + + +#include <csignal> +#include <cerrno> + + +#include <2geom/transforms.h> +#include <2geom/pathvector.h> +#include <cairo.h> +#include <glib.h> +#include <glibmm/i18n.h> + +// include support for only the compiled-in surface types +#ifdef CAIRO_HAS_PDF_SURFACE +#include <cairo-pdf.h> +#endif +#ifdef CAIRO_HAS_PS_SURFACE +#include <cairo-ps.h> +#endif + +#include "cairo-render-context.h" +#include "cairo-renderer.h" +#include "document.h" +#include "inkscape-version.h" +#include "rdf.h" +#include "style-internal.h" +#include "display/cairo-utils.h" +#include "display/canvas-bpath.h" +#include "display/curve.h" + +#include "extension/system.h" + +#include "helper/pixbuf-ops.h" +#include "helper/png-write.h" + +#include "io/sys.h" + +#include "include/source_date_epoch.h" + +#include "libnrtype/Layout-TNG.h" + +#include "object/sp-anchor.h" +#include "object/sp-clippath.h" +#include "object/sp-defs.h" +#include "object/sp-flowtext.h" +#include "object/sp-hatch-path.h" +#include "object/sp-image.h" +#include "object/sp-item-group.h" +#include "object/sp-item.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-marker.h" +#include "object/sp-mask.h" +#include "object/sp-pattern.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-root.h" +#include "object/sp-shape.h" +#include "object/sp-symbol.h" +#include "object/sp-text.h" +#include "object/sp-use.h" + +#include "util/units.h" + +//#define TRACE(_args) g_printf _args +#define TRACE(_args) +//#define TEST(_args) _args +#define TEST(_args) + +namespace Inkscape { +namespace Extension { +namespace Internal { + +CairoRenderer::CairoRenderer(void) += default; + +CairoRenderer::~CairoRenderer() +{ + /* restore default signal handling for SIGPIPE */ +#if !defined(_WIN32) && !defined(__WIN32__) + (void) signal(SIGPIPE, SIG_DFL); +#endif + + return; +} + +CairoRenderContext* +CairoRenderer::createContext() +{ + CairoRenderContext *new_context = new CairoRenderContext(this); + g_assert( new_context != nullptr ); + + new_context->_state = nullptr; + + // create initial render state + CairoRenderState *state = new_context->_createState(); + state->transform = Geom::identity(); + new_context->_state_stack.push_back(state); + new_context->_state = state; + + return new_context; +} + +void +CairoRenderer::destroyContext(CairoRenderContext *ctx) +{ + delete ctx; +} + +/* + +Here comes the rendering part which could be put into the 'render' methods of SPItems' + +*/ + +/* The below functions are copy&pasted plus slightly modified from *_invoke_print functions. */ +static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx); +static void sp_group_render(SPGroup *group, CairoRenderContext *ctx); +static void sp_anchor_render(SPAnchor *a, CairoRenderContext *ctx); +static void sp_use_render(SPUse *use, CairoRenderContext *ctx); +static void sp_shape_render(SPShape *shape, CairoRenderContext *ctx); +static void sp_text_render(SPText *text, CairoRenderContext *ctx); +static void sp_flowtext_render(SPFlowtext *flowtext, CairoRenderContext *ctx); +static void sp_image_render(SPImage *image, CairoRenderContext *ctx); +static void sp_symbol_render(SPSymbol *symbol, CairoRenderContext *ctx); +static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx); + +static void sp_shape_render_invoke_marker_rendering(SPMarker* marker, Geom::Affine tr, SPStyle* style, CairoRenderContext *ctx) +{ + bool render = true; + if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) { + if (style->stroke_width.computed > 1e-9) { + tr = Geom::Scale(style->stroke_width.computed) * tr; + } else { + render = false; // stroke width zero and marker is thus scaled down to zero, skip + } + } + + if (render) { + SPItem* marker_item = sp_item_first_item_child(marker); + if (marker_item) { + tr = (Geom::Affine)marker_item->transform * (Geom::Affine)marker->c2p * tr; + Geom::Affine old_tr = marker_item->transform; + marker_item->transform = tr; + ctx->getRenderer()->renderItem (ctx, marker_item); + marker_item->transform = old_tr; + } + } +} + +static void sp_shape_render(SPShape *shape, CairoRenderContext *ctx) +{ + if (!shape->_curve) { + return; + } + + Geom::OptRect pbox = shape->geometricBounds(); + + SPStyle* style = shape->style; + + SPObject *defs = dynamic_cast<SPObject *>(shape->document->getDefs()); + if (defs && defs->isAncestorOf(shape)) { + SPObject *parentobj = dynamic_cast<SPObject *>(shape->parent); + SPMarker *marker = dynamic_cast<SPMarker *>(parentobj); + while (!marker && parentobj != defs) { + parentobj = dynamic_cast<SPObject *>(parentobj->parent); + marker = dynamic_cast<SPMarker *>(parentobj); + } + SPObject *origin = nullptr; + if (marker) { + origin = (*marker->hrefList.begin()); + if (origin) { + SPStyle* styleorig = origin->style; + bool iscolorfill = styleorig->fill.isColor() || (styleorig->fill.isPaintserver() && !styleorig->getFillPaintServer()->isValid()); + bool iscolorstroke = styleorig->stroke.isColor() || (styleorig->stroke.isPaintserver() && !styleorig->getStrokePaintServer()->isValid()); + bool fillctxfill = style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL; + bool fillctxstroke = style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE; + bool strokectxfill = style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL; + bool strokectxstroke = style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE; + if (fillctxfill || fillctxstroke) { + if (fillctxfill ? iscolorfill : iscolorstroke) { + style->fill.setColor(fillctxfill ? styleorig->fill.value.color : styleorig->stroke.value.color); + } else if (fillctxfill ? styleorig->fill.isPaintserver() : styleorig->stroke.isPaintserver()) { + style->fill.value.href = fillctxfill ? styleorig->fill.value.href : styleorig->stroke.value.href; + } else { + style->fill.setNone(); + } + } + if (strokectxfill || strokectxstroke) { + if (strokectxfill ? iscolorfill : iscolorstroke) { + style->stroke.setColor(strokectxfill ? styleorig->fill.value.color : styleorig->stroke.value.color); + } else if (strokectxfill ? styleorig->fill.isPaintserver() : styleorig->stroke.isPaintserver()) { + style->stroke.value.href = strokectxfill ? styleorig->fill.value.href : styleorig->stroke.value.href; + } else { + style->stroke.setNone(); + } + } + style->fill.paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL; + style->stroke.paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL; + } + } + } + + Geom::PathVector const & pathv = shape->_curve->get_pathvector(); + if (pathv.empty()) { + return; + } + + if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_NORMAL || + (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL && + style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE)) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_OVER_FILL); + } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE && + style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL ) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_OVER_STROKE); + } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE && + style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY); + } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL && + style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY); + } + + // START marker + for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START + if ( shape->_marker[i] ) { + SPMarker* marker = shape->_marker[i]; + Geom::Affine tr; + if (marker->orient_mode == MARKER_ORIENT_AUTO) { + tr = sp_shape_marker_get_transform_at_start(pathv.begin()->front()); + } else if (marker->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) { + tr = Geom::Rotate::from_degrees( 180.0 ) * sp_shape_marker_get_transform_at_start(pathv.begin()->front()); + } else { + tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(pathv.begin()->front().pointAt(0)); + } + sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); + } + } + // MID marker + for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID + if ( !shape->_marker[i] ) continue; + SPMarker* marker = shape->_marker[i]; + for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) { + // START position + if ( path_it != pathv.begin() + && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there + { + Geom::Affine tr; + if (marker->orient_mode != MARKER_ORIENT_ANGLE) { + tr = sp_shape_marker_get_transform_at_start(path_it->front()); + } else { + tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(path_it->front().pointAt(0)); + } + sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); + } + // MID position + if (path_it->size_default() > 1) { + Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve + Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve + while (curve_it2 != path_it->end_default()) + { + /* Put marker between curve_it1 and curve_it2. + * Loop to end_default (so including closing segment), because when a path is closed, + * there should be a midpoint marker between last segment and closing straight line segment */ + Geom::Affine tr; + if (marker->orient_mode != MARKER_ORIENT_ANGLE) { + tr = sp_shape_marker_get_transform(*curve_it1, *curve_it2); + } else { + tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(curve_it1->pointAt(1)); + } + + sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); + + ++curve_it1; + ++curve_it2; + } + } + // END position + if ( path_it != (pathv.end()-1) && !path_it->empty()) { + Geom::Curve const &lastcurve = path_it->back_default(); + Geom::Affine tr; + if (marker->orient_mode != MARKER_ORIENT_ANGLE) { + tr = sp_shape_marker_get_transform_at_end(lastcurve); + } else { + tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(lastcurve.pointAt(1)); + } + sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); + } + } + } + // END marker + for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END + if ( shape->_marker[i] ) { + SPMarker* marker = shape->_marker[i]; + + /* Get reference to last curve in the path. + * For moveto-only path, this returns the "closing line segment". */ + Geom::Path const &path_last = pathv.back(); + unsigned int index = path_last.size_default(); + if (index > 0) { + index--; + } + Geom::Curve const &lastcurve = path_last[index]; + + Geom::Affine tr; + if (marker->orient_mode != MARKER_ORIENT_ANGLE) { + tr = sp_shape_marker_get_transform_at_end(lastcurve); + } else { + tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(lastcurve.pointAt(1)); + } + + sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx); + } + } + + if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL && + style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_OVER_FILL); + } else if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE && + style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL ) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_OVER_STROKE); + } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE && + style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY); + } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL && + style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) { + ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY); + } + +} + +static void sp_group_render(SPGroup *group, CairoRenderContext *ctx) +{ + CairoRenderer *renderer = ctx->getRenderer(); + + std::vector<SPObject*> l(group->childList(false)); + for(auto x : l){ + SPItem *item = dynamic_cast<SPItem*>(x); + if (item) { + renderer->renderItem(ctx, item); + } + } +} + +static void sp_use_render(SPUse *use, CairoRenderContext *ctx) +{ + bool translated = false; + CairoRenderer *renderer = ctx->getRenderer(); + + if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) { + Geom::Affine tp(Geom::Translate(use->x.computed, use->y.computed)); + ctx->pushState(); + ctx->transform(tp); + translated = true; + } + + if (use->child) { + renderer->renderItem(ctx, use->child); + } + + if (translated) { + ctx->popState(); + } +} + +static void sp_text_render(SPText *text, CairoRenderContext *ctx) +{ + text->layout.showGlyphs(ctx); +} + +static void sp_flowtext_render(SPFlowtext *flowtext, CairoRenderContext *ctx) +{ + flowtext->layout.showGlyphs(ctx); +} + +static void sp_image_render(SPImage *image, CairoRenderContext *ctx) +{ + if (!image->pixbuf) { + return; + } + if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) { + return; + } + + int w = image->pixbuf->width(); + int h = image->pixbuf->height(); + + double x = image->x.computed; + double y = image->y.computed; + double width = image->width.computed; + double height = image->height.computed; + + if (image->aspect_align != SP_ASPECT_NONE) { + calculatePreserveAspectRatio (image->aspect_align, image->aspect_clip, (double)w, (double)h, + &x, &y, &width, &height); + } + + if (image->aspect_clip == SP_ASPECT_SLICE && !ctx->getCurrentState()->has_overflow) { + ctx->addClippingRect(image->x.computed, image->y.computed, image->width.computed, image->height.computed); + } + + Geom::Translate tp(x, y); + Geom::Scale s(width / (double)w, height / (double)h); + Geom::Affine t(s * tp); + + ctx->renderImage(image->pixbuf, t, image->style); +} + +static void sp_anchor_render(SPAnchor *a, CairoRenderContext *ctx) +{ + CairoRenderer *renderer = ctx->getRenderer(); + + std::vector<SPObject*> l(a->childList(false)); + if (a->href) + ctx->tagBegin(a->href); + for(auto x : l){ + SPItem *item = dynamic_cast<SPItem*>(x); + if (item) { + renderer->renderItem(ctx, item); + } + } + if (a->href) + ctx->tagEnd(); +} + +static void sp_symbol_render(SPSymbol *symbol, CairoRenderContext *ctx) +{ + if (!symbol->cloned) { + return; + } + + /* Cloned <symbol> is actually renderable */ + ctx->pushState(); + ctx->transform(symbol->c2p); + + // apply viewbox if set + if (false /*symbol->viewBox_set*/) { + Geom::Affine vb2user; + double x, y, width, height; + double view_width, view_height; + x = 0.0; + y = 0.0; + width = 1.0; + height = 1.0; + + view_width = symbol->viewBox.width(); + view_height = symbol->viewBox.height(); + + calculatePreserveAspectRatio(symbol->aspect_align, symbol->aspect_clip, view_width, view_height, + &x, &y,&width, &height); + + // [itemTransform *] translate(x, y) * scale(w/vw, h/vh) * translate(-vx, -vy); + vb2user = Geom::identity(); + vb2user[0] = width / view_width; + vb2user[3] = height / view_height; + vb2user[4] = x - symbol->viewBox.left() * vb2user[0]; + vb2user[5] = y - symbol->viewBox.top() * vb2user[3]; + + ctx->transform(vb2user); + } + + sp_group_render(symbol, ctx); + ctx->popState(); +} + +static void sp_root_render(SPRoot *root, CairoRenderContext *ctx) +{ + CairoRenderer *renderer = ctx->getRenderer(); + + if (!ctx->getCurrentState()->has_overflow && root->parent) + ctx->addClippingRect(root->x.computed, root->y.computed, root->width.computed, root->height.computed); + + ctx->pushState(); + renderer->setStateForItem(ctx, root); + ctx->transform(root->c2p); + sp_group_render(root, ctx); + ctx->popState(); +} + +/** + This function converts the item to a raster image and includes the image into the cairo renderer. + It is only used for filters and then only when rendering filters as bitmaps is requested. +*/ +static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) +{ + + // The code was adapted from sp_selection_create_bitmap_copy in selection-chemistry.cpp + + // Calculate resolution + double res; + /** @TODO reimplement the resolution stuff (WHY?) + */ + res = ctx->getBitmapResolution(); + if(res == 0) { + res = Inkscape::Util::Quantity::convert(1, "in", "px"); + } + TRACE(("sp_asbitmap_render: resolution: %f\n", res )); + + // Get the bounding box of the selection in desktop coordinates. + Geom::OptRect bbox = item->documentVisualBounds(); + + // no bbox, e.g. empty group + if (!bbox) { + return; + } + + Geom::Rect docrect(Geom::Rect(Geom::Point(0, 0), item->document->getDimensions())); + bbox &= docrect; + + // no bbox, e.g. empty group + if (!bbox) { + return; + } + + // The width and height of the bitmap in pixels + unsigned width = ceil(bbox->width() * Inkscape::Util::Quantity::convert(res, "px", "in")); + unsigned height = ceil(bbox->height() * Inkscape::Util::Quantity::convert(res, "px", "in")); + + if (width == 0 || height == 0) return; + + // Scale to exactly fit integer bitmap inside bounding box + double scale_x = bbox->width() / width; + double scale_y = bbox->height() / height; + + // Location of bounding box in document coordinates. + double shift_x = bbox->min()[Geom::X]; + double shift_y = bbox->top(); + + // For default 96 dpi, snap bitmap to pixel grid + if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) { + shift_x = round (shift_x); + shift_y = round (shift_y); + } + + // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects + + // Matrix to put bitmap in correct place on document + Geom::Affine t_on_document = (Geom::Affine)(Geom::Scale (scale_x, scale_y)) * + (Geom::Affine)(Geom::Translate (shift_x, shift_y)); + + // ctx matrix already includes item transformation. We must substract. + Geom::Affine t_item = item->i2doc_affine(); + Geom::Affine t = t_on_document * t_item.inverse(); + + // Do the export + SPDocument *document = item->document; + + std::unique_ptr<Inkscape::Pixbuf> pb( + sp_generate_internal_bitmap(document, nullptr, + bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y], + width, height, res, res, (guint32) 0xffffff00, item )); + + if (pb) { + //TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL )); + + ctx->renderImage(pb.get(), t, item->style); + } +} + + +static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx) +{ + // Check item's visibility + if (item->isHidden()) { + return; + } + + if(ctx->getFilterToBitmap() && (item->style->filter.set != 0)) { + return sp_asbitmap_render(item, ctx); + } + + SPRoot *root = dynamic_cast<SPRoot *>(item); + if (root) { + TRACE(("root\n")); + sp_root_render(root, ctx); + } else { + SPSymbol *symbol = dynamic_cast<SPSymbol *>(item); + if (symbol) { + TRACE(("symbol\n")); + sp_symbol_render(symbol, ctx); + } else { + SPAnchor *anchor = dynamic_cast<SPAnchor *>(item); + if (anchor) { + TRACE(("<a>\n")); + sp_anchor_render(anchor, ctx); + } else { + SPShape *shape = dynamic_cast<SPShape *>(item); + if (shape) { + TRACE(("shape\n")); + sp_shape_render(shape, ctx); + } else { + SPUse *use = dynamic_cast<SPUse *>(item); + if (use) { + TRACE(("use begin---\n")); + sp_use_render(use, ctx); + TRACE(("---use end\n")); + } else { + SPText *text = dynamic_cast<SPText *>(item); + if (text) { + TRACE(("text\n")); + sp_text_render(text, ctx); + } else { + SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(item); + if (flowtext) { + TRACE(("flowtext\n")); + sp_flowtext_render(flowtext, ctx); + } else { + SPImage *image = dynamic_cast<SPImage *>(item); + if (image) { + TRACE(("image\n")); + sp_image_render(image, ctx); + } else { + SPGroup *group = dynamic_cast<SPGroup *>(item); + if (group) { + TRACE(("<g>\n")); + sp_group_render(group, ctx); + } + } + } + } + } + } + } + } + } +} + +void +CairoRenderer::setStateForItem(CairoRenderContext *ctx, SPItem const *item) +{ + ctx->setStateForStyle(item->style); + + CairoRenderState *state = ctx->getCurrentState(); + state->clip_path = item->getClipObject(); + state->mask = item->getMaskObject(); + state->item_transform = Geom::Affine (item->transform); + + // If parent_has_userspace is true the parent state's transform + // has to be used for the mask's/clippath's context. + // This is so because we use the image's/(flow)text's transform for positioning + // instead of explicitly specifying it and letting the renderer do the + // transformation before rendering the item. + if (dynamic_cast<SPText const *>(item) || dynamic_cast<SPFlowtext const *>(item) || dynamic_cast<SPImage const *>(item)) { + state->parent_has_userspace = TRUE; + } + TRACE(("setStateForItem opacity: %f\n", state->opacity)); +} + +// TODO change this to accept a const SPItem: +void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item) +{ + ctx->pushState(); + setStateForItem(ctx, item); + + CairoRenderState *state = ctx->getCurrentState(); + state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 ); + SPStyle* style = item->style; + SPGroup * group = dynamic_cast<SPGroup *>(item); + bool blend = false; + if (group && style->mix_blend_mode.set && style->mix_blend_mode.value != SP_CSS_BLEND_NORMAL) { + state->need_layer = true; + blend = true; + } + // Draw item on a temporary surface so a mask, clip-path, or opacity can be applied to it. + if (state->need_layer) { + state->merge_opacity = FALSE; + ctx->pushLayer(); + } + ctx->transform(item->transform); + sp_item_invoke_render(item, ctx); + + if (state->need_layer) + if (blend) { + ctx->popLayer(ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); // This applies clipping/masking + } else { + ctx->popLayer(); // This applies clipping/masking + } + ctx->popState(); +} + +void CairoRenderer::renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key) { + ctx->pushState(); + ctx->setStateForStyle(hatchPath.style); + ctx->transform(Geom::Translate(hatchPath.offset.computed, 0)); + + SPCurve *curve = hatchPath.calculateRenderCurve(key); + Geom::PathVector const & pathv =curve->get_pathvector(); + if (!pathv.empty()) { + ctx->renderPathVector(pathv, hatchPath.style, Geom::OptRect()); + } + + curve->unref(); + ctx->popState(); +} + +void CairoRenderer::setMetadata(CairoRenderContext *ctx, SPDocument *doc) { + // title + const gchar *title = rdf_get_work_entity(doc, rdf_find_entity("title")); + if (title) { + ctx->_metadata.title = title; + } + + // author + const gchar *author = rdf_get_work_entity(doc, rdf_find_entity("creator")); + if (author) { + ctx->_metadata.author = author; + } + + // subject + const gchar *subject = rdf_get_work_entity(doc, rdf_find_entity("description")); + if (subject) { + ctx->_metadata.subject = subject; + } + + // keywords + const gchar *keywords = rdf_get_work_entity(doc, rdf_find_entity("subject")); + if (keywords) { + ctx->_metadata.keywords = keywords; + } + + // copyright + const gchar *copyright = rdf_get_work_entity(doc, rdf_find_entity("rights")); + if (copyright) { + ctx->_metadata.copyright = copyright; + } + + // creator + ctx->_metadata.creator = Glib::ustring::compose("Inkscape %1 (https://inkscape.org)", + Inkscape::version_string_without_revision); + + // cdate (only used for for reproducible builds hack) + Glib::ustring cdate = ReproducibleBuilds::now_iso_8601(); + if (!cdate.empty()) { + ctx->_metadata.cdate = cdate; + } + + // mdate (currently unused) +} + +bool +CairoRenderer::setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, float bleedmargin_px, SPItem *base) +{ +// PLEASE note when making changes to the boundingbox and transform calculation, corresponding changes should be made to LaTeXTextRenderer::setupDocument !!! + + g_assert( ctx != nullptr ); + + if (!base) { + base = doc->getRoot(); + } + + Geom::Rect d; + if (pageBoundingBox) { + d = Geom::Rect::from_xywh(Geom::Point(0,0), doc->getDimensions()); + } else { + Geom::OptRect bbox = base->documentVisualBounds(); + if (!bbox) { + g_message("CairoRenderer: empty bounding box."); + return false; + } + d = *bbox; + } + d.expandBy(bleedmargin_px); + + double px_to_ctx_units = 1.0; + if (ctx->_vector_based_target) { + // convert from px to pt + px_to_ctx_units = Inkscape::Util::Quantity::convert(1, "px", "pt"); + } + + ctx->_width = d.width() * px_to_ctx_units; + ctx->_height = d.height() * px_to_ctx_units; + + TRACE(("setupDocument: %f x %f\n", ctx->_width, ctx->_height)); + + setMetadata(ctx, doc); + + bool ret = ctx->setupSurface(ctx->_width, ctx->_height); + + if (ret) { + if (pageBoundingBox) { + // translate to set bleed/margin + Geom::Affine tp( Geom::Translate( bleedmargin_px, bleedmargin_px ) ); + ctx->transform(tp); + } else { + // this transform translates the export drawing to a virtual page (0,0)-(width,height) + Geom::Affine tp(Geom::Translate(-d.min())); + ctx->transform(tp); + } + } + + return ret; +} + +// Apply an SVG clip path +void +CairoRenderer::applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp) +{ + g_assert( ctx != nullptr && ctx->_is_valid ); + + if (cp == nullptr) + return; + + CairoRenderContext::CairoRenderMode saved_mode = ctx->getRenderMode(); + ctx->setRenderMode(CairoRenderContext::RENDER_MODE_CLIP); + + // FIXME: the access to the first clippath view to obtain the bbox is completely bogus + Geom::Affine saved_ctm; + if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && cp->display->bbox) { + Geom::Rect clip_bbox = *cp->display->bbox; + Geom::Affine t(Geom::Scale(clip_bbox.dimensions())); + t[4] = clip_bbox.left(); + t[5] = clip_bbox.top(); + t *= ctx->getCurrentState()->transform; + saved_ctm = ctx->getTransform(); + ctx->setTransform(t); + } + + TRACE(("BEGIN clip\n")); + SPObject const *co = cp; + for (auto& child: co->children) { + SPItem const *item = dynamic_cast<SPItem const *>(&child); + if (item) { + + // combine transform of the item in clippath and the item using clippath: + Geom::Affine tempmat = item->transform * ctx->getCurrentState()->item_transform; + + // render this item in clippath + ctx->pushState(); + ctx->transform(tempmat); + setStateForItem(ctx, item); + // TODO fix this call to accept const items + sp_item_invoke_render(const_cast<SPItem *>(item), ctx); + ctx->popState(); + } + } + TRACE(("END clip\n")); + + // do clipping only if this was the first call to applyClipPath + if (ctx->getClipMode() == CairoRenderContext::CLIP_MODE_PATH + && saved_mode == CairoRenderContext::RENDER_MODE_NORMAL) + cairo_clip(ctx->_cr); + + if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX) + ctx->setTransform(saved_ctm); + + ctx->setRenderMode(saved_mode); +} + +// Apply an SVG mask +void +CairoRenderer::applyMask(CairoRenderContext *ctx, SPMask const *mask) +{ + g_assert( ctx != nullptr && ctx->_is_valid ); + + if (mask == nullptr) + return; + + // FIXME: the access to the first mask view to obtain the bbox is completely bogus + // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ? + if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && mask->display->bbox) { + Geom::Rect mask_bbox = *mask->display->bbox; + Geom::Affine t(Geom::Scale(mask_bbox.dimensions())); + t[4] = mask_bbox.left(); + t[5] = mask_bbox.top(); + t *= ctx->getCurrentState()->transform; + ctx->setTransform(t); + } + + // Clip mask contents... but... + // The mask's bounding box is the "geometric bounding box" which doesn't allow for + // filters which extend outside the bounding box. So don't clip. + // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0); + + ctx->pushState(); + + TRACE(("BEGIN mask\n")); + SPObject const *co = mask; + for (auto& child: co->children) { + SPItem const *item = dynamic_cast<SPItem const *>(&child); + if (item) { + // TODO fix const correctness: + renderItem(ctx, const_cast<SPItem*>(item)); + } + } + TRACE(("END mask\n")); + + ctx->popState(); +} + +void +calculatePreserveAspectRatio(unsigned int aspect_align, unsigned int aspect_clip, double vp_width, double vp_height, + double *x, double *y, double *width, double *height) +{ + if (aspect_align == SP_ASPECT_NONE) + return; + + double scalex, scaley, scale; + double new_width, new_height; + scalex = *width / vp_width; + scaley = *height / vp_height; + scale = (aspect_clip == SP_ASPECT_MEET) ? MIN(scalex, scaley) : MAX(scalex, scaley); + new_width = vp_width * scale; + new_height = vp_height * scale; + /* Now place viewbox to requested position */ + switch (aspect_align) { + case SP_ASPECT_XMIN_YMIN: + break; + case SP_ASPECT_XMID_YMIN: + *x -= 0.5 * (new_width - *width); + break; + case SP_ASPECT_XMAX_YMIN: + *x -= 1.0 * (new_width - *width); + break; + case SP_ASPECT_XMIN_YMID: + *y -= 0.5 * (new_height - *height); + break; + case SP_ASPECT_XMID_YMID: + *x -= 0.5 * (new_width - *width); + *y -= 0.5 * (new_height - *height); + break; + case SP_ASPECT_XMAX_YMID: + *x -= 1.0 * (new_width - *width); + *y -= 0.5 * (new_height - *height); + break; + case SP_ASPECT_XMIN_YMAX: + *y -= 1.0 * (new_height - *height); + break; + case SP_ASPECT_XMID_YMAX: + *x -= 0.5 * (new_width - *width); + *y -= 1.0 * (new_height - *height); + break; + case SP_ASPECT_XMAX_YMAX: + *x -= 1.0 * (new_width - *width); + *y -= 1.0 * (new_height - *height); + break; + default: + break; + } + *width = new_width; + *height = new_height; +} + +#include "clear-n_.h" + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + +#undef TRACE + + +/* + 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/extension/internal/cairo-renderer.h b/src/extension/internal/cairo-renderer.h new file mode 100644 index 0000000..26f2f41 --- /dev/null +++ b/src/extension/internal/cairo-renderer.h @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef EXTENSION_INTERNAL_CAIRO_RENDERER_H_SEEN +#define EXTENSION_INTERNAL_CAIRO_RENDERER_H_SEEN + +/** \file + * Declaration of CairoRenderer, a class used for rendering via a CairoRenderContext. + */ +/* + * Authors: + * Miklos Erdelyi <erdelyim@gmail.com> + * Abhishek Sharma + * + * Copyright (C) 2006 Miklos Erdelyi + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/extension.h" +#include <set> +#include <string> + +//#include "libnrtype/font-instance.h" +#include <cairo.h> + +class SPItem; +class SPClipPath; +class SPMask; +class SPHatchPath; + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class CairoRenderer; +class CairoRenderContext; + +class CairoRenderer { +public: + CairoRenderer(); + virtual ~CairoRenderer(); + + CairoRenderContext *createContext(); + void destroyContext(CairoRenderContext *ctx); + + void setStateForItem(CairoRenderContext *ctx, SPItem const *item); + + void applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp); + void applyMask(CairoRenderContext *ctx, SPMask const *mask); + + /** Initializes the CairoRenderContext according to the specified + SPDocument. A set*Target function can only be called on the context + before setupDocument. */ + bool setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, float bleedmargin_px, SPItem *base); + + /** Traverses the object tree and invokes the render methods. */ + void renderItem(CairoRenderContext *ctx, SPItem *item); + void renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key); + +private: + /** Extract metadata from doc and set it on ctx. */ + void setMetadata(CairoRenderContext *ctx, SPDocument *doc); +}; + +// FIXME: this should be a static method of CairoRenderer +void calculatePreserveAspectRatio(unsigned int aspect_align, unsigned int aspect_clip, double vp_width, + double vp_height, double *x, double *y, double *width, double *height); + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* !EXTENSION_INTERNAL_CAIRO_RENDERER_H_SEEN */ + +/* + 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/extension/internal/cdr-input.cpp b/src/extension/internal/cdr-input.cpp new file mode 100644 index 0000000..ab5bcdb --- /dev/null +++ b/src/extension/internal/cdr-input.cpp @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file came from libwpg as a source, their utility wpg2svg + * specifically. It has been modified to work as an Inkscape extension. + * The Inkscape extension code is covered by this copyright, but the + * rest is covered by the one below. + * + * Authors: + * Fridrich Strba (fridrich.strba@bluewin.ch) + * + * Copyright (C) 2012 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 + +#include <cstdio> + +#include "cdr-input.h" + +#ifdef WITH_LIBCDR + +#include <string> +#include <cstring> + +#include <libcdr/libcdr.h> + +// TODO: Drop this check when librevenge is widespread. +#if WITH_LIBCDR01 + #include <librevenge-stream/librevenge-stream.h> + + using librevenge::RVNGString; + using librevenge::RVNGFileStream; + using librevenge::RVNGStringVector; +#else + #include <libwpd-stream/libwpd-stream.h> + + typedef WPXString RVNGString; + typedef WPXFileStream RVNGFileStream; + typedef libcdr::CDRStringVector RVNGStringVector; +#endif + +#include <gtkmm/grid.h> +#include <gtkmm/spinbutton.h> + +#include "extension/system.h" +#include "extension/input.h" + +#include "document.h" +#include "inkscape.h" + +#include "ui/dialog-events.h" +#include <glibmm/i18n.h> + +#include "ui/view/svg-view-widget.h" + +#include "object/sp-root.h" + +#include "util/units.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + + +class CdrImportDialog : public Gtk::Dialog { +public: + CdrImportDialog(const std::vector<RVNGString> &vec); + ~CdrImportDialog() override; + + bool showDialog(); + unsigned getSelectedPage(); + void getImportSettings(Inkscape::XML::Node *prefs); + +private: + void _setPreviewPage(); + + // Signal handlers + void _onPageNumberChanged(); + void _onSpinButtonPress(GdkEventButton* button_event); + void _onSpinButtonRelease(GdkEventButton* button_event); + + class Gtk::VBox * vbox1; + class Inkscape::UI::View::SVGViewWidget * _previewArea; + class Gtk::Button * cancelbutton; + class Gtk::Button * okbutton; + + class Gtk::HBox * _page_selector_box; + class Gtk::Label * _labelSelect; + class Gtk::Label * _labelTotalPages; + class Gtk::SpinButton * _pageNumberSpin; + + const std::vector<RVNGString> &_vec; // Document to be imported + unsigned _current_page; // Current selected page + bool _spinning; // whether SpinButton is pressed (i.e. we're "spinning") +}; + +CdrImportDialog::CdrImportDialog(const std::vector<RVNGString> &vec) + : _previewArea(nullptr) + , _vec(vec) + , _current_page(1) + , _spinning(false) +{ + int num_pages = _vec.size(); + if ( num_pages <= 1 ) + return; + + // Dialog settings + this->set_title(_("Page Selector")); + this->set_modal(true); + sp_transientize(GTK_WIDGET(this->gobj())); //Make transient + this->property_window_position().set_value(Gtk::WIN_POS_NONE); + this->set_resizable(true); + this->property_destroy_with_parent().set_value(false); + + // Preview area + vbox1 = Gtk::manage(new class Gtk::VBox()); + this->get_content_area()->pack_start(*vbox1); + + // CONTROLS + _page_selector_box = Gtk::manage(new Gtk::HBox()); + + // "Select page:" label + _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:"))); + _labelTotalPages = Gtk::manage(new class Gtk::Label()); + _labelSelect->set_line_wrap(false); + _labelSelect->set_use_markup(false); + _labelSelect->set_selectable(false); + _page_selector_box->pack_start(*_labelSelect, Gtk::PACK_SHRINK); + + // Adjustment + spinner + auto pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _vec.size(), 1, 10, 0); + _pageNumberSpin = Gtk::manage(new Gtk::SpinButton(pageNumberSpin_adj, 1, 0)); + _pageNumberSpin->set_can_focus(); + _pageNumberSpin->set_update_policy(Gtk::UPDATE_ALWAYS); + _pageNumberSpin->set_numeric(true); + _pageNumberSpin->set_wrap(false); + _page_selector_box->pack_start(*_pageNumberSpin, Gtk::PACK_SHRINK); + + _labelTotalPages->set_line_wrap(false); + _labelTotalPages->set_use_markup(false); + _labelTotalPages->set_selectable(false); + gchar *label_text = g_strdup_printf(_("out of %i"), num_pages); + _labelTotalPages->set_label(label_text); + g_free(label_text); + _page_selector_box->pack_start(*_labelTotalPages, Gtk::PACK_SHRINK); + + vbox1->pack_end(*_page_selector_box, Gtk::PACK_SHRINK); + + // Buttons + cancelbutton = Gtk::manage(new Gtk::Button(_("_Cancel"), true)); + okbutton = Gtk::manage(new Gtk::Button(_("_OK"), true)); + this->add_action_widget(*cancelbutton, Gtk::RESPONSE_CANCEL); + this->add_action_widget(*okbutton, Gtk::RESPONSE_OK); + + // Show all widgets in dialog + this->show_all(); + + // Connect signals + _pageNumberSpin->signal_value_changed().connect(sigc::mem_fun(*this, &CdrImportDialog::_onPageNumberChanged)); + _pageNumberSpin->signal_button_press_event().connect_notify(sigc::mem_fun(*this, &CdrImportDialog::_onSpinButtonPress)); + _pageNumberSpin->signal_button_release_event().connect_notify(sigc::mem_fun(*this, &CdrImportDialog::_onSpinButtonRelease)); + + _setPreviewPage(); +} + +CdrImportDialog::~CdrImportDialog() = default; + +bool CdrImportDialog::showDialog() +{ + show(); + gint b = run(); + hide(); + if (b == Gtk::RESPONSE_OK || b == Gtk::RESPONSE_ACCEPT) { + return TRUE; + } else { + return FALSE; + } +} + +unsigned CdrImportDialog::getSelectedPage() +{ + return _current_page; +} + +void CdrImportDialog::_onPageNumberChanged() +{ + unsigned page = static_cast<unsigned>(_pageNumberSpin->get_value_as_int()); + _current_page = CLAMP(page, 1U, _vec.size()); + _setPreviewPage(); +} + +void CdrImportDialog::_onSpinButtonPress(GdkEventButton* /*button_event*/) +{ + _spinning = true; +} + +void CdrImportDialog::_onSpinButtonRelease(GdkEventButton* /*button_event*/) +{ + _spinning = false; + _setPreviewPage(); +} + +/** + * \brief Renders the given page's thumbnail + */ +void CdrImportDialog::_setPreviewPage() +{ + if (_spinning) { + return; + } + + SPDocument *doc = SPDocument::createNewDocFromMem(_vec[_current_page-1].cstr(), strlen(_vec[_current_page-1].cstr()), false); + if(!doc) { + g_warning("CDR import: Could not create preview for page %d", _current_page); + gchar const *no_preview_template = R"A( + <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'> + <path d='M 82,10 18,74 m 0,-64 64,64' style='fill:none;stroke:#ff0000;stroke-width:2px;'/> + <rect x='18' y='10' width='64' height='64' style='fill:none;stroke:#000000;stroke-width:1.5px;'/> + <text x='50' y='92' style='font-size:10px;text-anchor:middle;font-family:sans-serif;'>%s</text> + </svg> + )A"; + gchar * no_preview = g_strdup_printf(no_preview_template, _("No preview")); + doc = SPDocument::createNewDocFromMem(no_preview, strlen(no_preview), false); + g_free(no_preview); + } + + if (!doc) { + std::cerr << "CdrImportDialog::_setPreviewPage: No document!" << std::endl; + return; + } + + if (_previewArea) { + _previewArea->setDocument(doc); + } else { + _previewArea = Gtk::manage(new Inkscape::UI::View::SVGViewWidget(doc)); + vbox1->pack_start(*_previewArea, Gtk::PACK_EXPAND_WIDGET, 0); + } + + _previewArea->setResize(400, 400); + _previewArea->show_all(); +} + +SPDocument *CdrInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri) +{ + #ifdef _WIN32 + // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows + // therefore attempt to convert uri to the system codepage + // even if this is not possible the alternate short (8.3) file name will be used if available + gchar * converted_uri = g_win32_locale_filename_from_utf8(uri); + RVNGFileStream input(converted_uri); + g_free(converted_uri); + #else + RVNGFileStream input(uri); + #endif + + if (!libcdr::CDRDocument::isSupported(&input)) { + return nullptr; + } + + RVNGStringVector output; +#if WITH_LIBCDR01 + librevenge::RVNGSVGDrawingGenerator generator(output, "svg"); + + if (!libcdr::CDRDocument::parse(&input, &generator)) { +#else + if (!libcdr::CDRDocument::generateSVG(&input, output)) { +#endif + return nullptr; + } + + if (output.empty()) { + return nullptr; + } + + std::vector<RVNGString> tmpSVGOutput; + for (unsigned i=0; i<output.size(); ++i) { + RVNGString tmpString("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); + tmpString.append(output[i]); + tmpSVGOutput.push_back(tmpString); + } + + unsigned page_num = 1; + + // If only one page is present, import that one without bothering user + if (tmpSVGOutput.size() > 1) { + CdrImportDialog *dlg = nullptr; + if (INKSCAPE.use_gui()) { + dlg = new CdrImportDialog(tmpSVGOutput); + if (!dlg->showDialog()) { + delete dlg; + throw Input::open_cancelled(); + } + } + + // Get needed page + if (dlg) { + page_num = dlg->getSelectedPage(); + if (page_num < 1) + page_num = 1; + if (page_num > tmpSVGOutput.size()) + page_num = tmpSVGOutput.size(); + } + } + + SPDocument * doc = SPDocument::createNewDocFromMem(tmpSVGOutput[page_num-1].cstr(), strlen(tmpSVGOutput[page_num-1].cstr()), TRUE); + + // Set viewBox if it doesn't exist + if (doc && !doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit()))); + } + return doc; +} + +#include "clear-n_.h" + +void CdrInput::init() +{ + /* CDR */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Corel DRAW Input") "</name>\n" + "<id>org.inkscape.input.cdr</id>\n" + "<input>\n" + "<extension>.cdr</extension>\n" + "<mimetype>image/x-xcdr</mimetype>\n" + "<filetypename>" N_("Corel DRAW 7-X4 files (*.cdr)") "</filetypename>\n" + "<filetypetooltip>" N_("Open files saved in Corel DRAW 7-X4") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new CdrInput()); + + /* CDT */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Corel DRAW templates input") "</name>\n" + "<id>org.inkscape.input.cdt</id>\n" + "<input>\n" + "<extension>.cdt</extension>\n" + "<mimetype>application/x-xcdt</mimetype>\n" + "<filetypename>" N_("Corel DRAW 7-13 template files (*.cdt)") "</filetypename>\n" + "<filetypetooltip>" N_("Open files saved in Corel DRAW 7-13") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new CdrInput()); + + /* CCX */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Corel DRAW Compressed Exchange files input") "</name>\n" + "<id>org.inkscape.input.ccx</id>\n" + "<input>\n" + "<extension>.ccx</extension>\n" + "<mimetype>application/x-xccx</mimetype>\n" + "<filetypename>" N_("Corel DRAW Compressed Exchange files (*.ccx)") "</filetypename>\n" + "<filetypetooltip>" N_("Open compressed exchange files saved in Corel DRAW") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new CdrInput()); + + /* CMX */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Corel DRAW Presentation Exchange files input") "</name>\n" + "<id>org.inkscape.input.cmx</id>\n" + "<input>\n" + "<extension>.cmx</extension>\n" + "<mimetype>application/x-xcmx</mimetype>\n" + "<filetypename>" N_("Corel DRAW Presentation Exchange files (*.cmx)") "</filetypename>\n" + "<filetypetooltip>" N_("Open presentation exchange files saved in Corel DRAW") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new CdrInput()); + + return; + +} // init + +} } } /* namespace Inkscape, Extension, Implementation */ +#endif /* WITH_LIBCDR */ + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/cdr-input.h b/src/extension/internal/cdr-input.h new file mode 100644 index 0000000..546151f --- /dev/null +++ b/src/extension/internal/cdr-input.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This code abstracts the libwpg interfaces into the Inkscape + * input extension interface. + * + * Authors: + * Fridrich Strba (fridrich.strba@bluewin.ch) + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __EXTENSION_INTERNAL_CDROUTPUT_H__ +#define __EXTENSION_INTERNAL_CDROUTPUT_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#ifdef WITH_LIBCDR + +#include <gtkmm/dialog.h> + +#include "../implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class CdrInput : public Inkscape::Extension::Implementation::Implementation { + CdrInput () = default;; +public: + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ) override; + static void init( ); + +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + +#endif /* WITH_LIBCDR */ +#endif /* __EXTENSION_INTERNAL_CDROUTPUT_H__ */ + + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/clear-n_.h b/src/extension/internal/clear-n_.h new file mode 100644 index 0000000..ecd8eaa --- /dev/null +++ b/src/extension/internal/clear-n_.h @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + \file clear-n_.h + + A way to clear the N_ macro, which is defined as an inline function. + Unfortunately, this makes it so it is hard to use in static strings + where you only want to translate a small part. Including this + turns it back into a a macro. +*/ +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifdef N_ +#undef N_ +#endif +#define N_(x) x + +/* + 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/extension/internal/emf-inout.cpp b/src/extension/internal/emf-inout.cpp new file mode 100644 index 0000000..42291a8 --- /dev/null +++ b/src/extension/internal/emf-inout.cpp @@ -0,0 +1,3686 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Windows-only Enhanced Metafile input and output. + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * David Mathog + * Abhishek Sharma + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + * References: + * - How to Create & Play Enhanced Metafiles in Win32 + * http://support.microsoft.com/kb/q145999/ + * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles + * http://support.microsoft.com/kb/q66949/ + * - Metafile Functions + * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp + * - Metafile Structures + * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp + */ + +#include <cstdio> +#include <cstdlib> +#include <cstdint> +#include <3rdparty/libuemf/symbol_convert.h> + +#include "clear-n_.h" +#include "display/drawing-item.h" +#include "display/drawing.h" +#include "document.h" +#include "extension/db.h" +#include "extension/input.h" +#include "extension/output.h" +#include "extension/print.h" +#include "extension/system.h" +#include "inkscape.h" // even though it is included indirectly by emf-inout.h +#include "object/sp-path.h" +#include "object/sp-root.h" +#include "print.h" +#include "svg/css-ostringstream.h" +#include "svg/svg.h" +#include "util/units.h" // even though it is included indirectly by emf-inout.h + +#include "emf-print.h" +#include "emf-inout.h" + +#define PRINT_EMF "org.inkscape.print.emf" + +#ifndef U_PS_JOIN_MASK +#define U_PS_JOIN_MASK (U_PS_JOIN_BEVEL|U_PS_JOIN_MITER|U_PS_JOIN_ROUND) +#endif + +namespace Inkscape { +namespace Extension { +namespace Internal { + +static uint32_t ICMmode = 0; // not used yet, but code to read it from EMF implemented +static uint32_t BLTmode = 0; +float faraway = 10000000; // used in "exclude" clips, hopefully well outside any real drawing! + +Emf::Emf () // The null constructor +{ + return; +} + + +Emf::~Emf () //The destructor +{ + return; +} + + +bool +Emf::check (Inkscape::Extension::Extension * /*module*/) +{ + if (nullptr == Inkscape::Extension::db.get(PRINT_EMF)) + return FALSE; + return TRUE; +} + + +void +Emf::print_document_to_file(SPDocument *doc, const gchar *filename) +{ + Inkscape::Extension::Print *mod; + SPPrintContext context; + const gchar *oldconst; + gchar *oldoutput; + unsigned int ret; + + doc->ensureUpToDate(); + + mod = Inkscape::Extension::get_print(PRINT_EMF); + oldconst = mod->get_param_string("destination"); + oldoutput = g_strdup(oldconst); + mod->set_param_string("destination", filename); + +/* Start */ + context.module = mod; + /* fixme: This has to go into module constructor somehow */ + /* Create new arena */ + mod->base = doc->getRoot(); + Inkscape::Drawing drawing; + mod->dkey = SPItem::display_key_new(1); + mod->root = mod->base->invoke_show(drawing, mod->dkey, SP_ITEM_SHOW_DISPLAY); + drawing.setRoot(mod->root); + /* Print document */ + ret = mod->begin(doc); + if (ret) { + g_free(oldoutput); + throw Inkscape::Extension::Output::save_failed(); + } + mod->base->invoke_print(&context); + (void) mod->finish(); + /* Release arena */ + mod->base->invoke_hide(mod->dkey); + mod->base = nullptr; + mod->root = nullptr; // deleted by invoke_hide +/* end */ + + mod->set_param_string("destination", oldoutput); + g_free(oldoutput); + + return; +} + + +void +Emf::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) +{ + Inkscape::Extension::Extension * ext; + + ext = Inkscape::Extension::db.get(PRINT_EMF); + if (ext == nullptr) + return; + + bool new_val = mod->get_param_bool("textToPath"); + bool new_FixPPTCharPos = mod->get_param_bool("FixPPTCharPos"); // character position bug + // reserve FixPPT2 for opacity bug. Currently EMF does not export opacity values + bool new_FixPPTDashLine = mod->get_param_bool("FixPPTDashLine"); // dashed line bug + bool new_FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys"); // gradient bug + bool new_FixPPTLinGrad = mod->get_param_bool("FixPPTLinGrad"); // allow native rectangular linear gradient + bool new_FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch"); // force all patterns as standard EMF hatch + bool new_FixImageRot = mod->get_param_bool("FixImageRot"); // remove rotations on images + + TableGen( //possibly regenerate the unicode-convert tables + mod->get_param_bool("TnrToSymbol"), + mod->get_param_bool("TnrToWingdings"), + mod->get_param_bool("TnrToZapfDingbats"), + mod->get_param_bool("UsePUA") + ); + + ext->set_param_bool("FixPPTCharPos",new_FixPPTCharPos); // Remember to add any new ones to PrintEmf::init or a mysterious failure will result! + ext->set_param_bool("FixPPTDashLine",new_FixPPTDashLine); + ext->set_param_bool("FixPPTGrad2Polys",new_FixPPTGrad2Polys); + ext->set_param_bool("FixPPTLinGrad",new_FixPPTLinGrad); + ext->set_param_bool("FixPPTPatternAsHatch",new_FixPPTPatternAsHatch); + ext->set_param_bool("FixImageRot",new_FixImageRot); + ext->set_param_bool("textToPath", new_val); + + // ensure usage of dot as decimal separator in scanf/printf functions (independently of current locale) + char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr)); + setlocale(LC_NUMERIC, "C"); + + print_document_to_file(doc, filename); + + // restore decimal separator used in scanf/printf functions to initial value + setlocale(LC_NUMERIC, oldlocale); + g_free(oldlocale); + + return; +} + + +/* given the transformation matrix from worldTransform return the scale in the matrix part. Assumes that the + matrix is not used to skew, invert, or make another distorting transformation. */ +double Emf::current_scale(PEMF_CALLBACK_DATA d){ + double scale = + d->dc[d->level].worldTransform.eM11 * d->dc[d->level].worldTransform.eM22 - + d->dc[d->level].worldTransform.eM12 * d->dc[d->level].worldTransform.eM21; + if(scale <= 0.0)scale=1.0; /* something is dreadfully wrong with the matrix, but do not crash over it */ + scale=sqrt(scale); + return(scale); +} + +/* given the transformation matrix from worldTransform and the current x,y position in inkscape coordinates, + generate an SVG transform that gives the same amount of rotation, no scaling, and maps x,y back onto x,y. This is used for + rotating objects when the location of at least one point in that object is known. Returns: + "matrix(a,b,c,d,e,f)" (WITH the double quotes) +*/ +std::string Emf::current_matrix(PEMF_CALLBACK_DATA d, double x, double y, int useoffset){ + SVGOStringStream cxform; + double scale = current_scale(d); + cxform << "\"matrix("; + cxform << d->dc[d->level].worldTransform.eM11/scale; cxform << ","; + cxform << d->dc[d->level].worldTransform.eM12/scale; cxform << ","; + cxform << d->dc[d->level].worldTransform.eM21/scale; cxform << ","; + cxform << d->dc[d->level].worldTransform.eM22/scale; cxform << ","; + if(useoffset){ + /* for the "new" coordinates drop the worldtransform translations, not used here */ + double newx = x * d->dc[d->level].worldTransform.eM11/scale + y * d->dc[d->level].worldTransform.eM21/scale; + double newy = x * d->dc[d->level].worldTransform.eM12/scale + y * d->dc[d->level].worldTransform.eM22/scale; + cxform << x - newx; cxform << ","; + cxform << y - newy; + } + else { + cxform << "0,0"; + } + cxform << ")\""; + return(cxform.str()); +} + +/* given the transformation matrix from worldTransform return the rotation angle in radians. + counter clockwise from the x axis. */ +double Emf::current_rotation(PEMF_CALLBACK_DATA d){ + return -std::atan2(d->dc[d->level].worldTransform.eM12, d->dc[d->level].worldTransform.eM11); +} + +/* Add another 100 blank slots to the hatches array. +*/ +void Emf::enlarge_hatches(PEMF_CALLBACK_DATA d){ + d->hatches.size += 100; + d->hatches.strings = (char **) realloc(d->hatches.strings,d->hatches.size * sizeof(char *)); +} + +/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1) +*/ +int Emf::in_hatches(PEMF_CALLBACK_DATA d, char *test){ + int i; + for(i=0; i<d->hatches.count; i++){ + if(strcmp(test,d->hatches.strings[i])==0)return(i+1); + } + return(0); +} + +/* (Conditionally) add a hatch. If a matching hatch already exists nothing happens. If one + does not exist it is added to the hatches list and also entered into <defs>. + This is also used to add the path part of the hatches, which they reference with a xlink:href +*/ +uint32_t Emf::add_hatch(PEMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor){ + char hatchname[64]; // big enough + char hpathname[64]; // big enough + char hbkname[64]; // big enough + char tmpcolor[8]; + char bkcolor[8]; + uint32_t idx; + + switch(hatchType){ + case U_HS_SOLIDTEXTCLR: + case U_HS_DITHEREDTEXTCLR: + sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].textColor)); + break; + case U_HS_SOLIDBKCLR: + case U_HS_DITHEREDBKCLR: + sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor)); + break; + default: + sprintf(tmpcolor,"%6.6X",sethexcolor(hatchColor)); + break; + } + + /* For both bkMode types set the PATH + FOREGROUND COLOR for the indicated standard hatch. + This will be used late to compose, or recompose the transparent or opaque final hatch.*/ + + std::string refpath; // used to reference later the path pieces which are about to be created + sprintf(hpathname,"EMFhpath%d_%s",hatchType,tmpcolor); + idx = in_hatches(d,hpathname); + auto & defs = d->defs; + if(!idx){ // add path/color if not already present + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hpathname); + + defs += "\n"; + switch(hatchType){ + case U_HS_HORIZONTAL: + defs += " <path id=\""; + defs += hpathname; + defs += "\" d=\"M 0 0 6 0\" style=\"fill:none;stroke:#"; + defs += tmpcolor; + defs += "\" />\n"; + break; + case U_HS_VERTICAL: + defs += " <path id=\""; + defs += hpathname; + defs += "\" d=\"M 0 0 0 6\" style=\"fill:none;stroke:#"; + defs += tmpcolor; + defs += "\" />\n"; + break; + case U_HS_FDIAGONAL: + defs += " <line id=\"sub"; + defs += hpathname; + defs += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"; + defs += tmpcolor; + defs += "\"/>\n"; + break; + case U_HS_BDIAGONAL: + defs += " <line id=\"sub"; + defs += hpathname; + defs += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"; + defs += tmpcolor; + defs += "\"/>\n"; + break; + case U_HS_CROSS: + defs += " <path id=\""; + defs += hpathname; + defs += "\" d=\"M 0 0 6 0 M 0 0 0 6\" style=\"fill:none;stroke:#"; + defs += tmpcolor; + defs += "\" />\n"; + break; + case U_HS_DIAGCROSS: + defs += " <line id=\"subfd"; + defs += hpathname; + defs += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"; + defs += tmpcolor; + defs += "\"/>\n"; + defs += " <line id=\"subbd"; + defs += hpathname; + defs += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"; + defs += tmpcolor; + defs += "\"/>\n"; + break; + case U_HS_SOLIDCLR: + case U_HS_DITHEREDCLR: + case U_HS_SOLIDTEXTCLR: + case U_HS_DITHEREDTEXTCLR: + case U_HS_SOLIDBKCLR: + case U_HS_DITHEREDBKCLR: + default: + defs += " <path id=\""; + defs += hpathname; + defs += "\" d=\"M 0 0 6 0 6 6 0 6 z\" style=\"fill:#"; + defs += tmpcolor; + defs += ";stroke:none"; + defs += "\" />\n"; + break; + } + } + + // References to paths possibly just created above. These will be used in the actual patterns. + switch(hatchType){ + case U_HS_HORIZONTAL: + case U_HS_VERTICAL: + case U_HS_CROSS: + case U_HS_SOLIDCLR: + case U_HS_DITHEREDCLR: + case U_HS_SOLIDTEXTCLR: + case U_HS_DITHEREDTEXTCLR: + case U_HS_SOLIDBKCLR: + case U_HS_DITHEREDBKCLR: + default: + refpath += " <use xlink:href=\"#"; + refpath += hpathname; + refpath += "\" />\n"; + break; + case U_HS_FDIAGONAL: + case U_HS_BDIAGONAL: + refpath += " <use xlink:href=\"#sub"; + refpath += hpathname; + refpath += "\" />\n"; + refpath += " <use xlink:href=\"#sub"; + refpath += hpathname; + refpath += "\" transform=\"translate(6,0)\" />\n"; + refpath += " <use xlink:href=\"#sub"; + refpath += hpathname; + refpath += "\" transform=\"translate(-6,0)\" />\n"; + break; + case U_HS_DIAGCROSS: + refpath += " <use xlink:href=\"#subfd"; + refpath += hpathname; + refpath += "\" />\n"; + refpath += " <use xlink:href=\"#subfd"; + refpath += hpathname; + refpath += "\" transform=\"translate(6,0)\"/>\n"; + refpath += " <use xlink:href=\"#subfd"; + refpath += hpathname; + refpath += "\" transform=\"translate(-6,0)\"/>\n"; + refpath += " <use xlink:href=\"#subbd"; + refpath += hpathname; + refpath += "\" />\n"; + refpath += " <use xlink:href=\"#subbd"; + refpath += hpathname; + refpath += "\" transform=\"translate(6,0)\"/>\n"; + refpath += " <use xlink:href=\"#subbd"; + refpath += hpathname; + refpath += "\" transform=\"translate(-6,0)\"/>\n"; + break; + } + + if(d->dc[d->level].bkMode == U_TRANSPARENT || hatchType >= U_HS_SOLIDCLR){ + sprintf(hatchname,"EMFhatch%d_%s",hatchType,tmpcolor); + sprintf(hpathname,"EMFhpath%d_%s",hatchType,tmpcolor); + idx = in_hatches(d,hatchname); + if(!idx){ // add it if not already present + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hatchname); + defs += "\n"; + defs += " <pattern id=\""; + defs += hatchname; + defs += "\" xlink:href=\"#EMFhbasepattern\">\n"; + defs += refpath; + defs += " </pattern>\n"; + idx = d->hatches.count; + } + } + else { // bkMode==U_OPAQUE + /* Set up an object in the defs for this background, if there is not one already there */ + sprintf(bkcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor)); + sprintf(hbkname,"EMFhbkclr_%s",bkcolor); + idx = in_hatches(d,hbkname); + if(!idx){ // add path/color if not already present. Hatchtype is not needed in the name. + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hbkname); + + defs += "\n"; + defs += " <rect id=\""; + defs += hbkname; + defs += "\" x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"#"; + defs += bkcolor; + defs += "\" />\n"; + } + + // this is the pattern, its name will show up in Inkscape's pattern selector + sprintf(hatchname,"EMFhatch%d_%s_%s",hatchType,tmpcolor,bkcolor); + idx = in_hatches(d,hatchname); + if(!idx){ // add it if not already present + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hatchname); + defs += "\n"; + defs += " <pattern id=\""; + defs += hatchname; + defs += "\" xlink:href=\"#EMFhbasepattern\">\n"; + defs += " <use xlink:href=\"#"; + defs += hbkname; + defs += "\" />\n"; + defs += refpath; + defs += " </pattern>\n"; + idx = d->hatches.count; + } + } + return(idx-1); +} + +/* Add another 100 blank slots to the images array. +*/ +void Emf::enlarge_images(PEMF_CALLBACK_DATA d){ + d->images.size += 100; + d->images.strings = (char **) realloc(d->images.strings,d->images.size * sizeof(char *)); +} + +/* See if the image string is already in the list. If it is return its position (1->n, not 1-n-1) +*/ +int Emf::in_images(PEMF_CALLBACK_DATA d, const char *test){ + int i; + for(i=0; i<d->images.count; i++){ + if(strcmp(test,d->images.strings[i])==0)return(i+1); + } + return(0); +} + +/* (Conditionally) add an image. If a matching image already exists nothing happens. If one + does not exist it is added to the images list and also entered into <defs>. + + U_EMRCREATEMONOBRUSH records only work when the bitmap is monochrome. If we hit one that isn't + set idx to 2^32-1 and let the caller handle it. +*/ +uint32_t Emf::add_image(PEMF_CALLBACK_DATA d, void *pEmr, uint32_t cbBits, uint32_t cbBmi, + uint32_t iUsage, uint32_t offBits, uint32_t offBmi){ + + uint32_t idx; + char imagename[64]; // big enough + char imrotname[64]; // big enough + char xywh[64]; // big enough + int dibparams = U_BI_UNKNOWN; // type of image not yet determined + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = nullptr; + + char *rgba_px = nullptr; // RGBA pixels + const char *px = nullptr; // DIB pixels + const U_RGBQUAD *ct = nullptr; // DIB color table + U_RGBQUAD ct2[2]; + uint32_t width, height, colortype, numCt, invert; // if needed these values will be set in get_DIB_params + if(cbBits && cbBmi && (iUsage == U_DIB_RGB_COLORS)){ + // next call returns pointers and values, but allocates no memory + dibparams = get_DIB_params((const char *)pEmr, offBits, offBmi, &px, (const U_RGBQUAD **) &ct, + &numCt, &width, &height, &colortype, &invert); + if(dibparams ==U_BI_RGB){ + // U_EMRCREATEMONOBRUSH uses text/bk colors instead of what is in the color map. + if(((PU_EMR)pEmr)->iType == U_EMR_CREATEMONOBRUSH){ + if(numCt==2){ + ct2[0] = U_RGB2BGR(d->dc[d->level].textColor); + ct2[1] = U_RGB2BGR(d->dc[d->level].bkColor); + ct = &ct2[0]; + } + else { // This record is invalid, nothing more to do here, let caller handle it + return(U_EMR_INVALID); + } + } + + if(!DIB_to_RGBA( + px, // DIB pixel array + ct, // DIB color table + numCt, // DIB color table number of entries + &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free. + width, // Width of pixel array in record + height, // Height of pixel array in record + colortype, // DIB BitCount Enumeration + numCt, // Color table used if not 0 + invert // If DIB rows are in opposite order from RGBA rows + )){ + toPNG( // Get the image from the RGBA px into mempng + &mempng, + width, height, // of the SRC bitmap + rgba_px + ); + free(rgba_px); + } + } + } + + gchar *base64String=nullptr; + if(dibparams == U_BI_JPEG || dibparams==U_BI_PNG){ // image was binary png or jpg in source file + base64String = g_base64_encode((guchar*) px, numCt ); + } + else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine + base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size ); + free(mempng.buffer); + } + else { // unknown or unsupported image type or failed conversion, insert the common bad image picture + width = 3; + height = 4; + base64String = bad_image_png(); + } + + idx = in_images(d, (char *) base64String); + auto & defs = d->defs; + if(!idx){ // add it if not already present - we looked at the actual data for comparison + if(d->images.count == d->images.size){ enlarge_images(d); } + idx = d->images.count; + d->images.strings[d->images.count++]=strdup(base64String); + + sprintf(imagename,"EMFimage%d",idx++); + sprintf(xywh," x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" ",width,height); // reuse this buffer + + defs += "\n"; + defs += " <image id=\""; + defs += imagename; + defs += "\"\n "; + defs += xywh; + defs += "\n"; + if(dibparams == U_BI_JPEG){ defs += " xlink:href=\"data:image/jpeg;base64,"; } + else { defs += " xlink:href=\"data:image/png;base64,"; } + defs += base64String; + defs += "\"\n"; + defs += " preserveAspectRatio=\"none\"\n"; + defs += " />\n"; + + + defs += "\n"; + defs += " <pattern id=\""; + defs += imagename; + defs += "_ref\"\n "; + defs += xywh; + defs += "\n patternUnits=\"userSpaceOnUse\""; + defs += " >\n"; + defs += " <use id=\""; + defs += imagename; + defs += "_ign\" "; + defs += " xlink:href=\"#"; + defs += imagename; + defs += "\" />\n"; + defs += " "; + defs += " </pattern>\n"; + } + g_free(base64String);//wait until this point to free because it might be a duplicate image + + /* image allows the inner image to be rotated nicely, load this one second only if needed + imagename retained from above + Here comes a dreadful hack. How do we determine if this rotation of the base image has already + been loaded? The image names contain no identifying information, they are just numbered sequentially. + So the rotated name is EMFrotimage###_XXXXXX, where ### is the number of the referred to image, and + XXXX is the rotation in radians x 1000000 and truncated. That is then stored in BASE64 as the "image". + The corresponding SVG generated though is not for an image, but a reference to an image. + The name of the pattern MUST still be EMFimage###_ref or output_style() will not be able to use it. + */ + if(current_rotation(d) >= 0.00001 || current_rotation(d) <= -0.00001){ /* some rotation, allow a little rounding error around 0 degrees */ + int tangle = round(current_rotation(d)*1000000.0); + sprintf(imrotname,"EMFrotimage%d_%d",idx-1,tangle); + base64String = g_base64_encode((guchar*) imrotname, strlen(imrotname) ); + idx = in_images(d, (char *) base64String); // scan for this "image" + if(!idx){ + if(d->images.count == d->images.size){ enlarge_images(d); } + idx = d->images.count; + d->images.strings[d->images.count++]=strdup(base64String); + sprintf(imrotname,"EMFimage%d",idx++); + + defs += "\n"; + defs += " <pattern\n"; + defs += " id=\""; + defs += imrotname; + defs += "_ref\"\n"; + defs += " xlink:href=\"#"; + defs += imagename; + defs += "_ref\"\n"; + defs += " patternTransform="; + defs += current_matrix(d, 0.0, 0.0, 0); //j use offset 0,0 + defs += " />\n"; + } + g_free(base64String); + } + + return(idx-1); +} + +/* Add another 100 blank slots to the gradients array. +*/ +void Emf::enlarge_gradients(PEMF_CALLBACK_DATA d){ + d->gradients.size += 100; + d->gradients.strings = (char **) realloc(d->gradients.strings,d->gradients.size * sizeof(char *)); +} + +/* See if the gradient name is already in the list. If it is return its position (1->n, not 1-n-1) +*/ +int Emf::in_gradients(PEMF_CALLBACK_DATA d, const char *test){ + int i; + for(i=0; i<d->gradients.count; i++){ + if(strcmp(test,d->gradients.strings[i])==0)return(i+1); + } + return(0); +} + +U_COLORREF trivertex_to_colorref(U_TRIVERTEX tv){ + U_COLORREF uc; + uc.Red = tv.Red >> 8; + uc.Green = tv.Green >> 8; + uc.Blue = tv.Blue >> 8; + uc.Reserved = tv.Alpha >> 8; // Not used + return(uc); +} + +/* (Conditionally) add a gradient. If a matching gradient already exists nothing happens. If one + does not exist it is added to the gradients list and also entered into <defs>. + Only call this with H or V gradient, not a triangle. +*/ +uint32_t Emf::add_gradient(PEMF_CALLBACK_DATA d, uint32_t gradientType, U_TRIVERTEX tv1, U_TRIVERTEX tv2){ + char hgradname[64]; // big enough + char tmpcolor1[8]; + char tmpcolor2[8]; + char gradc; + uint32_t idx; + std::string x2,y2; + + U_COLORREF gradientColor1 = trivertex_to_colorref(tv1); + U_COLORREF gradientColor2 = trivertex_to_colorref(tv2); + + + sprintf(tmpcolor1,"%6.6X",sethexcolor(gradientColor1)); + sprintf(tmpcolor2,"%6.6X",sethexcolor(gradientColor2)); + switch(gradientType){ + case U_GRADIENT_FILL_RECT_H: + gradc='H'; + x2="100"; + y2="0"; + break; + case U_GRADIENT_FILL_RECT_V: + gradc='V'; + x2="0"; + y2="100"; + break; + default: // this should never happen, but fill these in to avoid compiler warnings + gradc='!'; + x2="0"; + y2="0"; + break; + } + + /* Even though the gradient was defined as Horizontal or Vertical if the rectangle is rotated it needs to + be at some other alignment, and that needs gradienttransform. Set the name using the same sort of hack + as for add_image. + */ + int tangle = round(current_rotation(d)*1000000.0); + sprintf(hgradname,"LinGrd%c_%s_%s_%d",gradc,tmpcolor1,tmpcolor2,tangle); + + idx = in_gradients(d,hgradname); + if(!idx){ // gradient does not yet exist + if(d->gradients.count == d->gradients.size){ enlarge_gradients(d); } + d->gradients.strings[d->gradients.count++]=strdup(hgradname); + idx = d->gradients.count; + SVGOStringStream stmp; + stmp << " <linearGradient id=\""; + stmp << hgradname; + stmp << "\" x1=\""; + stmp << pix_to_x_point(d, tv1.x , tv1.y); + stmp << "\" y1=\""; + stmp << pix_to_y_point(d, tv1.x , tv1.y); + stmp << "\" x2=\""; + if(gradc=='H'){ // UR corner + stmp << pix_to_x_point(d, tv2.x , tv1.y); + stmp << "\" y2=\""; + stmp << pix_to_y_point(d, tv2.x , tv1.y); + } + else { // LL corner + stmp << pix_to_x_point(d, tv1.x , tv2.y); + stmp << "\" y2=\""; + stmp << pix_to_y_point(d, tv1.x , tv2.y); + } + stmp << "\" gradientTransform=\"(1,0,0,1,0,0)\""; + stmp << " gradientUnits=\"userSpaceOnUse\"\n"; + stmp << ">\n"; + stmp << " <stop offset=\"0\" style=\"stop-color:#"; + stmp << tmpcolor1; + stmp << ";stop-opacity:1\" />\n"; + stmp << " <stop offset=\"1\" style=\"stop-color:#"; + stmp << tmpcolor2; + stmp << ";stop-opacity:1\" />\n"; + stmp << " </linearGradient>\n"; + d->defs += stmp.str().c_str(); + } + + return(idx-1); +} + +/* Add another 100 blank slots to the clips array. +*/ +void Emf::enlarge_clips(PEMF_CALLBACK_DATA d){ + d->clips.size += 100; + d->clips.strings = (char **) realloc(d->clips.strings,d->clips.size * sizeof(char *)); +} + +/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1) +*/ +int Emf::in_clips(PEMF_CALLBACK_DATA d, const char *test){ + int i; + for(i=0; i<d->clips.count; i++){ + if(strcmp(test,d->clips.strings[i])==0)return(i+1); + } + return(0); +} + +/* (Conditionally) add a clip. + If a matching clip already exists nothing happens + If one does exist it is added to the clips list, entered into <defs>. +*/ +void Emf::add_clips(PEMF_CALLBACK_DATA d, const char *clippath, unsigned int logic){ + int op = combine_ops_to_livarot(logic); + Geom::PathVector combined_vect; + char *combined = nullptr; + if (op >= 0 && d->dc[d->level].clip_id) { + unsigned int real_idx = d->dc[d->level].clip_id - 1; + Geom::PathVector old_vect = sp_svg_read_pathv(d->clips.strings[real_idx]); + Geom::PathVector new_vect = sp_svg_read_pathv(clippath); + combined_vect = sp_pathvector_boolop(new_vect, old_vect, (bool_op) op , (FillRule) fill_oddEven, (FillRule) fill_oddEven); + combined = sp_svg_write_path(combined_vect); + } + else { + combined = strdup(clippath); // COPY operation, erases everything and starts a new one + } + + uint32_t idx = in_clips(d, combined); + if(!idx){ // add clip if not already present + if(d->clips.count == d->clips.size){ enlarge_clips(d); } + d->clips.strings[d->clips.count++]=strdup(combined); + d->dc[d->level].clip_id = d->clips.count; // one more than the slot where it is actually stored + SVGOStringStream tmp_clippath; + tmp_clippath << "\n<clipPath"; + tmp_clippath << "\n\tclipPathUnits=\"userSpaceOnUse\" "; + tmp_clippath << "\n\tid=\"clipEmfPath" << d->dc[d->level].clip_id << "\""; + tmp_clippath << " >"; + tmp_clippath << "\n\t<path d=\""; + tmp_clippath << combined; + tmp_clippath << "\""; + tmp_clippath << "\n\t/>"; + tmp_clippath << "\n</clipPath>"; + d->outdef += tmp_clippath.str().c_str(); + } + else { + d->dc[d->level].clip_id = idx; + } + free(combined); +} + + + +void +Emf::output_style(PEMF_CALLBACK_DATA d, int iType) +{ +// SVGOStringStream tmp_id; + SVGOStringStream tmp_style; + char tmp[1024] = {0}; + + float fill_rgb[3]; + d->dc[d->level].style.fill.value.color.get_rgb_floatv(fill_rgb); + float stroke_rgb[3]; + d->dc[d->level].style.stroke.value.color.get_rgb_floatv(stroke_rgb); + + // for U_EMR_BITBLT with no image, try to approximate some of these operations/ + // Assume src color is "white" + if(d->dwRop3){ + switch(d->dwRop3){ + case U_PATINVERT: // invert pattern + fill_rgb[0] = 1.0 - fill_rgb[0]; + fill_rgb[1] = 1.0 - fill_rgb[1]; + fill_rgb[2] = 1.0 - fill_rgb[2]; + break; + case U_SRCINVERT: // treat all of these as black + case U_DSTINVERT: + case U_BLACKNESS: + case U_SRCERASE: + case U_NOTSRCCOPY: + fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=0.0; + break; + case U_SRCCOPY: // treat all of these as white + case U_NOTSRCERASE: + case U_WHITENESS: + fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=1.0; + break; + case U_SRCPAINT: // use the existing color + case U_SRCAND: + case U_MERGECOPY: + case U_MERGEPAINT: + case U_PATPAINT: + case U_PATCOPY: + default: + break; + } + d->dwRop3 = 0; // might as well reset it here, it must be set for each BITBLT + } + + // Implement some of these, the ones where the original screen color does not matter. + // The options that merge screen and pen colors cannot be done correctly because we + // have no way of knowing what color is already on the screen. For those just pass the + // pen color through. + switch(d->dwRop2){ + case U_R2_BLACK: + fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 0.0; + stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 0.0; + break; + case U_R2_NOTMERGEPEN: + case U_R2_MASKNOTPEN: + break; + case U_R2_NOTCOPYPEN: + fill_rgb[0] = 1.0 - fill_rgb[0]; + fill_rgb[1] = 1.0 - fill_rgb[1]; + fill_rgb[2] = 1.0 - fill_rgb[2]; + stroke_rgb[0] = 1.0 - stroke_rgb[0]; + stroke_rgb[1] = 1.0 - stroke_rgb[1]; + stroke_rgb[2] = 1.0 - stroke_rgb[2]; + break; + case U_R2_MASKPENNOT: + case U_R2_NOT: + case U_R2_XORPEN: + case U_R2_NOTMASKPEN: + case U_R2_NOTXORPEN: + case U_R2_NOP: + case U_R2_MERGENOTPEN: + case U_R2_COPYPEN: + case U_R2_MASKPEN: + case U_R2_MERGEPENNOT: + case U_R2_MERGEPEN: + break; + case U_R2_WHITE: + fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 1.0; + stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 1.0; + break; + default: + break; + } + + +// tmp_id << "\n\tid=\"" << (d->id++) << "\""; +// d->outsvg += tmp_id.str().c_str(); + d->outsvg += "\n\tstyle=\""; + if (iType == U_EMR_STROKEPATH || !d->dc[d->level].fill_set) { + tmp_style << "fill:none;"; + } else { + switch(d->dc[d->level].fill_mode){ + // both of these use the url(#) method + case DRAW_PATTERN: + snprintf(tmp, 1023, "fill:url(#%s); ",d->hatches.strings[d->dc[d->level].fill_idx]); + tmp_style << tmp; + break; + case DRAW_IMAGE: + snprintf(tmp, 1023, "fill:url(#EMFimage%d_ref); ",d->dc[d->level].fill_idx); + tmp_style << tmp; + break; + case DRAW_LINEAR_GRADIENT: + case DRAW_PAINT: + default: // <-- this should never happen, but just in case... + snprintf( + tmp, 1023, + "fill:#%02x%02x%02x;", + SP_COLOR_F_TO_U(fill_rgb[0]), + SP_COLOR_F_TO_U(fill_rgb[1]), + SP_COLOR_F_TO_U(fill_rgb[2]) + ); + tmp_style << tmp; + break; + } + snprintf( + tmp, 1023, + "fill-rule:%s;", + (d->dc[d->level].style.fill_rule.value == SP_WIND_RULE_NONZERO ? "evenodd" : "nonzero") + ); + tmp_style << tmp; + tmp_style << "fill-opacity:1;"; + + // if the stroke is the same as the fill, and the right size not to change the end size of the object, do not do it separately + if( + (d->dc[d->level].fill_set ) && + (d->dc[d->level].stroke_set ) && + (d->dc[d->level].style.stroke_width.value == 1 ) && + (d->dc[d->level].fill_mode == d->dc[d->level].stroke_mode) && + ( + (d->dc[d->level].fill_mode != DRAW_PAINT) || + ( + (fill_rgb[0]==stroke_rgb[0]) && + (fill_rgb[1]==stroke_rgb[1]) && + (fill_rgb[2]==stroke_rgb[2]) + ) + ) + ){ + d->dc[d->level].stroke_set = false; + } + } + + if (iType == U_EMR_FILLPATH || !d->dc[d->level].stroke_set) { + tmp_style << "stroke:none;"; + } else { + switch(d->dc[d->level].stroke_mode){ + // both of these use the url(#) method + case DRAW_PATTERN: + snprintf(tmp, 1023, "stroke:url(#%s); ",d->hatches.strings[d->dc[d->level].stroke_idx]); + tmp_style << tmp; + break; + case DRAW_IMAGE: + snprintf(tmp, 1023, "stroke:url(#EMFimage%d_ref); ",d->dc[d->level].stroke_idx); + tmp_style << tmp; + break; + case DRAW_LINEAR_GRADIENT: + case DRAW_PAINT: + default: // <-- this should never happen, but just in case... + snprintf( + tmp, 1023, + "stroke:#%02x%02x%02x;", + SP_COLOR_F_TO_U(stroke_rgb[0]), + SP_COLOR_F_TO_U(stroke_rgb[1]), + SP_COLOR_F_TO_U(stroke_rgb[2]) + ); + tmp_style << tmp; + break; + } + tmp_style << "stroke-width:" << + MAX( 0.001, d->dc[d->level].style.stroke_width.value ) << "px;"; + + tmp_style << "stroke-linecap:" << + ( + d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_BUTT ? "butt" : + d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_ROUND ? "round" : + d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_SQUARE ? "square" : + "unknown" + ) << ";"; + + tmp_style << "stroke-linejoin:" << + ( + d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_MITER ? "miter" : + d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_ROUND ? "round" : + d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_BEVEL ? "bevel" : + "unknown" + ) << ";"; + + // Set miter limit if known, even if it is not needed immediately (not miter) + tmp_style << "stroke-miterlimit:" << + MAX( 2.0, d->dc[d->level].style.stroke_miterlimit.value ) << ";"; + + if (d->dc[d->level].style.stroke_dasharray.set && + !d->dc[d->level].style.stroke_dasharray.values.empty() ) + { + tmp_style << "stroke-dasharray:"; + for (unsigned i=0; i<d->dc[d->level].style.stroke_dasharray.values.size(); i++) { + if (i) + tmp_style << ","; + tmp_style << d->dc[d->level].style.stroke_dasharray.values[i].value; + } + tmp_style << ";"; + tmp_style << "stroke-dashoffset:0;"; + } else { + tmp_style << "stroke-dasharray:none;"; + } + tmp_style << "stroke-opacity:1;"; + } + tmp_style << "\" "; + if (d->dc[d->level].clip_id) + tmp_style << "\n\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\" "; + + d->outsvg += tmp_style.str().c_str(); +} + + +double +Emf::_pix_x_to_point(PEMF_CALLBACK_DATA d, double px) +{ + double scale = (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0); + double tmp; + tmp = ((((double) (px - d->dc[d->level].winorg.x))*scale) + d->dc[d->level].vieworg.x) * d->D2PscaleX; + tmp -= d->ulCornerOutX; //The EMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner + return(tmp); +} + +double +Emf::_pix_y_to_point(PEMF_CALLBACK_DATA d, double py) +{ + double scale = (d->dc[d->level].ScaleInY ? d->dc[d->level].ScaleInY : 1.0); + double tmp; + tmp = ((((double) (py - d->dc[d->level].winorg.y))*scale) * d->E2IdirY + d->dc[d->level].vieworg.y) * d->D2PscaleY; + tmp -= d->ulCornerOutY; //The EMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner + return(tmp); +} + + +double +Emf::pix_to_x_point(PEMF_CALLBACK_DATA d, double px, double py) +{ + double wpx = px * d->dc[d->level].worldTransform.eM11 + py * d->dc[d->level].worldTransform.eM21 + d->dc[d->level].worldTransform.eDx; + double x = _pix_x_to_point(d, wpx); + + return x; +} + +double +Emf::pix_to_y_point(PEMF_CALLBACK_DATA d, double px, double py) +{ + + double wpy = px * d->dc[d->level].worldTransform.eM12 + py * d->dc[d->level].worldTransform.eM22 + d->dc[d->level].worldTransform.eDy; + double y = _pix_y_to_point(d, wpy); + + return y; + +} + +double +Emf::pix_to_abs_size(PEMF_CALLBACK_DATA d, double px) +{ + double ppx = fabs(px * (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0) * d->D2PscaleX * current_scale(d)); + return ppx; +} + +/* snaps coordinate pairs made up of values near +/-faraway, +/-faraway to exactly faraway. + This eliminates coordinate drift on repeated clipping cycles which use exclude. + It should not affect internals of normal drawings because the value of faraway is so large. +*/ +void +Emf::snap_to_faraway_pair(double *x, double *y) +{ + if((std::abs(std::abs(*x) - faraway)/faraway <= 1e-4) && (std::abs(std::abs(*y) - faraway)/faraway <= 1e-4)){ + *x = (*x > 0 ? faraway : -faraway); + *y = (*y > 0 ? faraway : -faraway); + } +} + +/* returns "x,y" (without the quotes) in inkscape coordinates for a pair of EMF x,y coordinates. + Since exclude clip can go through here, it calls snap_to_faraway_pair for numerical stability. +*/ +std::string Emf::pix_to_xy(PEMF_CALLBACK_DATA d, double x, double y){ + SVGOStringStream cxform; + double tx = pix_to_x_point(d,x,y); + double ty = pix_to_y_point(d,x,y); + snap_to_faraway_pair(&tx,&ty); + cxform << tx; + cxform << ","; + cxform << ty; + return(cxform.str()); +} + + +void +Emf::select_pen(PEMF_CALLBACK_DATA d, int index) +{ + PU_EMRCREATEPEN pEmr = nullptr; + + if (index >= 0 && index < d->n_obj){ + pEmr = (PU_EMRCREATEPEN) d->emf_obj[index].lpEMFR; + } + + if (!pEmr){ return; } + + switch (pEmr->lopn.lopnStyle & U_PS_STYLE_MASK) { + case U_PS_DASH: + case U_PS_DOT: + case U_PS_DASHDOT: + case U_PS_DASHDOTDOT: + { + SPILength spilength(1.f); + int penstyle = (pEmr->lopn.lopnStyle & U_PS_STYLE_MASK); + if (!d->dc[d->level].style.stroke_dasharray.values.empty() && + (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray != + d->dc[d->level - 1].style.stroke_dasharray))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + spilength.setDouble(3); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + spilength.setDouble(1); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + if (penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + + d->dc[d->level].style.stroke_dasharray.set = true; + break; + } + + case U_PS_SOLID: + default: + { + d->dc[d->level].style.stroke_dasharray.set = false; + break; + } + } + + switch (pEmr->lopn.lopnStyle & U_PS_ENDCAP_MASK) { + case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND; break; } + case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; break; } + case U_PS_ENDCAP_FLAT: + default: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT; break; } + } + + switch (pEmr->lopn.lopnStyle & U_PS_JOIN_MASK) { + case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL; break; } + case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; break; } + case U_PS_JOIN_ROUND: + default: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND; break; } + } + + d->dc[d->level].stroke_set = true; + + if (pEmr->lopn.lopnStyle == U_PS_NULL) { + d->dc[d->level].style.stroke_width.value = 0; + d->dc[d->level].stroke_set = false; + } else if (pEmr->lopn.lopnWidth.x) { + int cur_level = d->level; + d->level = d->emf_obj[index].level; + double pen_width = pix_to_abs_size( d, pEmr->lopn.lopnWidth.x ); + d->level = cur_level; + d->dc[d->level].style.stroke_width.value = pen_width; + } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?) + //d->dc[d->level].style.stroke_width.value = 1.0; + int cur_level = d->level; + d->level = d->emf_obj[index].level; + double pen_width = pix_to_abs_size( d, 1 ); + d->level = cur_level; + d->dc[d->level].style.stroke_width.value = pen_width; + } + + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->lopn.lopnColor) ); + g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->lopn.lopnColor) ); + b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->lopn.lopnColor) ); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); +} + + +void +Emf::select_extpen(PEMF_CALLBACK_DATA d, int index) +{ + PU_EMREXTCREATEPEN pEmr = nullptr; + + if (index >= 0 && index < d->n_obj) + pEmr = (PU_EMREXTCREATEPEN) d->emf_obj[index].lpEMFR; + + if (!pEmr) + return; + + switch (pEmr->elp.elpPenStyle & U_PS_STYLE_MASK) { + case U_PS_USERSTYLE: + { + if (pEmr->elp.elpNumEntries) { + if (!d->dc[d->level].style.stroke_dasharray.values.empty() && + (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray != + d->dc[d->level - 1].style.stroke_dasharray))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + for (unsigned int i=0; i<pEmr->elp.elpNumEntries; i++) { + double dash_length = pix_to_abs_size( d, pEmr->elp.elpStyleEntry[i] ); + d->dc[d->level].style.stroke_dasharray.values.emplace_back(dash_length); + } + d->dc[d->level].style.stroke_dasharray.set = true; + } else { + d->dc[d->level].style.stroke_dasharray.set = false; + } + break; + } + + case U_PS_DASH: + case U_PS_DOT: + case U_PS_DASHDOT: + case U_PS_DASHDOTDOT: + { + int penstyle = (pEmr->elp.elpPenStyle & U_PS_STYLE_MASK); + if (!d->dc[d->level].style.stroke_dasharray.values.empty() && + (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray != + d->dc[d->level - 1].style.stroke_dasharray))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + SPILength spilength; + if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + spilength.setDouble(3); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + spilength.setDouble(2); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + spilength.setDouble(1); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + spilength.setDouble(2); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + if (penstyle==U_PS_DASHDOTDOT) { + spilength.setDouble(1); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + spilength.setDouble(2); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + + d->dc[d->level].style.stroke_dasharray.set = true; + break; + } + case U_PS_SOLID: +/* includes these for now, some should maybe not be in here + case U_PS_NULL: + case U_PS_INSIDEFRAME: + case U_PS_ALTERNATE: + case U_PS_STYLE_MASK: +*/ + default: + { + d->dc[d->level].style.stroke_dasharray.set = false; + break; + } + } + + switch (pEmr->elp.elpPenStyle & U_PS_ENDCAP_MASK) { + case U_PS_ENDCAP_ROUND: + { + d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND; + break; + } + case U_PS_ENDCAP_SQUARE: + { + d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; + break; + } + case U_PS_ENDCAP_FLAT: + default: + { + d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT; + break; + } + } + + switch (pEmr->elp.elpPenStyle & U_PS_JOIN_MASK) { + case U_PS_JOIN_BEVEL: + { + d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL; + break; + } + case U_PS_JOIN_MITER: + { + d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; + break; + } + case U_PS_JOIN_ROUND: + default: + { + d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND; + break; + } + } + + d->dc[d->level].stroke_set = true; + + if (pEmr->elp.elpPenStyle == U_PS_NULL) { // draw nothing, but fill out all the values with something + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); + g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); + b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].style.stroke_width.value = 0; + d->dc[d->level].stroke_set = false; + d->dc[d->level].stroke_mode = DRAW_PAINT; + } + else { + if (pEmr->elp.elpWidth) { + int cur_level = d->level; + d->level = d->emf_obj[index].level; + double pen_width = pix_to_abs_size( d, pEmr->elp.elpWidth ); + d->level = cur_level; + d->dc[d->level].style.stroke_width.value = pen_width; + } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?) + //d->dc[d->level].style.stroke_width.value = 1.0; + int cur_level = d->level; + d->level = d->emf_obj[index].level; + double pen_width = pix_to_abs_size( d, 1 ); + d->level = cur_level; + d->dc[d->level].style.stroke_width.value = pen_width; + } + + if( pEmr->elp.elpBrushStyle == U_BS_SOLID){ + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->elp.elpColor) ); + g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->elp.elpColor) ); + b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->elp.elpColor) ); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].stroke_mode = DRAW_PAINT; + d->dc[d->level].stroke_set = true; + } + else if(pEmr->elp.elpBrushStyle == U_BS_HATCHED){ + d->dc[d->level].stroke_idx = add_hatch(d, pEmr->elp.elpHatch, pEmr->elp.elpColor); + d->dc[d->level].stroke_recidx = index; // used if the hatch needs to be redone due to bkMode, textmode, etc. changes + d->dc[d->level].stroke_mode = DRAW_PATTERN; + d->dc[d->level].stroke_set = true; + } + else if(pEmr->elp.elpBrushStyle == U_BS_DIBPATTERN || pEmr->elp.elpBrushStyle == U_BS_DIBPATTERNPT){ + d->dc[d->level].stroke_idx = add_image(d, (void *)pEmr, pEmr->cbBits, pEmr->cbBmi, *(uint32_t *) &(pEmr->elp.elpColor), pEmr->offBits, pEmr->offBmi); + d->dc[d->level].stroke_mode = DRAW_IMAGE; + d->dc[d->level].stroke_set = true; + } + else { // U_BS_PATTERN and anything strange that falls in, stroke is solid textColor + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); + g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); + b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); + d->dc[d->level].stroke_mode = DRAW_PAINT; + d->dc[d->level].stroke_set = true; + } + } +} + + +void +Emf::select_brush(PEMF_CALLBACK_DATA d, int index) +{ + uint32_t tidx; + uint32_t iType; + + if (index >= 0 && index < d->n_obj){ + iType = ((PU_EMR) (d->emf_obj[index].lpEMFR))->iType; + if(iType == U_EMR_CREATEBRUSHINDIRECT){ + PU_EMRCREATEBRUSHINDIRECT pEmr = (PU_EMRCREATEBRUSHINDIRECT) d->emf_obj[index].lpEMFR; + if( pEmr->lb.lbStyle == U_BS_SOLID){ + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->lb.lbColor) ); + g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->lb.lbColor) ); + b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->lb.lbColor) ); + d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].fill_mode = DRAW_PAINT; + d->dc[d->level].fill_set = true; + } + else if(pEmr->lb.lbStyle == U_BS_HATCHED){ + d->dc[d->level].fill_idx = add_hatch(d, pEmr->lb.lbHatch, pEmr->lb.lbColor); + d->dc[d->level].fill_recidx = index; // used if the hatch needs to be redone due to bkMode, textmode, etc. changes + d->dc[d->level].fill_mode = DRAW_PATTERN; + d->dc[d->level].fill_set = true; + } + } + else if(iType == U_EMR_CREATEDIBPATTERNBRUSHPT || iType == U_EMR_CREATEMONOBRUSH){ + PU_EMRCREATEDIBPATTERNBRUSHPT pEmr = (PU_EMRCREATEDIBPATTERNBRUSHPT) d->emf_obj[index].lpEMFR; + tidx = add_image(d, (void *) pEmr, pEmr->cbBits, pEmr->cbBmi, pEmr->iUsage, pEmr->offBits, pEmr->offBmi); + if(tidx == U_EMR_INVALID){ // This happens if createmonobrush has a DIB that isn't monochrome + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); + g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); + b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); + d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].fill_mode = DRAW_PAINT; + } + else { + d->dc[d->level].fill_idx = tidx; + d->dc[d->level].fill_mode = DRAW_IMAGE; + } + d->dc[d->level].fill_set = true; + } + } +} + + +void +Emf::select_font(PEMF_CALLBACK_DATA d, int index) +{ + PU_EMREXTCREATEFONTINDIRECTW pEmr = nullptr; + + if (index >= 0 && index < d->n_obj) + pEmr = (PU_EMREXTCREATEFONTINDIRECTW) d->emf_obj[index].lpEMFR; + + if (!pEmr)return; + + + /* The logfont information always starts with a U_LOGFONT structure but the U_EMREXTCREATEFONTINDIRECTW + is defined as U_LOGFONT_PANOSE so it can handle one of those if that is actually present. Currently only logfont + is supported, and the remainder, it it really is a U_LOGFONT_PANOSE record, is ignored + */ + int cur_level = d->level; + d->level = d->emf_obj[index].level; + double font_size = pix_to_abs_size( d, pEmr->elfw.elfLogFont.lfHeight ); + /* snap the font_size to the nearest 1/32nd of a point. + (The size is converted from Pixels to points, snapped, and converted back.) + See the notes where d->D2Pscale[XY] are set for the reason why. + Typically this will set the font to the desired exact size. If some peculiar size + was intended this will, at worst, make it .03125 off, which is unlikely to be a problem. */ + font_size = round(20.0 * 0.8 * font_size)/(20.0 * 0.8); + d->level = cur_level; + d->dc[d->level].style.font_size.computed = font_size; + d->dc[d->level].style.font_weight.value = + pEmr->elfw.elfLogFont.lfWeight == U_FW_THIN ? SP_CSS_FONT_WEIGHT_100 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_200 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_LIGHT ? SP_CSS_FONT_WEIGHT_300 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_400 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_MEDIUM ? SP_CSS_FONT_WEIGHT_500 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_SEMIBOLD ? SP_CSS_FONT_WEIGHT_600 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_700 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_800 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_HEAVY ? SP_CSS_FONT_WEIGHT_900 : + pEmr->elfw.elfLogFont.lfWeight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_NORMAL : + pEmr->elfw.elfLogFont.lfWeight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_BOLD : + pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_LIGHTER : + pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_BOLDER : + SP_CSS_FONT_WEIGHT_NORMAL; + d->dc[d->level].style.font_style.value = (pEmr->elfw.elfLogFont.lfItalic ? SP_CSS_FONT_STYLE_ITALIC : SP_CSS_FONT_STYLE_NORMAL); + d->dc[d->level].style.text_decoration_line.underline = pEmr->elfw.elfLogFont.lfUnderline; + d->dc[d->level].style.text_decoration_line.line_through = pEmr->elfw.elfLogFont.lfStrikeOut; + d->dc[d->level].style.text_decoration_line.set = true; + d->dc[d->level].style.text_decoration_line.inherit = false; + // malformed EMF with empty filename may exist, ignore font change if encountered + char *ctmp = U_Utf16leToUtf8((uint16_t *) (pEmr->elfw.elfLogFont.lfFaceName), U_LF_FACESIZE, nullptr); + if(ctmp){ + if (d->dc[d->level].font_name){ free(d->dc[d->level].font_name); } + if(*ctmp){ + d->dc[d->level].font_name = ctmp; + } + else { // Malformed EMF might specify an empty font name + free(ctmp); + d->dc[d->level].font_name = strdup("Arial"); // Default font, EMF spec says device can pick whatever it wants + } + } + d->dc[d->level].style.baseline_shift.value = round((double)((pEmr->elfw.elfLogFont.lfEscapement + 3600) % 3600)) / 10.0; // use baseline_shift instead of text_transform to avoid overflow +} + +void +Emf::delete_object(PEMF_CALLBACK_DATA d, int index) +{ + if (index >= 0 && index < d->n_obj) { + d->emf_obj[index].type = 0; +// We are keeping a copy of the EMR rather than just a structure. Currently that is not necessary as the entire +// EMF is read in at once and is stored in a big malloc. However, in past versions it was handled +// record by record, and we might need to do that again at some point in the future if we start running into EMF +// files too big to fit into memory. + if (d->emf_obj[index].lpEMFR) + free(d->emf_obj[index].lpEMFR); + d->emf_obj[index].lpEMFR = nullptr; + } +} + + +void +Emf::insert_object(PEMF_CALLBACK_DATA d, int index, int type, PU_ENHMETARECORD pObj) +{ + if (index >= 0 && index < d->n_obj) { + delete_object(d, index); + d->emf_obj[index].type = type; + d->emf_obj[index].level = d->level; + d->emf_obj[index].lpEMFR = emr_dup((char *) pObj); + } +} + +/* Identify probable Adobe Illustrator produced EMF files, which do strange things with the scaling. + The few so far observed all had this format. +*/ +int Emf::AI_hack(PU_EMRHEADER pEmr){ + int ret=0; + char *ptr; + ptr = (char *)pEmr; + PU_EMRSETMAPMODE nEmr = (PU_EMRSETMAPMODE) (ptr + pEmr->emr.nSize); + char *string = nullptr; + if(pEmr->nDescription)string = U_Utf16leToUtf8((uint16_t *)((char *) pEmr + pEmr->offDescription), pEmr->nDescription, nullptr); + if(string){ + if((pEmr->nDescription >= 13) && + (0==strcmp("Adobe Systems",string)) && + (nEmr->emr.iType == U_EMR_SETMAPMODE) && + (nEmr->iMode == U_MM_ANISOTROPIC)){ ret=1; } + free(string); + } + return(ret); +} + +/** + \fn create a UTF-32LE buffer and fill it with UNICODE unknown character + \param count number of copies of the Unicode unknown character to fill with +*/ +uint32_t *Emf::unknown_chars(size_t count){ + uint32_t *res = (uint32_t *) malloc(sizeof(uint32_t) * (count + 1)); + if(!res)throw "Inkscape fatal memory allocation error - cannot continue"; + for(uint32_t i=0; i<count; i++){ res[i] = 0xFFFD; } + res[count]=0; + return res; +} + +/** + \fn store SVG for an image given the pixmap and various coordinate information + \param d + \param pEmr + \param dx (double) destination x in inkscape pixels + \param dy (double) destination y in inkscape pixels + \param dw (double) destination width in inkscape pixels + \param dh (double) destination height in inkscape pixels + \param sx (int) source x in src image pixels + \param sy (int) source y in src image pixels + \param iUsage + \param offBits + \param cbBits + \param offBmi + \param cbBmi +*/ +void Emf::common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr, + double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh, + uint32_t iUsage, uint32_t offBits, uint32_t cbBits, uint32_t offBmi, uint32_t cbBmi){ + + SVGOStringStream tmp_image; + int dibparams = U_BI_UNKNOWN; // type of image not yet determined + + tmp_image << "\n\t <image\n"; + if (d->dc[d->level].clip_id){ + tmp_image << "\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n"; + } + tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n "; + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = nullptr; + + char *rgba_px = nullptr; // RGBA pixels + char *sub_px = nullptr; // RGBA pixels, subarray + const char *px = nullptr; // DIB pixels + const U_RGBQUAD *ct = nullptr; // DIB color table + uint32_t width, height, colortype, numCt, invert; // if needed these values will be set in get_DIB_params + if(cbBits && cbBmi && (iUsage == U_DIB_RGB_COLORS)){ + // next call returns pointers and values, but allocates no memory + dibparams = get_DIB_params((const char *)pEmr, offBits, offBmi, &px, (const U_RGBQUAD **) &ct, + &numCt, &width, &height, &colortype, &invert); + if(dibparams ==U_BI_RGB){ + if(sw == 0 || sh == 0){ + sw = width; + sh = height; + } + + if(!DIB_to_RGBA( + px, // DIB pixel array + ct, // DIB color table + numCt, // DIB color table number of entries + &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free. + width, // Width of pixel array + height, // Height of pixel array + colortype, // DIB BitCount Enumeration + numCt, // Color table used if not 0 + invert // If DIB rows are in opposite order from RGBA rows + )){ + sub_px = RGBA_to_RGBA( // returns either a subset (side effect: frees rgba_px) or NULL (for subset == entire image) + rgba_px, // full pixel array from DIB + width, // Width of pixel array + height, // Height of pixel array + sx,sy, // starting point in pixel array + &sw,&sh // columns/rows to extract from the pixel array (output array size) + ); + + if(!sub_px)sub_px=rgba_px; + toPNG( // Get the image from the RGBA px into mempng + &mempng, + sw, sh, // size of the extracted pixel array + sub_px + ); + free(sub_px); + } + } + } + + gchar *base64String=nullptr; + if(dibparams == U_BI_JPEG){ // image was binary jpg in source file + tmp_image << " xlink:href=\"data:image/jpeg;base64,"; + base64String = g_base64_encode((guchar*) px, numCt ); + } + else if(dibparams==U_BI_PNG){ // image was binary png in source file + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = g_base64_encode((guchar*) px, numCt ); + } + else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size ); + free(mempng.buffer); + } + else { // unknown or unsupported image type or failed conversion, insert the common bad image picture + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = bad_image_png(); + } + tmp_image << base64String; + g_free(base64String); + + tmp_image << "\"\n height=\"" << dh << "\"\n width=\"" << dw << "\"\n"; + + tmp_image << " transform=" << current_matrix(d, dx, dy, 1); // calculate appropriate offset + tmp_image << " preserveAspectRatio=\"none\"\n"; + tmp_image << "/> \n"; + + d->outsvg += tmp_image.str().c_str(); + d->path = ""; +} + +/** + \fn myEnhMetaFileProc(char *contents, unsigned int length, PEMF_CALLBACK_DATA lpData) + \param contents binary contents of an EMF file + \param length length in bytes of contents + \param d Inkscape data structures returned by this call +*/ +//THis was a callback, just build it into a normal function +int Emf::myEnhMetaFileProc(char *contents, unsigned int length, PEMF_CALLBACK_DATA d) +{ + uint32_t off=0; + uint32_t emr_mask; + int OK =1; + int file_status=1; + uint32_t nSize; + uint32_t iType; + const char *blimit = contents + length; + PU_ENHMETARECORD lpEMFR; + TCHUNK_SPECS tsp; + uint32_t tbkMode = U_TRANSPARENT; // holds proposed change to bkMode, if text is involved saving these to the DC must wait until the text is written + U_COLORREF tbkColor = U_RGB(255, 255, 255); // holds proposed change to bkColor + + // code for end user debugging + int eDbgRecord=0; + int eDbgComment=0; + int eDbgFinal=0; + char const* eDbgString = getenv( "INKSCAPE_DBG_EMF" ); + if ( eDbgString != nullptr ) { + if(strstr(eDbgString,"RECORD")){ eDbgRecord = 1; } + if(strstr(eDbgString,"COMMENT")){ eDbgComment = 1; } + if(strstr(eDbgString,"FINAL")){ eDbgFinal = 1; } + } + + /* initialize the tsp for text reassembly */ + tsp.string = nullptr; + tsp.ori = 0.0; /* degrees */ + tsp.fs = 12.0; /* font size */ + tsp.x = 0.0; + tsp.y = 0.0; + tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/ + tsp.vadvance = 0.0; /* meaningful only when a complex contains two or more lines */ + tsp.taln = ALILEFT + ALIBASE; + tsp.ldir = LDIR_LR; + tsp.spaces = 0; // this field is only used for debugging + tsp.color.Red = 0; /* RGB Black */ + tsp.color.Green = 0; /* RGB Black */ + tsp.color.Blue = 0; /* RGB Black */ + tsp.color.Reserved = 0; /* not used */ + tsp.italics = 0; + tsp.weight = 80; + tsp.decoration = TXTDECOR_NONE; + tsp.condensed = 100; + tsp.co = 0; + tsp.fi_idx = -1; /* set to an invalid */ + + while(OK){ + if(off>=length)return(0); //normally should exit from while after EMREOF sets OK to false. + + // check record sizes and types thoroughly + int badrec = 0; + if (!U_emf_record_sizeok(contents + off, blimit, &nSize, &iType, 1) || + !U_emf_record_safe(contents + off)){ + badrec = 1; + } + else { + emr_mask = emr_properties(iType); + if (emr_mask == U_EMR_INVALID) { badrec = 1; } + } + if (badrec) { + file_status = 0; + break; + } + + lpEMFR = (PU_ENHMETARECORD)(contents + off); + +// At run time define environment variable INKSCAPE_DBG_EMF to include string RECORD. +// Users may employ this to track down toxic records + if(eDbgRecord){ + std::cout << "record type: " << iType << " name " << U_emr_names(iType) << " length: " << nSize << " offset: " << off <<std::endl; + } + off += nSize; + + SVGOStringStream tmp_outsvg; + SVGOStringStream tmp_path; + SVGOStringStream tmp_str; + SVGOStringStream dbg_str; + +/* Uncomment the following to track down text problems */ +//std::cout << "tri->dirty:"<< d->tri->dirty << " emr_mask: " << std::hex << emr_mask << std::dec << std::endl; + + // incompatible change to text drawing detected (color or background change) forces out existing text + // OR + // next record is valid type and forces pending text to be drawn immediately + if ((d->dc[d->level].dirty & DIRTY_TEXT) || ((emr_mask != U_EMR_INVALID) && (emr_mask & U_DRAW_TEXT) && d->tri->dirty)){ + TR_layout_analyze(d->tri); + if (d->dc[d->level].clip_id){ + SVGOStringStream tmp_clip; + tmp_clip << "\n<g\n\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n>"; + d->outsvg += tmp_clip.str().c_str(); + } + TR_layout_2_svg(d->tri); + SVGOStringStream ts; + ts << d->tri->out; + d->outsvg += ts.str().c_str(); + d->tri = trinfo_clear(d->tri); + if (d->dc[d->level].clip_id){ + d->outsvg += "\n</g>\n"; + } + } + if(d->dc[d->level].dirty){ //Apply the delayed background changes, clear the flag + d->dc[d->level].bkMode = tbkMode; + memcpy(&(d->dc[d->level].bkColor),&tbkColor, sizeof(U_COLORREF)); + + if(d->dc[d->level].dirty & DIRTY_TEXT){ + // U_COLORREF and TRCOLORREF are exactly the same in memory, but the compiler needs some convincing... + if(tbkMode == U_TRANSPARENT){ (void) trinfo_load_bk(d->tri, BKCLR_NONE, *(TRCOLORREF *) &tbkColor); } + else { (void) trinfo_load_bk(d->tri, BKCLR_LINE, *(TRCOLORREF *) &tbkColor); } // Opaque + } + + /* It is possible to have a series of EMF records that would result in + the following creating hash patterns which are never used. For instance, if + there were a series of records that changed the background color but did nothing + else. + */ + if((d->dc[d->level].stroke_mode == DRAW_PATTERN) && (d->dc[d->level].dirty & DIRTY_STROKE)){ + select_extpen(d, d->dc[d->level].stroke_recidx); + } + + if((d->dc[d->level].fill_mode == DRAW_PATTERN) && (d->dc[d->level].dirty & DIRTY_FILL)){ + select_brush(d, d->dc[d->level].fill_recidx); + } + + d->dc[d->level].dirty = 0; + } + +// std::cout << "BEFORE DRAW logic d->mask: " << std::hex << d->mask << " emr_mask: " << emr_mask << std::dec << std::endl; +/* +std::cout << "BEFORE DRAW" + << " test0 " << ( d->mask & U_DRAW_VISIBLE) + << " test1 " << ( d->mask & U_DRAW_FORCE) + << " test2 " << (emr_mask & U_DRAW_ALTERS) + << " test3 " << (emr_mask & U_DRAW_VISIBLE) + << " test4 " << !(d->mask & U_DRAW_ONLYTO) + << " test5 " << ((d->mask & U_DRAW_ONLYTO) && !(emr_mask & U_DRAW_ONLYTO) ) + << std::endl; +*/ + + if( + (emr_mask != U_EMR_INVALID) && // next record is valid type + (d->mask & U_DRAW_VISIBLE) && // Current set of objects are drawable + ( + (d->mask & U_DRAW_FORCE) || // This draw is forced by STROKE/FILL/STROKEANDFILL PATH + (emr_mask & U_DRAW_ALTERS) || // Next record would alter the drawing environment in some way + ( + (emr_mask & U_DRAW_VISIBLE) && // Next record is visible... + ( + ( !(d->mask & U_DRAW_ONLYTO) ) || // Non *TO records cannot be followed by any Visible + ((d->mask & U_DRAW_ONLYTO) && !(emr_mask & U_DRAW_ONLYTO) )// *TO records can only be followed by other *TO records + ) + ) + ) + ){ +// std::cout << "PATH DRAW at TOP path" << *(d->path) << std::endl; + if(!(d->path.empty())){ + d->outsvg += " <path "; // this is the ONLY place <path should be used!!! One exception, gradientfill. + if(d->drawtype){ // explicit draw type EMR record + output_style(d, d->drawtype); + } + else if(d->mask & U_DRAW_CLOSED){ // implicit draw type + output_style(d, U_EMR_STROKEANDFILLPATH); + } + else { + output_style(d, U_EMR_STROKEPATH); + } + d->outsvg += "\n\t"; + d->outsvg += "\n\td=\""; // this is the ONLY place d=" should be used!!!! One exception, gradientfill. + d->outsvg += d->path; + d->outsvg += " \" /> \n"; + d->path = ""; + } + // reset the flags + d->mask = 0; + d->drawtype = 0; + } +// std::cout << "AFTER DRAW logic d->mask: " << std::hex << d->mask << " emr_mask: " << emr_mask << std::dec << std::endl; + + switch (iType) + { + case U_EMR_HEADER: + { + dbg_str << "<!-- U_EMR_HEADER -->\n"; + + d->outdef += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"; + + if (d->pDesc) { + d->outdef += "<!-- "; + d->outdef += d->pDesc; + d->outdef += " -->\n"; + } + + PU_EMRHEADER pEmr = (PU_EMRHEADER) lpEMFR; + SVGOStringStream tmp_outdef; + tmp_outdef << "<svg\n"; + tmp_outdef << " xmlns:svg=\"http://www.w3.org/2000/svg\"\n"; + tmp_outdef << " xmlns=\"http://www.w3.org/2000/svg\"\n"; + tmp_outdef << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"; + tmp_outdef << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"; // needed for sodipodi:role + tmp_outdef << " version=\"1.0\"\n"; + + + /* inclusive-inclusive, so the size is 1 more than the difference */ + d->MM100InX = pEmr->rclFrame.right - pEmr->rclFrame.left + 1; + d->MM100InY = pEmr->rclFrame.bottom - pEmr->rclFrame.top + 1; + d->PixelsInX = pEmr->rclBounds.right - pEmr->rclBounds.left + 1; + d->PixelsInY = pEmr->rclBounds.bottom - pEmr->rclBounds.top + 1; + + /* + calculate ratio of Inkscape dpi/EMF device dpi + This can cause problems later due to accuracy limits in the EMF. A high resolution + EMF might have a final D2Pscale[XY] of 0.074998, and adjusting the (integer) device size + by 1 will still not get it exactly to 0.075. Later when the font size is calculated it + can end up as 29.9992 or 22.4994 instead of the intended 30 or 22.5. This is handled by + snapping font sizes to the nearest .01. The best estimate is made by using both values. + */ + if ((pEmr->szlMillimeters.cx + pEmr->szlMillimeters.cy) && ( pEmr->szlDevice.cx + pEmr->szlDevice.cy)){ + d->E2IdirY = 1.0; // assume MM_TEXT, if not, this will be changed later + d->D2PscaleX = d->D2PscaleY = Inkscape::Util::Quantity::convert(1, "mm", "px") * + (double)(pEmr->szlMillimeters.cx + pEmr->szlMillimeters.cy)/ + (double)( pEmr->szlDevice.cx + pEmr->szlDevice.cy); + } + trinfo_load_qe(d->tri, d->D2PscaleX); /* quantization error that will affect text positions */ + + /* Adobe Illustrator files set mapmode to MM_ANISOTROPIC and somehow or other this + converts the rclFrame values from MM_HIMETRIC to MM_HIENGLISH, with another factor of 3 thrown + in for good measure. Ours not to question why... + */ + if(AI_hack(pEmr)){ + d->MM100InX *= 25.4/(10.0*3.0); + d->MM100InY *= 25.4/(10.0*3.0); + d->D2PscaleX *= 25.4/(10.0*3.0); + d->D2PscaleY *= 25.4/(10.0*3.0); + } + + d->MMX = d->MM100InX / 100.0; + d->MMY = d->MM100InY / 100.0; + + d->PixelsOutX = Inkscape::Util::Quantity::convert(d->MMX, "mm", "px"); + d->PixelsOutY = Inkscape::Util::Quantity::convert(d->MMY, "mm", "px"); + + // Upper left corner, from header rclBounds, in device units, usually both 0, but not always + d->ulCornerInX = pEmr->rclBounds.left; + d->ulCornerInY = pEmr->rclBounds.top; + d->ulCornerOutX = d->ulCornerInX * d->D2PscaleX; + d->ulCornerOutY = d->ulCornerInY * d->E2IdirY * d->D2PscaleY; + + tmp_outdef << + " width=\"" << d->MMX << "mm\"\n" << + " height=\"" << d->MMY << "mm\">\n"; + d->outdef += tmp_outdef.str().c_str(); + d->outdef += "<defs>"; // temporary end of header + + // d->defs holds any defines which are read in. + + tmp_outsvg << "\n</defs>\n\n"; // start of main body + + if (pEmr->nHandles) { + d->n_obj = pEmr->nHandles; + d->emf_obj = new EMF_OBJECT[d->n_obj]; + + // Init the new emf_obj list elements to null, provided the + // dynamic allocation succeeded. + if ( d->emf_obj != nullptr ) + { + for( int i=0; i < d->n_obj; ++i ) + d->emf_obj[i].lpEMFR = nullptr; + } //if + + } else { + d->emf_obj = nullptr; + } + + break; + } + case U_EMR_POLYBEZIER: + { + dbg_str << "<!-- U_EMR_POLYBEZIER -->\n"; + + PU_EMRPOLYBEZIER pEmr = (PU_EMRPOLYBEZIER) lpEMFR; + uint32_t i,j; + + if (pEmr->cptl<4) + break; + + d->mask |= emr_mask; + + tmp_str << + "\n\tM " << + pix_to_xy( d, pEmr->aptl[0].x, pEmr->aptl[0].y) << " "; + + for (i=1; i<pEmr->cptl; ) { + tmp_str << "\n\tC "; + for (j=0; j<3 && i<pEmr->cptl; j++,i++) { + tmp_str << pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y) << " "; + } + } + + tmp_path << tmp_str.str().c_str(); + + break; + } + case U_EMR_POLYGON: + { + dbg_str << "<!-- U_EMR_POLYGON -->\n"; + + PU_EMRPOLYGON pEmr = (PU_EMRPOLYGON) lpEMFR; + uint32_t i; + + if (pEmr->cptl < 2) + break; + + d->mask |= emr_mask; + + tmp_str << + "\n\tM " << + pix_to_xy( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " "; + + for (i=1; i<pEmr->cptl; i++) { + tmp_str << + "\n\tL " << + pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; + } + + tmp_path << tmp_str.str().c_str(); + tmp_path << " z"; + + break; + } + case U_EMR_POLYLINE: + { + dbg_str << "<!-- U_EMR_POLYLINE -->\n"; + + PU_EMRPOLYLINE pEmr = (PU_EMRPOLYLINE) lpEMFR; + uint32_t i; + + if (pEmr->cptl<2) + break; + + d->mask |= emr_mask; + + tmp_str << + "\n\tM " << + pix_to_xy( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " "; + + for (i=1; i<pEmr->cptl; i++) { + tmp_str << + "\n\tL " << + pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; + } + + tmp_path << tmp_str.str().c_str(); + + break; + } + case U_EMR_POLYBEZIERTO: + { + dbg_str << "<!-- U_EMR_POLYBEZIERTO -->\n"; + + PU_EMRPOLYBEZIERTO pEmr = (PU_EMRPOLYBEZIERTO) lpEMFR; + uint32_t i,j; + + d->mask |= emr_mask; + + for (i=0; i<pEmr->cptl;) { + tmp_path << "\n\tC "; + for (j=0; j<3 && i<pEmr->cptl; j++,i++) { + tmp_path << + pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; + } + } + + break; + } + case U_EMR_POLYLINETO: + { + dbg_str << "<!-- U_EMR_POLYLINETO -->\n"; + + PU_EMRPOLYLINETO pEmr = (PU_EMRPOLYLINETO) lpEMFR; + uint32_t i; + + d->mask |= emr_mask; + + for (i=0; i<pEmr->cptl;i++) { + tmp_path << + "\n\tL " << + pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; + } + + break; + } + case U_EMR_POLYPOLYLINE: + case U_EMR_POLYPOLYGON: + { + if (lpEMFR->iType == U_EMR_POLYPOLYLINE) + dbg_str << "<!-- U_EMR_POLYPOLYLINE -->\n"; + if (lpEMFR->iType == U_EMR_POLYPOLYGON) + dbg_str << "<!-- U_EMR_POLYPOLYGON -->\n"; + + PU_EMRPOLYPOLYGON pEmr = (PU_EMRPOLYPOLYGON) lpEMFR; + unsigned int n, i, j; + + d->mask |= emr_mask; + + U_POINTL *aptl = (PU_POINTL) &pEmr->aPolyCounts[pEmr->nPolys]; + + i = 0; + for (n=0; n<pEmr->nPolys && i<pEmr->cptl; n++) { + SVGOStringStream poly_path; + + poly_path << "\n\tM " << pix_to_xy( d, aptl[i].x, aptl[i].y) << " "; + i++; + + for (j=1; j<pEmr->aPolyCounts[n] && i<pEmr->cptl; j++) { + poly_path << "\n\tL " << pix_to_xy( d, aptl[i].x, aptl[i].y) << " "; + i++; + } + + tmp_str << poly_path.str().c_str(); + if (lpEMFR->iType == U_EMR_POLYPOLYGON) + tmp_str << " z"; + tmp_str << " \n"; + } + + tmp_path << tmp_str.str().c_str(); + + break; + } + case U_EMR_SETWINDOWEXTEX: + { + dbg_str << "<!-- U_EMR_SETWINDOWEXTEX -->\n"; + + PU_EMRSETWINDOWEXTEX pEmr = (PU_EMRSETWINDOWEXTEX) lpEMFR; + + d->dc[d->level].sizeWnd = pEmr->szlExtent; + + if (!d->dc[d->level].sizeWnd.cx || !d->dc[d->level].sizeWnd.cy) { + d->dc[d->level].sizeWnd = d->dc[d->level].sizeView; + if (!d->dc[d->level].sizeWnd.cx || !d->dc[d->level].sizeWnd.cy) { + d->dc[d->level].sizeWnd.cx = d->PixelsOutX; + d->dc[d->level].sizeWnd.cy = d->PixelsOutY; + } + } + + if (!d->dc[d->level].sizeView.cx || !d->dc[d->level].sizeView.cy) { + d->dc[d->level].sizeView = d->dc[d->level].sizeWnd; + } + + /* scales logical to EMF pixels, transfer a negative sign on Y, if any */ + if (d->dc[d->level].sizeWnd.cx && d->dc[d->level].sizeWnd.cy) { + d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].sizeWnd.cx; + d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].sizeWnd.cy; + if(d->dc[d->level].ScaleInY < 0){ + d->dc[d->level].ScaleInY *= -1.0; + d->E2IdirY = -1.0; + } + } + else { + d->dc[d->level].ScaleInX = 1; + d->dc[d->level].ScaleInY = 1; + } + break; + } + case U_EMR_SETWINDOWORGEX: + { + dbg_str << "<!-- U_EMR_SETWINDOWORGEX -->\n"; + + PU_EMRSETWINDOWORGEX pEmr = (PU_EMRSETWINDOWORGEX) lpEMFR; + d->dc[d->level].winorg = pEmr->ptlOrigin; + break; + } + case U_EMR_SETVIEWPORTEXTEX: + { + dbg_str << "<!-- U_EMR_SETVIEWPORTEXTEX -->\n"; + + PU_EMRSETVIEWPORTEXTEX pEmr = (PU_EMRSETVIEWPORTEXTEX) lpEMFR; + + d->dc[d->level].sizeView = pEmr->szlExtent; + + if (!d->dc[d->level].sizeView.cx || !d->dc[d->level].sizeView.cy) { + d->dc[d->level].sizeView = d->dc[d->level].sizeWnd; + if (!d->dc[d->level].sizeView.cx || !d->dc[d->level].sizeView.cy) { + d->dc[d->level].sizeView.cx = d->PixelsOutX; + d->dc[d->level].sizeView.cy = d->PixelsOutY; + } + } + + if (!d->dc[d->level].sizeWnd.cx || !d->dc[d->level].sizeWnd.cy) { + d->dc[d->level].sizeWnd = d->dc[d->level].sizeView; + } + + /* scales logical to EMF pixels, transfer a negative sign on Y, if any */ + if (d->dc[d->level].sizeWnd.cx && d->dc[d->level].sizeWnd.cy) { + d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].sizeWnd.cx; + d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].sizeWnd.cy; + if( d->dc[d->level].ScaleInY < 0){ + d->dc[d->level].ScaleInY *= -1.0; + d->E2IdirY = -1.0; + } + } + else { + d->dc[d->level].ScaleInX = 1; + d->dc[d->level].ScaleInY = 1; + } + break; + } + case U_EMR_SETVIEWPORTORGEX: + { + dbg_str << "<!-- U_EMR_SETVIEWPORTORGEX -->\n"; + + PU_EMRSETVIEWPORTORGEX pEmr = (PU_EMRSETVIEWPORTORGEX) lpEMFR; + d->dc[d->level].vieworg = pEmr->ptlOrigin; + break; + } + case U_EMR_SETBRUSHORGEX: dbg_str << "<!-- U_EMR_SETBRUSHORGEX -->\n"; break; + case U_EMR_EOF: + { + dbg_str << "<!-- U_EMR_EOF -->\n"; + + tmp_outsvg << "</svg>\n"; + d->outsvg = d->outdef + d->defs + d->outsvg; + OK=0; + break; + } + case U_EMR_SETPIXELV: dbg_str << "<!-- U_EMR_SETPIXELV -->\n"; break; + case U_EMR_SETMAPPERFLAGS: dbg_str << "<!-- U_EMR_SETMAPPERFLAGS -->\n"; break; + case U_EMR_SETMAPMODE: + { + dbg_str << "<!-- U_EMR_SETMAPMODE -->\n"; + PU_EMRSETMAPMODE pEmr = (PU_EMRSETMAPMODE) lpEMFR; + switch (pEmr->iMode){ + case U_MM_TEXT: + default: + // Use all values from the header. + break; + /* For all of the following the indicated scale this will be encoded in WindowExtEx/ViewportExtex + and show up in ScaleIn[XY] + */ + case U_MM_LOMETRIC: // 1 LU = 0.1 mm, + case U_MM_HIMETRIC: // 1 LU = 0.01 mm + case U_MM_LOENGLISH: // 1 LU = 0.1 in + case U_MM_HIENGLISH: // 1 LU = 0.01 in + case U_MM_TWIPS: // 1 LU = 1/1440 in + d->E2IdirY = -1.0; + // Use d->D2Pscale[XY] values from the header. + break; + case U_MM_ISOTROPIC: // ScaleIn[XY] should be set elsewhere by SETVIEWPORTEXTEX and SETWINDOWEXTEX + case U_MM_ANISOTROPIC: + break; + } + break; + } + case U_EMR_SETBKMODE: + { + dbg_str << "<!-- U_EMR_SETBKMODE -->\n"; + PU_EMRSETBKMODE pEmr = (PU_EMRSETBKMODE) lpEMFR; + tbkMode = pEmr->iMode; + if(tbkMode != d->dc[d->level].bkMode){ + d->dc[d->level].dirty |= DIRTY_TEXT; + if(tbkMode != d->dc[d->level].bkMode){ + if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; } + if(d->dc[d->level].stroke_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_STROKE; } + } + memcpy(&tbkColor,&(d->dc[d->level].bkColor),sizeof(U_COLORREF)); + } + break; + } + case U_EMR_SETPOLYFILLMODE: + { + dbg_str << "<!-- U_EMR_SETPOLYFILLMODE -->\n"; + + PU_EMRSETPOLYFILLMODE pEmr = (PU_EMRSETPOLYFILLMODE) lpEMFR; + d->dc[d->level].style.fill_rule.value = + (pEmr->iMode == U_ALTERNATE + ? SP_WIND_RULE_NONZERO + : (pEmr->iMode == U_WINDING ? SP_WIND_RULE_INTERSECT : SP_WIND_RULE_NONZERO)); + break; + } + case U_EMR_SETROP2: + { + dbg_str << "<!-- U_EMR_SETROP2 -->\n"; + PU_EMRSETROP2 pEmr = (PU_EMRSETROP2) lpEMFR; + d->dwRop2 = pEmr->iMode; + break; + } + case U_EMR_SETSTRETCHBLTMODE: + { + PU_EMRSETSTRETCHBLTMODE pEmr = (PU_EMRSETSTRETCHBLTMODE) lpEMFR; // from wingdi.h + BLTmode = pEmr->iMode; + dbg_str << "<!-- U_EMR_SETSTRETCHBLTMODE -->\n"; + break; + } + case U_EMR_SETTEXTALIGN: + { + dbg_str << "<!-- U_EMR_SETTEXTALIGN -->\n"; + + PU_EMRSETTEXTALIGN pEmr = (PU_EMRSETTEXTALIGN) lpEMFR; + d->dc[d->level].textAlign = pEmr->iMode; + break; + } + case U_EMR_SETCOLORADJUSTMENT: + dbg_str << "<!-- U_EMR_SETCOLORADJUSTMENT -->\n"; + break; + case U_EMR_SETTEXTCOLOR: + { + dbg_str << "<!-- U_EMR_SETTEXTCOLOR -->\n"; + + PU_EMRSETTEXTCOLOR pEmr = (PU_EMRSETTEXTCOLOR) lpEMFR; + d->dc[d->level].textColor = pEmr->crColor; + if(tbkMode != d->dc[d->level].bkMode){ + if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; } + if(d->dc[d->level].stroke_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_STROKE; } + } + // not text_dirty, because multicolored complex text is supported in libTERE + break; + } + case U_EMR_SETBKCOLOR: + { + dbg_str << "<!-- U_EMR_SETBKCOLOR -->\n"; + + PU_EMRSETBKCOLOR pEmr = (PU_EMRSETBKCOLOR) lpEMFR; + tbkColor = pEmr->crColor; + if(memcmp(&tbkColor, &(d->dc[d->level].bkColor), sizeof(U_COLORREF))){ + d->dc[d->level].dirty |= DIRTY_TEXT; + if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; } + if(d->dc[d->level].stroke_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_STROKE; } + tbkMode = d->dc[d->level].bkMode; + } + break; + } + case U_EMR_OFFSETCLIPRGN: + { + dbg_str << "<!-- U_EMR_OFFSETCLIPRGN -->\n"; + if (d->dc[d->level].clip_id) { // can only offsetan existing clipping path + PU_EMROFFSETCLIPRGN pEmr = (PU_EMROFFSETCLIPRGN) lpEMFR; + U_POINTL off = pEmr->ptlOffset; + unsigned int real_idx = d->dc[d->level].clip_id - 1; + Geom::PathVector tmp_vect = sp_svg_read_pathv(d->clips.strings[real_idx]); + double ox = pix_to_x_point(d, off.x, off.y) - pix_to_x_point(d, 0, 0); // take into account all active transforms + double oy = pix_to_y_point(d, off.x, off.y) - pix_to_y_point(d, 0, 0); + Geom::Affine tf = Geom::Translate(ox,oy); + tmp_vect *= tf; + char *tmp_path = sp_svg_write_path(tmp_vect); + add_clips(d, tmp_path, U_RGN_COPY); + free(tmp_path); + } + break; + } + case U_EMR_MOVETOEX: + { + dbg_str << "<!-- U_EMR_MOVETOEX -->\n"; + + PU_EMRMOVETOEX pEmr = (PU_EMRMOVETOEX) lpEMFR; + + d->mask |= emr_mask; + + d->dc[d->level].cur = pEmr->ptl; + + tmp_path << + "\n\tM " << pix_to_xy( d, pEmr->ptl.x, pEmr->ptl.y ) << " "; + break; + } + case U_EMR_SETMETARGN: dbg_str << "<!-- U_EMR_SETMETARGN -->\n"; break; + case U_EMR_EXCLUDECLIPRECT: + { + dbg_str << "<!-- U_EMR_EXCLUDECLIPRECT -->\n"; + + PU_EMREXCLUDECLIPRECT pEmr = (PU_EMREXCLUDECLIPRECT) lpEMFR; + U_RECTL rc = pEmr->rclClip; + + SVGOStringStream tmp_path; + //outer rect, clockwise + tmp_path << "M " << faraway << "," << faraway << " "; + tmp_path << "L " << faraway << "," << -faraway << " "; + tmp_path << "L " << -faraway << "," << -faraway << " "; + tmp_path << "L " << -faraway << "," << faraway << " "; + tmp_path << "z "; + //inner rect, counterclockwise (sign of Y is reversed) + tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " "; + tmp_path << "z"; + + add_clips(d, tmp_path.str().c_str(), U_RGN_AND); + + d->path = ""; + d->drawtype = 0; + break; + } + case U_EMR_INTERSECTCLIPRECT: + { + dbg_str << "<!-- U_EMR_INTERSECTCLIPRECT -->\n"; + + PU_EMRINTERSECTCLIPRECT pEmr = (PU_EMRINTERSECTCLIPRECT) lpEMFR; + U_RECTL rc = pEmr->rclClip; + + SVGOStringStream tmp_path; + tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " "; + tmp_path << "z"; + + add_clips(d, tmp_path.str().c_str(), U_RGN_AND); + + d->path = ""; + d->drawtype = 0; + break; + } + case U_EMR_SCALEVIEWPORTEXTEX: dbg_str << "<!-- U_EMR_SCALEVIEWPORTEXTEX -->\n"; break; + case U_EMR_SCALEWINDOWEXTEX: dbg_str << "<!-- U_EMR_SCALEWINDOWEXTEX -->\n"; break; + case U_EMR_SAVEDC: + dbg_str << "<!-- U_EMR_SAVEDC -->\n"; + + if (d->level < EMF_MAX_DC) { + d->dc[d->level + 1] = d->dc[d->level]; + if(d->dc[d->level].font_name){ + d->dc[d->level + 1].font_name = strdup(d->dc[d->level].font_name); // or memory access problems because font name pointer duplicated + } + d->level = d->level + 1; + } + break; + case U_EMR_RESTOREDC: + { + dbg_str << "<!-- U_EMR_RESTOREDC -->\n"; + + PU_EMRRESTOREDC pEmr = (PU_EMRRESTOREDC) lpEMFR; + int old_level = d->level; + if (pEmr->iRelative >= 0) { + if (pEmr->iRelative < d->level) + d->level = pEmr->iRelative; + } + else { + if (d->level + pEmr->iRelative >= 0) + d->level = d->level + pEmr->iRelative; + } + while (old_level > d->level) { + if (!d->dc[old_level].style.stroke_dasharray.values.empty() && + (old_level == 0 || (old_level > 0 && d->dc[old_level].style.stroke_dasharray != + d->dc[old_level - 1].style.stroke_dasharray))) { + d->dc[old_level].style.stroke_dasharray.values.clear(); + } + if(d->dc[old_level].font_name){ + free(d->dc[old_level].font_name); // else memory leak + d->dc[old_level].font_name = nullptr; + } + old_level--; + } + break; + } + case U_EMR_SETWORLDTRANSFORM: + { + dbg_str << "<!-- U_EMR_SETWORLDTRANSFORM -->\n"; + + PU_EMRSETWORLDTRANSFORM pEmr = (PU_EMRSETWORLDTRANSFORM) lpEMFR; + d->dc[d->level].worldTransform = pEmr->xform; + break; + } + case U_EMR_MODIFYWORLDTRANSFORM: + { + dbg_str << "<!-- U_EMR_MODIFYWORLDTRANSFORM -->\n"; + + PU_EMRMODIFYWORLDTRANSFORM pEmr = (PU_EMRMODIFYWORLDTRANSFORM) lpEMFR; + switch (pEmr->iMode) + { + case U_MWT_IDENTITY: + d->dc[d->level].worldTransform.eM11 = 1.0; + d->dc[d->level].worldTransform.eM12 = 0.0; + d->dc[d->level].worldTransform.eM21 = 0.0; + d->dc[d->level].worldTransform.eM22 = 1.0; + d->dc[d->level].worldTransform.eDx = 0.0; + d->dc[d->level].worldTransform.eDy = 0.0; + break; + case U_MWT_LEFTMULTIPLY: + { +// d->dc[d->level].worldTransform = pEmr->xform * worldTransform; + + float a11 = pEmr->xform.eM11; + float a12 = pEmr->xform.eM12; + float a13 = 0.0; + float a21 = pEmr->xform.eM21; + float a22 = pEmr->xform.eM22; + float a23 = 0.0; + float a31 = pEmr->xform.eDx; + float a32 = pEmr->xform.eDy; + float a33 = 1.0; + + float b11 = d->dc[d->level].worldTransform.eM11; + float b12 = d->dc[d->level].worldTransform.eM12; + //float b13 = 0.0; + float b21 = d->dc[d->level].worldTransform.eM21; + float b22 = d->dc[d->level].worldTransform.eM22; + //float b23 = 0.0; + float b31 = d->dc[d->level].worldTransform.eDx; + float b32 = d->dc[d->level].worldTransform.eDy; + //float b33 = 1.0; + + float c11 = a11*b11 + a12*b21 + a13*b31;; + float c12 = a11*b12 + a12*b22 + a13*b32;; + //float c13 = a11*b13 + a12*b23 + a13*b33;; + float c21 = a21*b11 + a22*b21 + a23*b31;; + float c22 = a21*b12 + a22*b22 + a23*b32;; + //float c23 = a21*b13 + a22*b23 + a23*b33;; + float c31 = a31*b11 + a32*b21 + a33*b31;; + float c32 = a31*b12 + a32*b22 + a33*b32;; + //float c33 = a31*b13 + a32*b23 + a33*b33;; + + d->dc[d->level].worldTransform.eM11 = c11;; + d->dc[d->level].worldTransform.eM12 = c12;; + d->dc[d->level].worldTransform.eM21 = c21;; + d->dc[d->level].worldTransform.eM22 = c22;; + d->dc[d->level].worldTransform.eDx = c31; + d->dc[d->level].worldTransform.eDy = c32; + + break; + } + case U_MWT_RIGHTMULTIPLY: + { +// d->dc[d->level].worldTransform = worldTransform * pEmr->xform; + + float a11 = d->dc[d->level].worldTransform.eM11; + float a12 = d->dc[d->level].worldTransform.eM12; + float a13 = 0.0; + float a21 = d->dc[d->level].worldTransform.eM21; + float a22 = d->dc[d->level].worldTransform.eM22; + float a23 = 0.0; + float a31 = d->dc[d->level].worldTransform.eDx; + float a32 = d->dc[d->level].worldTransform.eDy; + float a33 = 1.0; + + float b11 = pEmr->xform.eM11; + float b12 = pEmr->xform.eM12; + //float b13 = 0.0; + float b21 = pEmr->xform.eM21; + float b22 = pEmr->xform.eM22; + //float b23 = 0.0; + float b31 = pEmr->xform.eDx; + float b32 = pEmr->xform.eDy; + //float b33 = 1.0; + + float c11 = a11*b11 + a12*b21 + a13*b31;; + float c12 = a11*b12 + a12*b22 + a13*b32;; + //float c13 = a11*b13 + a12*b23 + a13*b33;; + float c21 = a21*b11 + a22*b21 + a23*b31;; + float c22 = a21*b12 + a22*b22 + a23*b32;; + //float c23 = a21*b13 + a22*b23 + a23*b33;; + float c31 = a31*b11 + a32*b21 + a33*b31;; + float c32 = a31*b12 + a32*b22 + a33*b32;; + //float c33 = a31*b13 + a32*b23 + a33*b33;; + + d->dc[d->level].worldTransform.eM11 = c11;; + d->dc[d->level].worldTransform.eM12 = c12;; + d->dc[d->level].worldTransform.eM21 = c21;; + d->dc[d->level].worldTransform.eM22 = c22;; + d->dc[d->level].worldTransform.eDx = c31; + d->dc[d->level].worldTransform.eDy = c32; + + break; + } +// case MWT_SET: + default: + d->dc[d->level].worldTransform = pEmr->xform; + break; + } + break; + } + case U_EMR_SELECTOBJECT: + { + dbg_str << "<!-- U_EMR_SELECTOBJECT -->\n"; + + PU_EMRSELECTOBJECT pEmr = (PU_EMRSELECTOBJECT) lpEMFR; + unsigned int index = pEmr->ihObject; + + if (index & U_STOCK_OBJECT) { + switch (index) { + case U_NULL_BRUSH: + d->dc[d->level].fill_mode = DRAW_PAINT; + d->dc[d->level].fill_set = false; + break; + case U_BLACK_BRUSH: + case U_DKGRAY_BRUSH: + case U_GRAY_BRUSH: + case U_LTGRAY_BRUSH: + case U_WHITE_BRUSH: + { + float val = 0; + switch (index) { + case U_BLACK_BRUSH: + val = 0.0 / 255.0; + break; + case U_DKGRAY_BRUSH: + val = 64.0 / 255.0; + break; + case U_GRAY_BRUSH: + val = 128.0 / 255.0; + break; + case U_LTGRAY_BRUSH: + val = 192.0 / 255.0; + break; + case U_WHITE_BRUSH: + val = 255.0 / 255.0; + break; + } + d->dc[d->level].style.fill.value.color.set( val, val, val ); + + d->dc[d->level].fill_mode = DRAW_PAINT; + d->dc[d->level].fill_set = true; + break; + } + case U_NULL_PEN: + d->dc[d->level].stroke_mode = DRAW_PAINT; + d->dc[d->level].stroke_set = false; + break; + case U_BLACK_PEN: + case U_WHITE_PEN: + { + float val = index == U_BLACK_PEN ? 0 : 1; + d->dc[d->level].style.stroke_dasharray.set = false; + d->dc[d->level].style.stroke_width.value = 1.0; + d->dc[d->level].style.stroke.value.color.set( val, val, val ); + + d->dc[d->level].stroke_mode = DRAW_PAINT; + d->dc[d->level].stroke_set = true; + + break; + } + } + } else { + if ( /*index >= 0 &&*/ index < (unsigned int) d->n_obj) { + switch (d->emf_obj[index].type) + { + case U_EMR_CREATEPEN: + select_pen(d, index); + break; + case U_EMR_CREATEBRUSHINDIRECT: + case U_EMR_CREATEDIBPATTERNBRUSHPT: + case U_EMR_CREATEMONOBRUSH: + select_brush(d, index); + break; + case U_EMR_EXTCREATEPEN: + select_extpen(d, index); + break; + case U_EMR_EXTCREATEFONTINDIRECTW: + select_font(d, index); + break; + } + } + } + break; + } + case U_EMR_CREATEPEN: + { + dbg_str << "<!-- U_EMR_CREATEPEN -->\n"; + + PU_EMRCREATEPEN pEmr = (PU_EMRCREATEPEN) lpEMFR; + insert_object(d, pEmr->ihPen, U_EMR_CREATEPEN, lpEMFR); + break; + } + case U_EMR_CREATEBRUSHINDIRECT: + { + dbg_str << "<!-- U_EMR_CREATEBRUSHINDIRECT -->\n"; + + PU_EMRCREATEBRUSHINDIRECT pEmr = (PU_EMRCREATEBRUSHINDIRECT) lpEMFR; + insert_object(d, pEmr->ihBrush, U_EMR_CREATEBRUSHINDIRECT, lpEMFR); + break; + } + case U_EMR_DELETEOBJECT: + dbg_str << "<!-- U_EMR_DELETEOBJECT -->\n"; + // Objects here are not deleted until the draw completes, new ones may write over an existing one. + break; + case U_EMR_ANGLEARC: + dbg_str << "<!-- U_EMR_ANGLEARC -->\n"; + break; + case U_EMR_ELLIPSE: + { + dbg_str << "<!-- U_EMR_ELLIPSE -->\n"; + + PU_EMRELLIPSE pEmr = (PU_EMRELLIPSE) lpEMFR; + U_RECTL rclBox = pEmr->rclBox; + + double cx = pix_to_x_point( d, (rclBox.left + rclBox.right)/2.0, (rclBox.bottom + rclBox.top)/2.0 ); + double cy = pix_to_y_point( d, (rclBox.left + rclBox.right)/2.0, (rclBox.bottom + rclBox.top)/2.0 ); + double rx = pix_to_abs_size( d, std::abs(rclBox.right - rclBox.left )/2.0 ); + double ry = pix_to_abs_size( d, std::abs(rclBox.top - rclBox.bottom)/2.0 ); + + SVGOStringStream tmp_ellipse; + tmp_ellipse << "cx=\"" << cx << "\" "; + tmp_ellipse << "cy=\"" << cy << "\" "; + tmp_ellipse << "rx=\"" << rx << "\" "; + tmp_ellipse << "ry=\"" << ry << "\" "; + + d->mask |= emr_mask; + + d->outsvg += " <ellipse "; + output_style(d, lpEMFR->iType); // + d->outsvg += "\n\t"; + d->outsvg += tmp_ellipse.str().c_str(); + d->outsvg += "/> \n"; + d->path = ""; + break; + } + case U_EMR_RECTANGLE: + { + dbg_str << "<!-- U_EMR_RECTANGLE -->\n"; + + PU_EMRRECTANGLE pEmr = (PU_EMRRECTANGLE) lpEMFR; + U_RECTL rc = pEmr->rclBox; + + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n\tM " << pix_to_xy( d, rc.left , rc.top ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, rc.right, rc.top ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, rc.right, rc.bottom ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, rc.left, rc.bottom ) << " "; + tmp_rectangle << "\n\tz"; + + d->mask |= emr_mask; + + tmp_path << tmp_rectangle.str().c_str(); + break; + } + case U_EMR_ROUNDRECT: + { + dbg_str << "<!-- U_EMR_ROUNDRECT -->\n"; + + PU_EMRROUNDRECT pEmr = (PU_EMRROUNDRECT) lpEMFR; + U_RECTL rc = pEmr->rclBox; + U_SIZEL corner = pEmr->szlCorner; + double f = 4.*(sqrt(2) - 1)/3; + double f1 = 1.0 - f; + double cnx = corner.cx/2; + double cny = corner.cy/2; + + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n" + << " M " + << pix_to_xy(d, rc.left , rc.top + cny ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, rc.left , rc.top + cny*f1 ) + << " " + << pix_to_xy(d, rc.left + cnx*f1 , rc.top ) + << " " + << pix_to_xy(d, rc.left + cnx , rc.top ) + << "\n"; + tmp_rectangle << " L " + << pix_to_xy(d, rc.right - cnx , rc.top ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, rc.right - cnx*f1 , rc.top ) + << " " + << pix_to_xy(d, rc.right , rc.top + cny*f1 ) + << " " + << pix_to_xy(d, rc.right , rc.top + cny ) + << "\n"; + tmp_rectangle << " L " + << pix_to_xy(d, rc.right , rc.bottom - cny ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, rc.right , rc.bottom - cny*f1 ) + << " " + << pix_to_xy(d, rc.right - cnx*f1 , rc.bottom ) + << " " + << pix_to_xy(d, rc.right - cnx , rc.bottom ) + << "\n"; + tmp_rectangle << " L " + << pix_to_xy(d, rc.left + cnx , rc.bottom ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, rc.left + cnx*f1 , rc.bottom ) + << " " + << pix_to_xy(d, rc.left , rc.bottom - cny*f1 ) + << " " + << pix_to_xy(d, rc.left , rc.bottom - cny ) + << "\n"; + tmp_rectangle << " z\n"; + + + d->mask |= emr_mask; + + tmp_path << tmp_rectangle.str().c_str(); + break; + } + case U_EMR_ARC: + { + dbg_str << "<!-- U_EMR_ARC -->\n"; + U_PAIRF center,start,end,size; + int f1; + int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1); + int stat = emr_arc_points( lpEMFR, &f1, f2, ¢er, &start, &end, &size); + if(!stat){ + tmp_path << "\n\tM " << pix_to_xy(d, start.x, start.y); + tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0; + tmp_path << " "; + tmp_path << 180.0 * current_rotation(d)/M_PI; + tmp_path << " "; + tmp_path << " " << f1 << "," << f2 << " "; + tmp_path << pix_to_xy(d, end.x, end.y) << " \n"; + d->mask |= emr_mask; + } + else { + dbg_str << "<!-- ARC record is invalid -->\n"; + } + break; + } + case U_EMR_CHORD: + { + dbg_str << "<!-- U_EMR_CHORD -->\n"; + U_PAIRF center,start,end,size; + int f1; + int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1); + if(!emr_arc_points( lpEMFR, &f1, f2, ¢er, &start, &end, &size)){ + tmp_path << "\n\tM " << pix_to_xy(d, start.x, start.y); + tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0; + tmp_path << " "; + tmp_path << 180.0 * current_rotation(d)/M_PI; + tmp_path << " "; + tmp_path << " " << f1 << "," << f2 << " "; + tmp_path << pix_to_xy(d, end.x, end.y) << " \n"; + tmp_path << " z "; + d->mask |= emr_mask; + } + else { + dbg_str << "<!-- CHORD record is invalid -->\n"; + } + break; + } + case U_EMR_PIE: + { + dbg_str << "<!-- U_EMR_PIE -->\n"; + U_PAIRF center,start,end,size; + int f1; + int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1); + if(!emr_arc_points( lpEMFR, &f1, f2, ¢er, &start, &end, &size)){ + tmp_path << "\n\tM " << pix_to_xy(d, center.x, center.y); + tmp_path << "\n\tL " << pix_to_xy(d, start.x, start.y); + tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0; + tmp_path << " "; + tmp_path << 180.0 * current_rotation(d)/M_PI; + tmp_path << " "; + tmp_path << " " << f1 << "," << f2 << " "; + tmp_path << pix_to_xy(d, end.x, end.y) << " \n"; + tmp_path << " z "; + d->mask |= emr_mask; + } + else { + dbg_str << "<!-- PIE record is invalid -->\n"; + } + break; + } + case U_EMR_SELECTPALETTE: dbg_str << "<!-- U_EMR_SELECTPALETTE -->\n"; break; + case U_EMR_CREATEPALETTE: dbg_str << "<!-- U_EMR_CREATEPALETTE -->\n"; break; + case U_EMR_SETPALETTEENTRIES: dbg_str << "<!-- U_EMR_SETPALETTEENTRIES -->\n"; break; + case U_EMR_RESIZEPALETTE: dbg_str << "<!-- U_EMR_RESIZEPALETTE -->\n"; break; + case U_EMR_REALIZEPALETTE: dbg_str << "<!-- U_EMR_REALIZEPALETTE -->\n"; break; + case U_EMR_EXTFLOODFILL: dbg_str << "<!-- U_EMR_EXTFLOODFILL -->\n"; break; + case U_EMR_LINETO: + { + dbg_str << "<!-- U_EMR_LINETO -->\n"; + + PU_EMRLINETO pEmr = (PU_EMRLINETO) lpEMFR; + + d->mask |= emr_mask; + + tmp_path << + "\n\tL " << pix_to_xy( d, pEmr->ptl.x, pEmr->ptl.y) << " "; + break; + } + case U_EMR_ARCTO: + { + dbg_str << "<!-- U_EMR_ARCTO -->\n"; + U_PAIRF center,start,end,size; + int f1; + int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1); + if(!emr_arc_points( lpEMFR, &f1, f2, ¢er, &start, &end, &size)){ + // draw a line from current position to start, arc from there + tmp_path << "\n\tL " << pix_to_xy(d, start.x, start.y); + tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0; + tmp_path << " "; + tmp_path << 180.0 * current_rotation(d)/M_PI; + tmp_path << " "; + tmp_path << " " << f1 << "," << f2 << " "; + tmp_path << pix_to_xy(d, end.x, end.y)<< " "; + + d->mask |= emr_mask; + } + else { + dbg_str << "<!-- ARCTO record is invalid -->\n"; + } + break; + } + case U_EMR_POLYDRAW: dbg_str << "<!-- U_EMR_POLYDRAW -->\n"; break; + case U_EMR_SETARCDIRECTION: + { + dbg_str << "<!-- U_EMR_SETARCDIRECTION -->\n"; + PU_EMRSETARCDIRECTION pEmr = (PU_EMRSETARCDIRECTION) lpEMFR; + if(d->arcdir == U_AD_CLOCKWISE || d->arcdir == U_AD_COUNTERCLOCKWISE){ // EMF file could be corrupt + d->arcdir = pEmr->iArcDirection; + } + break; + } + case U_EMR_SETMITERLIMIT: + { + dbg_str << "<!-- U_EMR_SETMITERLIMIT -->\n"; + + PU_EMRSETMITERLIMIT pEmr = (PU_EMRSETMITERLIMIT) lpEMFR; + + //The function takes a float but saves a 32 bit int in the U_EMR_SETMITERLIMIT record. + float miterlimit = *((int32_t *) &(pEmr->eMiterLimit)); + d->dc[d->level].style.stroke_miterlimit.value = miterlimit; //ratio, not a pt size + if (d->dc[d->level].style.stroke_miterlimit.value < 2) + d->dc[d->level].style.stroke_miterlimit.value = 2.0; + break; + } + case U_EMR_BEGINPATH: + { + dbg_str << "<!-- U_EMR_BEGINPATH -->\n"; + // The next line should never be needed, should have been handled before main switch + // qualifier added because EMF's encountered where moveto preceded beginpath followed by lineto + if(d->mask & U_DRAW_VISIBLE){ + d->path = ""; + } + d->mask |= emr_mask; + break; + } + case U_EMR_ENDPATH: + { + dbg_str << "<!-- U_EMR_ENDPATH -->\n"; + d->mask &= (0xFFFFFFFF - U_DRAW_ONLYTO); // clear the OnlyTo bit (it might not have been set), prevents any further path extension + break; + } + case U_EMR_CLOSEFIGURE: + { + dbg_str << "<!-- U_EMR_CLOSEFIGURE -->\n"; + // EMF may contain multiple closefigures on one path + tmp_path << "\n\tz"; + d->mask |= U_DRAW_CLOSED; + break; + } + case U_EMR_FILLPATH: + { + dbg_str << "<!-- U_EMR_FILLPATH -->\n"; + if(d->mask & U_DRAW_PATH){ // Operation only effects declared paths + if(!(d->mask & U_DRAW_CLOSED)){ // Close a path not explicitly closed by an EMRCLOSEFIGURE, otherwise fill makes no sense + tmp_path << "\n\tz"; + d->mask |= U_DRAW_CLOSED; + } + d->mask |= emr_mask; + d->drawtype = U_EMR_FILLPATH; + } + break; + } + case U_EMR_STROKEANDFILLPATH: + { + dbg_str << "<!-- U_EMR_STROKEANDFILLPATH -->\n"; + if(d->mask & U_DRAW_PATH){ // Operation only effects declared paths + if(!(d->mask & U_DRAW_CLOSED)){ // Close a path not explicitly closed by an EMRCLOSEFIGURE, otherwise fill makes no sense + tmp_path << "\n\tz"; + d->mask |= U_DRAW_CLOSED; + } + d->mask |= emr_mask; + d->drawtype = U_EMR_STROKEANDFILLPATH; + } + break; + } + case U_EMR_STROKEPATH: + { + dbg_str << "<!-- U_EMR_STROKEPATH -->\n"; + if(d->mask & U_DRAW_PATH){ // Operation only effects declared paths + d->mask |= emr_mask; + d->drawtype = U_EMR_STROKEPATH; + } + break; + } + case U_EMR_FLATTENPATH: dbg_str << "<!-- U_EMR_FLATTENPATH -->\n"; break; + case U_EMR_WIDENPATH: dbg_str << "<!-- U_EMR_WIDENPATH -->\n"; break; + case U_EMR_SELECTCLIPPATH: + { + dbg_str << "<!-- U_EMR_SELECTCLIPPATH -->\n"; + PU_EMRSELECTCLIPPATH pEmr = (PU_EMRSELECTCLIPPATH) lpEMFR; + int logic = pEmr->iMode; + + if ((logic < U_RGN_MIN) || (logic > U_RGN_MAX)){ break; } + add_clips(d, d->path.c_str(), logic); // finds an existing one or stores this, sets clip_id + d->path = ""; + d->drawtype = 0; + break; + } + case U_EMR_ABORTPATH: + { + dbg_str << "<!-- U_EMR_ABORTPATH -->\n"; + d->path = ""; + d->drawtype = 0; + break; + } + case U_EMR_UNDEF69: dbg_str << "<!-- U_EMR_UNDEF69 -->\n"; break; + case U_EMR_COMMENT: + { + dbg_str << "<!-- U_EMR_COMMENT -->\n"; + + PU_EMRCOMMENT pEmr = (PU_EMRCOMMENT) lpEMFR; + + char *szTxt = (char *) pEmr->Data; + + for (uint32_t i = 0; i < pEmr->cbData; i++) { + if ( *szTxt) { + if ( *szTxt >= ' ' && *szTxt < 'z' && *szTxt != '<' && *szTxt != '>' ) { + tmp_str << *szTxt; + } + szTxt++; + } + } + + if (false && strlen(tmp_str.str().c_str())) { + tmp_outsvg << " <!-- \""; + tmp_outsvg << tmp_str.str().c_str(); + tmp_outsvg << "\" -->\n"; + } + + break; + } + case U_EMR_FILLRGN: dbg_str << "<!-- U_EMR_FILLRGN -->\n"; break; + case U_EMR_FRAMERGN: dbg_str << "<!-- U_EMR_FRAMERGN -->\n"; break; + case U_EMR_INVERTRGN: dbg_str << "<!-- U_EMR_INVERTRGN -->\n"; break; + case U_EMR_PAINTRGN: dbg_str << "<!-- U_EMR_PAINTRGN -->\n"; break; + case U_EMR_EXTSELECTCLIPRGN: + { + dbg_str << "<!-- U_EMR_EXTSELECTCLIPRGN -->\n"; + + PU_EMREXTSELECTCLIPRGN pEmr = (PU_EMREXTSELECTCLIPRGN) lpEMFR; + // the only mode we implement - this clears the clipping region + if (pEmr->iMode == U_RGN_COPY) { + d->dc[d->level].clip_id = 0; + } + break; + } + case U_EMR_BITBLT: + { + dbg_str << "<!-- U_EMR_BITBLT -->\n"; + + PU_EMRBITBLT pEmr = (PU_EMRBITBLT) lpEMFR; + // Treat all nonImage bitblts as a rectangular write. Definitely not correct, but at + // least it leaves objects where the operations should have been. + if (!pEmr->cbBmiSrc) { + // should be an application of a DIBPATTERNBRUSHPT, use a solid color instead + + if(pEmr->dwRop == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */ + int32_t dx = pEmr->Dest.x; + int32_t dy = pEmr->Dest.y; + int32_t dw = pEmr->cDest.x; + int32_t dh = pEmr->cDest.y; + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " "; + tmp_rectangle << "\n\tz"; + + d->mask |= emr_mask; + d->dwRop3 = pEmr->dwRop; // we will try to approximate SOME of these + d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that + + tmp_path << tmp_rectangle.str().c_str(); + } + else { + double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dw = pix_to_abs_size( d, pEmr->cDest.x); + double dh = pix_to_abs_size( d, pEmr->cDest.y); + //source position within the bitmap, in pixels + int sx = pEmr->Src.x + pEmr->xformSrc.eDx; + int sy = pEmr->Src.y + pEmr->xformSrc.eDy; + int sw = 0; // extract all of the image + int sh = 0; + if(sx<0)sx=0; + if(sy<0)sy=0; + common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh, + pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); + } + break; + } + case U_EMR_STRETCHBLT: + { + dbg_str << "<!-- U_EMR_STRETCHBLT -->\n"; + PU_EMRSTRETCHBLT pEmr = (PU_EMRSTRETCHBLT) lpEMFR; + // Always grab image, ignore modes. + if (pEmr->cbBmiSrc) { + double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dw = pix_to_abs_size( d, pEmr->cDest.x); + double dh = pix_to_abs_size( d, pEmr->cDest.y); + //source position within the bitmap, in pixels + int sx = pEmr->Src.x + pEmr->xformSrc.eDx; + int sy = pEmr->Src.y + pEmr->xformSrc.eDy; + int sw = pEmr->cSrc.x; // extract the specified amount of the image + int sh = pEmr->cSrc.y; + common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh, + pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); + } + break; + } + case U_EMR_MASKBLT: + { + dbg_str << "<!-- U_EMR_MASKBLT -->\n"; + PU_EMRMASKBLT pEmr = (PU_EMRMASKBLT) lpEMFR; + // Always grab image, ignore masks and modes. + if (pEmr->cbBmiSrc) { + double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y); + double dw = pix_to_abs_size( d, pEmr->cDest.x); + double dh = pix_to_abs_size( d, pEmr->cDest.y); + int sx = pEmr->Src.x + pEmr->xformSrc.eDx; //source position within the bitmap, in pixels + int sy = pEmr->Src.y + pEmr->xformSrc.eDy; + int sw = 0; // extract all of the image + int sh = 0; + common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh, + pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); + } + break; + } + case U_EMR_PLGBLT: dbg_str << "<!-- U_EMR_PLGBLT -->\n"; break; + case U_EMR_SETDIBITSTODEVICE: dbg_str << "<!-- U_EMR_SETDIBITSTODEVICE -->\n"; break; + case U_EMR_STRETCHDIBITS: + { + // Some applications use multiple EMF operations, including multiple STRETCHDIBITS to create + // images with transparent regions. PowerPoint does this with rotated images, for instance. + // Parsing all of that to derive a single resultant image object is left for a later version + // of this code. In the meantime, every STRETCHDIBITS goes directly to an image. The Inkscape + // user can sort out transparency later using Gimp, if need be. + + PU_EMRSTRETCHDIBITS pEmr = (PU_EMRSTRETCHDIBITS) lpEMFR; + double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y ); + double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y ); + double dw = pix_to_abs_size( d, pEmr->cDest.x); + double dh = pix_to_abs_size( d, pEmr->cDest.y); + int sx = pEmr->Src.x; //source position within the bitmap, in pixels + int sy = pEmr->Src.y; + int sw = pEmr->cSrc.x; // extract the specified amount of the image + int sh = pEmr->cSrc.y; + common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh, + pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc); + + dbg_str << "<!-- U_EMR_STRETCHDIBITS -->\n"; + break; + } + case U_EMR_EXTCREATEFONTINDIRECTW: + { + dbg_str << "<!-- U_EMR_EXTCREATEFONTINDIRECTW -->\n"; + + PU_EMREXTCREATEFONTINDIRECTW pEmr = (PU_EMREXTCREATEFONTINDIRECTW) lpEMFR; + insert_object(d, pEmr->ihFont, U_EMR_EXTCREATEFONTINDIRECTW, lpEMFR); + break; + } + case U_EMR_EXTTEXTOUTA: + case U_EMR_EXTTEXTOUTW: + case U_EMR_SMALLTEXTOUT: + { + dbg_str << "<!-- U_EMR_EXTTEXTOUTA/W -->\n"; + + PU_EMREXTTEXTOUTW pEmr = (PU_EMREXTTEXTOUTW) lpEMFR; + PU_EMRSMALLTEXTOUT pEmrS = (PU_EMRSMALLTEXTOUT) lpEMFR; + + double x1,y1; + int roff = sizeof(U_EMRSMALLTEXTOUT); //offset to the start of the variable fields, only used with U_EMR_SMALLTEXTOUT + int cChars; + if(lpEMFR->iType==U_EMR_SMALLTEXTOUT){ + x1 = pEmrS->Dest.x; + y1 = pEmrS->Dest.y; + cChars = pEmrS->cChars; + if(!(pEmrS->fuOptions & U_ETO_NO_RECT)){ roff += sizeof(U_RECTL); } + } + else { + x1 = pEmr->emrtext.ptlReference.x; + y1 = pEmr->emrtext.ptlReference.y; + cChars = 0; + } + uint32_t fOptions = pEmr->emrtext.fOptions; + + if (d->dc[d->level].textAlign & U_TA_UPDATECP) { + x1 = d->dc[d->level].cur.x; + y1 = d->dc[d->level].cur.y; + } + + double x = pix_to_x_point(d, x1, y1); + double y = pix_to_y_point(d, x1, y1); + + /* Rotation issues are handled entirely in libTERE now */ + + uint32_t *dup_wt = nullptr; + + if( lpEMFR->iType==U_EMR_EXTTEXTOUTA){ + /* These should be JUST ASCII, but they might not be... + If it holds Utf-8 or plain ASCII the first call will succeed. + If not, assume that it holds Latin1. + If that fails then something is really screwed up! + */ + dup_wt = U_Utf8ToUtf32le((char *) pEmr + pEmr->emrtext.offString, pEmr->emrtext.nChars, nullptr); + if(!dup_wt)dup_wt = U_Latin1ToUtf32le((char *) pEmr + pEmr->emrtext.offString, pEmr->emrtext.nChars, nullptr); + if(!dup_wt)dup_wt = unknown_chars(pEmr->emrtext.nChars); + } + else if( lpEMFR->iType==U_EMR_EXTTEXTOUTW){ + dup_wt = U_Utf16leToUtf32le((uint16_t *)((char *) pEmr + pEmr->emrtext.offString), pEmr->emrtext.nChars, nullptr); + if(!dup_wt)dup_wt = unknown_chars(pEmr->emrtext.nChars); + } + else { // U_EMR_SMALLTEXTOUT + if(pEmrS->fuOptions & U_ETO_SMALL_CHARS){ + dup_wt = U_Utf8ToUtf32le((char *) pEmrS + roff, cChars, nullptr); + } + else { + dup_wt = U_Utf16leToUtf32le((uint16_t *)((char *) pEmrS + roff), cChars, nullptr); + } + if(!dup_wt)dup_wt = unknown_chars(cChars); + } + + msdepua(dup_wt); //convert everything in Microsoft's private use area. For Symbol, Wingdings, Dingbats + + if(NonToUnicode(dup_wt, d->dc[d->level].font_name)){ + free(d->dc[d->level].font_name); + d->dc[d->level].font_name = strdup("Times New Roman"); + } + + char *ansi_text; + ansi_text = (char *) U_Utf32leToUtf8((uint32_t *)dup_wt, 0, nullptr); + free(dup_wt); + // Empty string or starts with an invalid escape/control sequence, which is bogus text. Throw it out before g_markup_escape_text can make things worse + if(*((uint8_t *)ansi_text) <= 0x1F){ + free(ansi_text); + ansi_text=nullptr; + } + + if (ansi_text) { + + SVGOStringStream ts; + + gchar *escaped_text = g_markup_escape_text(ansi_text, -1); + + tsp.x = x*0.8; // TERE expects sizes in points. + tsp.y = y*0.8; + tsp.color.Red = d->dc[d->level].textColor.Red; + tsp.color.Green = d->dc[d->level].textColor.Green; + tsp.color.Blue = d->dc[d->level].textColor.Blue; + tsp.color.Reserved = 0; + switch(d->dc[d->level].style.font_style.value){ + case SP_CSS_FONT_STYLE_OBLIQUE: + tsp.italics = FC_SLANT_OBLIQUE; break; + case SP_CSS_FONT_STYLE_ITALIC: + tsp.italics = FC_SLANT_ITALIC; break; + default: + case SP_CSS_FONT_STYLE_NORMAL: + tsp.italics = FC_SLANT_ROMAN; break; + } + switch(d->dc[d->level].style.font_weight.value){ + case SP_CSS_FONT_WEIGHT_100: tsp.weight = FC_WEIGHT_THIN ; break; + case SP_CSS_FONT_WEIGHT_200: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break; + case SP_CSS_FONT_WEIGHT_300: tsp.weight = FC_WEIGHT_LIGHT ; break; + case SP_CSS_FONT_WEIGHT_400: tsp.weight = FC_WEIGHT_NORMAL ; break; + case SP_CSS_FONT_WEIGHT_500: tsp.weight = FC_WEIGHT_MEDIUM ; break; + case SP_CSS_FONT_WEIGHT_600: tsp.weight = FC_WEIGHT_SEMIBOLD ; break; + case SP_CSS_FONT_WEIGHT_700: tsp.weight = FC_WEIGHT_BOLD ; break; + case SP_CSS_FONT_WEIGHT_800: tsp.weight = FC_WEIGHT_EXTRABOLD ; break; + case SP_CSS_FONT_WEIGHT_900: tsp.weight = FC_WEIGHT_HEAVY ; break; + case SP_CSS_FONT_WEIGHT_NORMAL: tsp.weight = FC_WEIGHT_NORMAL ; break; + case SP_CSS_FONT_WEIGHT_BOLD: tsp.weight = FC_WEIGHT_BOLD ; break; + case SP_CSS_FONT_WEIGHT_LIGHTER: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break; + case SP_CSS_FONT_WEIGHT_BOLDER: tsp.weight = FC_WEIGHT_EXTRABOLD ; break; + default: tsp.weight = FC_WEIGHT_NORMAL ; break; + } + // EMF only supports two types of text decoration + tsp.decoration = TXTDECOR_NONE; + if(d->dc[d->level].style.text_decoration_line.underline){ tsp.decoration |= TXTDECOR_UNDER; } + if(d->dc[d->level].style.text_decoration_line.line_through){ tsp.decoration |= TXTDECOR_STRIKE;} + + // EMF textalignment is a bit strange: 0x6 is center, 0x2 is right, 0x0 is left, the value 0x4 is also drawn left + tsp.taln = ((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_CENTER) ? ALICENTER : + (((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_LEFT) ? ALILEFT : + ALIRIGHT); + tsp.taln |= ((d->dc[d->level].textAlign & U_TA_BASEBIT) ? ALIBASE : + ((d->dc[d->level].textAlign & U_TA_BOTTOM) ? ALIBOT : + ALITOP)); + + // language direction can be encoded two ways, U_TA_RTLREADING is preferred + if( (fOptions & U_ETO_RTLREADING) || (d->dc[d->level].textAlign & U_TA_RTLREADING) ){ tsp.ldir = LDIR_RL; } + else{ tsp.ldir = LDIR_LR; } + + tsp.condensed = FC_WIDTH_NORMAL; // Not implemented well in libTERE (yet) + tsp.ori = d->dc[d->level].style.baseline_shift.value; // For now orientation is always the same as escapement + tsp.ori += 180.0 * current_rotation(d)/ M_PI; // radians to degrees + tsp.string = (uint8_t *) U_strdup(escaped_text); // this will be free'd much later at a trinfo_clear(). + tsp.fs = d->dc[d->level].style.font_size.computed * 0.8; // Font size in points + char *fontspec = TR_construct_fontspec(&tsp, d->dc[d->level].font_name); + tsp.fi_idx = ftinfo_load_fontname(d->tri->fti,fontspec); + free(fontspec); + // when font name includes narrow it may not be set to "condensed". Narrow fonts do not work well anyway though + // as the metrics from fontconfig may not match, or the font may not be present. + if(0<= TR_findcasesub(d->dc[d->level].font_name, (char *) "Narrow")){ tsp.co=1; } + else { tsp.co=0; } + + int status = trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ori is actually escapement + if(status==-1){ // change of escapement, emit what we have and reset + TR_layout_analyze(d->tri); + if (d->dc[d->level].clip_id){ + SVGOStringStream tmp_clip; + tmp_clip << "\n<g\n\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n>"; + d->outsvg += tmp_clip.str().c_str(); + } + TR_layout_2_svg(d->tri); + ts << d->tri->out; + d->outsvg += ts.str().c_str(); + d->tri = trinfo_clear(d->tri); + (void) trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ignore return status, it must work + if (d->dc[d->level].clip_id){ + d->outsvg += "\n</g>\n"; + } + } + + g_free(escaped_text); + free(ansi_text); + } + + break; + } + case U_EMR_POLYBEZIER16: + { + dbg_str << "<!-- U_EMR_POLYBEZIER16 -->\n"; + + PU_EMRPOLYBEZIER16 pEmr = (PU_EMRPOLYBEZIER16) lpEMFR; + PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ? + uint32_t i,j; + + if (pEmr->cpts<4) + break; + + d->mask |= emr_mask; + + tmp_str << "\n\tM " << pix_to_xy( d, apts[0].x, apts[0].y ) << " "; + + for (i=1; i<pEmr->cpts; ) { + tmp_str << "\n\tC "; + for (j=0; j<3 && i<pEmr->cpts; j++,i++) { + tmp_str << pix_to_xy( d, apts[i].x, apts[i].y ) << " "; + } + } + + tmp_path << tmp_str.str().c_str(); + + break; + } + case U_EMR_POLYGON16: + { + dbg_str << "<!-- U_EMR_POLYGON16 -->\n"; + + PU_EMRPOLYGON16 pEmr = (PU_EMRPOLYGON16) lpEMFR; + PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ? + SVGOStringStream tmp_poly; + unsigned int i; + unsigned int first = 0; + + d->mask |= emr_mask; + + // skip the first point? + tmp_poly << "\n\tM " << pix_to_xy( d, apts[first].x, apts[first].y ) << " "; + + for (i=first+1; i<pEmr->cpts; i++) { + tmp_poly << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y ) << " "; + } + + tmp_path << tmp_poly.str().c_str(); + tmp_path << "\n\tz"; + d->mask |= U_DRAW_CLOSED; + + break; + } + case U_EMR_POLYLINE16: + { + dbg_str << "<!-- U_EMR_POLYLINE16 -->\n"; + + PU_EMRPOLYLINE16 pEmr = (PU_EMRPOLYLINE16) lpEMFR; + PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ? + uint32_t i; + + if (pEmr->cpts<2) + break; + + d->mask |= emr_mask; + + tmp_str << "\n\tM " << pix_to_xy( d, apts[0].x, apts[0].y ) << " "; + + for (i=1; i<pEmr->cpts; i++) { + tmp_str << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y ) << " "; + } + + tmp_path << tmp_str.str().c_str(); + + break; + } + case U_EMR_POLYBEZIERTO16: + { + dbg_str << "<!-- U_EMR_POLYBEZIERTO16 -->\n"; + + PU_EMRPOLYBEZIERTO16 pEmr = (PU_EMRPOLYBEZIERTO16) lpEMFR; + PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ? + uint32_t i,j; + + d->mask |= emr_mask; + + for (i=0; i<pEmr->cpts;) { + tmp_path << "\n\tC "; + for (j=0; j<3 && i<pEmr->cpts; j++,i++) { + tmp_path << pix_to_xy( d, apts[i].x, apts[i].y) << " "; + } + } + + break; + } + case U_EMR_POLYLINETO16: + { + dbg_str << "<!-- U_EMR_POLYLINETO16 -->\n"; + + PU_EMRPOLYLINETO16 pEmr = (PU_EMRPOLYLINETO16) lpEMFR; + PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ? + uint32_t i; + + d->mask |= emr_mask; + + for (i=0; i<pEmr->cpts;i++) { + tmp_path << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y) << " "; + } + + break; + } + case U_EMR_POLYPOLYLINE16: + case U_EMR_POLYPOLYGON16: + { + if (lpEMFR->iType == U_EMR_POLYPOLYLINE16) + dbg_str << "<!-- U_EMR_POLYPOLYLINE16 -->\n"; + if (lpEMFR->iType == U_EMR_POLYPOLYGON16) + dbg_str << "<!-- U_EMR_POLYPOLYGON16 -->\n"; + + PU_EMRPOLYPOLYGON16 pEmr = (PU_EMRPOLYPOLYGON16) lpEMFR; + unsigned int n, i, j; + + d->mask |= emr_mask; + + PU_POINT16 apts = (PU_POINT16) &pEmr->aPolyCounts[pEmr->nPolys]; + + i = 0; + for (n=0; n<pEmr->nPolys && i<pEmr->cpts; n++) { + SVGOStringStream poly_path; + + poly_path << "\n\tM " << pix_to_xy( d, apts[i].x, apts[i].y) << " "; + i++; + + for (j=1; j<pEmr->aPolyCounts[n] && i<pEmr->cpts; j++) { + poly_path << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y) << " "; + i++; + } + + tmp_str << poly_path.str().c_str(); + if (lpEMFR->iType == U_EMR_POLYPOLYGON16) + tmp_str << " z"; + tmp_str << " \n"; + } + + tmp_path << tmp_str.str().c_str(); + + break; + } + case U_EMR_POLYDRAW16: dbg_str << "<!-- U_EMR_POLYDRAW16 -->\n"; break; + case U_EMR_CREATEMONOBRUSH: + { + dbg_str << "<!-- U_EMR_CREATEDIBPATTERNBRUSHPT -->\n"; + + PU_EMRCREATEMONOBRUSH pEmr = (PU_EMRCREATEMONOBRUSH) lpEMFR; + insert_object(d, pEmr->ihBrush, U_EMR_CREATEMONOBRUSH, lpEMFR); + break; + } + case U_EMR_CREATEDIBPATTERNBRUSHPT: + { + dbg_str << "<!-- U_EMR_CREATEDIBPATTERNBRUSHPT -->\n"; + + PU_EMRCREATEDIBPATTERNBRUSHPT pEmr = (PU_EMRCREATEDIBPATTERNBRUSHPT) lpEMFR; + insert_object(d, pEmr->ihBrush, U_EMR_CREATEDIBPATTERNBRUSHPT, lpEMFR); + break; + } + case U_EMR_EXTCREATEPEN: + { + dbg_str << "<!-- U_EMR_EXTCREATEPEN -->\n"; + + PU_EMREXTCREATEPEN pEmr = (PU_EMREXTCREATEPEN) lpEMFR; + insert_object(d, pEmr->ihPen, U_EMR_EXTCREATEPEN, lpEMFR); + break; + } + case U_EMR_POLYTEXTOUTA: dbg_str << "<!-- U_EMR_POLYTEXTOUTA -->\n"; break; + case U_EMR_POLYTEXTOUTW: dbg_str << "<!-- U_EMR_POLYTEXTOUTW -->\n"; break; + case U_EMR_SETICMMODE: + { + dbg_str << "<!-- U_EMR_SETICMMODE -->\n"; + PU_EMRSETICMMODE pEmr = (PU_EMRSETICMMODE) lpEMFR; + ICMmode= pEmr->iMode; + break; + } + case U_EMR_CREATECOLORSPACE: dbg_str << "<!-- U_EMR_CREATECOLORSPACE -->\n"; break; + case U_EMR_SETCOLORSPACE: dbg_str << "<!-- U_EMR_SETCOLORSPACE -->\n"; break; + case U_EMR_DELETECOLORSPACE: dbg_str << "<!-- U_EMR_DELETECOLORSPACE -->\n"; break; + case U_EMR_GLSRECORD: dbg_str << "<!-- U_EMR_GLSRECORD -->\n"; break; + case U_EMR_GLSBOUNDEDRECORD: dbg_str << "<!-- U_EMR_GLSBOUNDEDRECORD -->\n"; break; + case U_EMR_PIXELFORMAT: dbg_str << "<!-- U_EMR_PIXELFORMAT -->\n"; break; + case U_EMR_DRAWESCAPE: dbg_str << "<!-- U_EMR_DRAWESCAPE -->\n"; break; + case U_EMR_EXTESCAPE: dbg_str << "<!-- U_EMR_EXTESCAPE -->\n"; break; + case U_EMR_UNDEF107: dbg_str << "<!-- U_EMR_UNDEF107 -->\n"; break; + // U_EMR_SMALLTEXTOUT is handled with U_EMR_EXTTEXTOUTA/W above + case U_EMR_FORCEUFIMAPPING: dbg_str << "<!-- U_EMR_FORCEUFIMAPPING -->\n"; break; + case U_EMR_NAMEDESCAPE: dbg_str << "<!-- U_EMR_NAMEDESCAPE -->\n"; break; + case U_EMR_COLORCORRECTPALETTE: dbg_str << "<!-- U_EMR_COLORCORRECTPALETTE -->\n"; break; + case U_EMR_SETICMPROFILEA: dbg_str << "<!-- U_EMR_SETICMPROFILEA -->\n"; break; + case U_EMR_SETICMPROFILEW: dbg_str << "<!-- U_EMR_SETICMPROFILEW -->\n"; break; + case U_EMR_ALPHABLEND: dbg_str << "<!-- U_EMR_ALPHABLEND -->\n"; break; + case U_EMR_SETLAYOUT: dbg_str << "<!-- U_EMR_SETLAYOUT -->\n"; break; + case U_EMR_TRANSPARENTBLT: dbg_str << "<!-- U_EMR_TRANSPARENTBLT -->\n"; break; + case U_EMR_UNDEF117: dbg_str << "<!-- U_EMR_UNDEF117 -->\n"; break; + case U_EMR_GRADIENTFILL: + { + /* Gradient fill is doable for rectangles because those correspond to linear gradients. However, + the general case for the triangle fill, with a different color in each corner of the triangle, + has no SVG equivalent and cannot be easily emulated with SVG gradients. So the linear gradient + is implemented, and the triangle fill just paints with the color of the first corner. + + This record can hold a series of gradients so we are forced to add path elements directly here, + it cannot wait for the top of the main loop. Any existing path is erased. + + */ + dbg_str << "<!-- U_EMR_GRADIENTFILL -->\n"; + PU_EMRGRADIENTFILL pEmr = (PU_EMRGRADIENTFILL) lpEMFR; + int nV = pEmr->nTriVert; // Number of TriVertex objects + int nG = pEmr->nGradObj; // Number of gradient triangle/rectangle objects + U_TRIVERTEX *tv = (U_TRIVERTEX *)(((char *)lpEMFR) + sizeof(U_EMRGRADIENTFILL)); + if( pEmr->ulMode == U_GRADIENT_FILL_RECT_H || + pEmr->ulMode == U_GRADIENT_FILL_RECT_V + ){ + SVGOStringStream tmp_rectangle; + int i,fill_idx; + U_GRADIENT4 *rcs = (U_GRADIENT4 *)(((char *)lpEMFR) + sizeof(U_EMRGRADIENTFILL) + sizeof(U_TRIVERTEX)*nV); + for(i=0;i<nG;i++){ + tmp_rectangle << "\n<path d=\""; + fill_idx = add_gradient(d, pEmr->ulMode, tv[rcs[i].UpperLeft], tv[rcs[i].LowerRight]); + tmp_rectangle << "\n\tM " << pix_to_xy( d, tv[rcs[i].UpperLeft ].x , tv[rcs[i].UpperLeft ].y ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, tv[rcs[i].LowerRight].x , tv[rcs[i].UpperLeft ].y ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, tv[rcs[i].LowerRight].x , tv[rcs[i].LowerRight].y ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, tv[rcs[i].UpperLeft ].x , tv[rcs[i].LowerRight].y ) << " "; + tmp_rectangle << "\n\tz\""; + tmp_rectangle << "\n\tstyle=\"stroke:none;fill:url(#"; + tmp_rectangle << d->gradients.strings[fill_idx]; + tmp_rectangle << ");\"\n"; + if (d->dc[d->level].clip_id){ + tmp_rectangle << "\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n"; + } + tmp_rectangle << "/>\n"; + } + d->outsvg += tmp_rectangle.str().c_str(); + } + else if(pEmr->ulMode == U_GRADIENT_FILL_TRIANGLE){ + SVGOStringStream tmp_triangle; + char tmpcolor[8]; + int i; + U_GRADIENT3 *tris = (U_GRADIENT3 *)(((char *)lpEMFR) + sizeof(U_EMRGRADIENTFILL) + sizeof(U_TRIVERTEX)*nV); + for(i=0;i<nG;i++){ + tmp_triangle << "\n<path d=\""; + sprintf(tmpcolor,"%6.6X",sethexcolor(trivertex_to_colorref(tv[tris[i].Vertex1]))); + tmp_triangle << "\n\tM " << pix_to_xy( d, tv[tris[i].Vertex1].x , tv[tris[i].Vertex1].y ) << " "; + tmp_triangle << "\n\tL " << pix_to_xy( d, tv[tris[i].Vertex2].x , tv[tris[i].Vertex2].y ) << " "; + tmp_triangle << "\n\tL " << pix_to_xy( d, tv[tris[i].Vertex3].x , tv[tris[i].Vertex3].y ) << " "; + tmp_triangle << "\n\tz\""; + tmp_triangle << "\n\tstyle=\"stroke:none;fill:#"; + tmp_triangle << tmpcolor; + tmp_triangle << ";\"\n/>\n"; + } + d->outsvg += tmp_triangle.str().c_str(); + } + d->path = ""; + // if it is anything else the record is bogus, so ignore it + break; + } + case U_EMR_SETLINKEDUFIS: dbg_str << "<!-- U_EMR_SETLINKEDUFIS -->\n"; break; + case U_EMR_SETTEXTJUSTIFICATION: dbg_str << "<!-- U_EMR_SETTEXTJUSTIFICATION -->\n"; break; + case U_EMR_COLORMATCHTOTARGETW: dbg_str << "<!-- U_EMR_COLORMATCHTOTARGETW -->\n"; break; + case U_EMR_CREATECOLORSPACEW: dbg_str << "<!-- U_EMR_CREATECOLORSPACEW -->\n"; break; + default: + dbg_str << "<!-- U_EMR_??? -->\n"; + break; + } //end of switch +// At run time define environment variable INKSCAPE_DBG_EMF to include string COMMENT. +// Users may employ this to to place a comment for each processed EMR record in the SVG + if(eDbgComment){ + d->outsvg += dbg_str.str().c_str(); + } + d->outsvg += tmp_outsvg.str().c_str(); + d->path += tmp_path.str().c_str(); + + } //end of while +// At run time define environment variable INKSCAPE_DBG_EMF to include string FINAL +// Users may employ this to to show the final SVG derived from the EMF + if(eDbgFinal){ + std::cout << d->outsvg << std::endl; + } + (void) emr_properties(U_EMR_INVALID); // force the release of the lookup table memory, returned value is irrelevant + + return(file_status); +} + +void Emf::free_emf_strings(EMF_STRINGS name){ + if(name.count){ + for(int i=0; i< name.count; i++){ free(name.strings[i]); } + free(name.strings); + } + name.count = 0; + name.size = 0; +} + +SPDocument * +Emf::open( Inkscape::Extension::Input * /*mod*/, const gchar *uri ) +{ + if (uri == nullptr) { + return nullptr; + } + + // ensure usage of dot as decimal separator in scanf/printf functions (indepentendly of current locale) + char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr)); + setlocale(LC_NUMERIC, "C"); + + EMF_CALLBACK_DATA d; + + d.n_obj = 0; //these might not be set otherwise if the input file is corrupt + d.emf_obj = nullptr; + d.dc[0].font_name = strdup("Arial"); // Default font, set only on lowest level, it copies up from there EMF spec says device can pick whatever it wants + + // set up the size default for patterns in defs. This might not be referenced if there are no patterns defined in the drawing. + + d.defs += "\n"; + d.defs += " <pattern id=\"EMFhbasepattern\" \n"; + d.defs += " patternUnits=\"userSpaceOnUse\"\n"; + d.defs += " width=\"6\" \n"; + d.defs += " height=\"6\" \n"; + d.defs += " x=\"0\" \n"; + d.defs += " y=\"0\"> \n"; + d.defs += " </pattern> \n"; + + + size_t length; + char *contents; + if(emf_readdata(uri, &contents, &length))return(nullptr); + + d.pDesc = nullptr; + + // set up the text reassembly system + if(!(d.tri = trinfo_init(nullptr)))return(nullptr); + (void) trinfo_load_ft_opts(d.tri, 1, + FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, + FT_KERNING_UNSCALED); + + int good = myEnhMetaFileProc(contents,length, &d); + free(contents); + + if (d.pDesc){ free( d.pDesc ); } + +// std::cout << "SVG Output: " << std::endl << d.outsvg << std::endl; + + SPDocument *doc = nullptr; + if (good) { + doc = SPDocument::createNewDocFromMem(d.outsvg.c_str(), strlen(d.outsvg.c_str()), TRUE); + } + + free_emf_strings(d.hatches); + free_emf_strings(d.images); + free_emf_strings(d.gradients); + free_emf_strings(d.clips); + + if (d.emf_obj) { + int i; + for (i=0; i<d.n_obj; i++) + delete_object(&d, i); + delete[] d.emf_obj; + } + + d.dc[0].style.stroke_dasharray.values.clear(); + + for(int i=0; i<=EMF_MAX_DC; i++){ + if(d.dc[i].font_name)free(d.dc[i].font_name); + } + + d.tri = trinfo_release_except_FC(d.tri); + + // in earlier versions no viewbox was generated and a call to setViewBoxIfMissing() was needed here. + + // restore decimal separator used in scanf/printf functions to initial value + setlocale(LC_NUMERIC, oldlocale); + g_free(oldlocale); + + return doc; +} + + +void +Emf::init () +{ + /* EMF in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("EMF Input") "</name>\n" + "<id>org.inkscape.input.emf</id>\n" + "<input>\n" + "<extension>.emf</extension>\n" + "<mimetype>image/x-emf</mimetype>\n" + "<filetypename>" N_("Enhanced Metafiles (*.emf)") "</filetypename>\n" + "<filetypetooltip>" N_("Enhanced Metafiles") "</filetypetooltip>\n" + "<output_extension>org.inkscape.output.emf</output_extension>\n" + "</input>\n" + "</inkscape-extension>", new Emf()); + + /* EMF out */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("EMF Output") "</name>\n" + "<id>org.inkscape.output.emf</id>\n" + "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"bool\">true</param>\n" + "<param name=\"TnrToSymbol\" gui-text=\"" N_("Map Unicode to Symbol font") "\" type=\"bool\">true</param>\n" + "<param name=\"TnrToWingdings\" gui-text=\"" N_("Map Unicode to Wingdings") "\" type=\"bool\">true</param>\n" + "<param name=\"TnrToZapfDingbats\" gui-text=\"" N_("Map Unicode to Zapf Dingbats") "\" type=\"bool\">true</param>\n" + "<param name=\"UsePUA\" gui-text=\"" N_("Use MS Unicode PUA (0xF020-0xF0FF) for converted characters") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTCharPos\" gui-text=\"" N_("Compensate for PPT font bug") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTDashLine\" gui-text=\"" N_("Convert dashed/dotted lines to single lines") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTGrad2Polys\" gui-text=\"" N_("Convert gradients to colored polygon series") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTLinGrad\" gui-text=\"" N_("Use native rectangular linear gradients") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTPatternAsHatch\" gui-text=\"" N_("Map all fill patterns to standard EMF hatches") "\" type=\"bool\">false</param>\n" + "<param name=\"FixImageRot\" gui-text=\"" N_("Ignore image rotations") "\" type=\"bool\">false</param>\n" + "<output>\n" + "<extension>.emf</extension>\n" + "<mimetype>image/x-emf</mimetype>\n" + "<filetypename>" N_("Enhanced Metafile (*.emf)") "</filetypename>\n" + "<filetypetooltip>" N_("Enhanced Metafile") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new Emf()); + + return; +} + + +} } } /* namespace Inkscape, Extension, Implementation */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/emf-inout.h b/src/extension/internal/emf-inout.h new file mode 100644 index 0000000..1891e8b --- /dev/null +++ b/src/extension/internal/emf-inout.h @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Enhanced Metafile Input/Output + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * David Mathog + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_EXTENSION_INTERNAL_EMF_H +#define SEEN_EXTENSION_INTERNAL_EMF_H + +#include <3rdparty/libuemf/uemf.h> +#include <3rdparty/libuemf/uemf_safe.h> +#include <3rdparty/libuemf/uemf_endian.h> // for U_emf_record_sizeok() +#include "extension/internal/metafile-inout.h" // picks up PNG +#include "extension/implementation/implementation.h" +#include "style.h" +#include "text_reassemble.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +#define DIRTY_NONE 0x00 +#define DIRTY_TEXT 0x01 +#define DIRTY_FILL 0x02 +#define DIRTY_STROKE 0x04 + +struct EMF_OBJECT { + int type = 0; + int level = 0; + char *lpEMFR = nullptr; +}; +using PEMF_OBJECT = EMF_OBJECT *; + +struct EMF_STRINGS { + int size = 0; // number of slots allocated in strings + int count = 0; // number of slots used in strings + char **strings = nullptr; // place to store strings +}; +using PEMF_STRINGS = EMF_STRINGS *; + +struct EMF_DEVICE_CONTEXT { + EMF_DEVICE_CONTEXT() : + // SPStyle: class with constructor + font_name(nullptr), + clip_id(0), + stroke_set(false), stroke_mode(0), stroke_idx(0), stroke_recidx(0), + fill_set(false), fill_mode(0), fill_idx(0), fill_recidx(0), + dirty(0), + // sizeWnd, sizeView, winorg, vieworg, + ScaleInX(0), ScaleInY(0), + ScaleOutX(0), ScaleOutY(0), + bkMode(U_TRANSPARENT), + // bkColor, textColor + textAlign(0) + // worldTransform, cur + { + font_name = nullptr; + sizeWnd = sizel_set( 0.0, 0.0 ); + sizeView = sizel_set( 0.0, 0.0 ); + winorg = point32_set( 0.0, 0.0 ); + vieworg = point32_set( 0.0, 0.0 ); + bkColor = U_RGB(255, 255, 255); // default foreground color (white) + textColor = U_RGB(0, 0, 0); // default foreground color (black) + worldTransform.eM11 = 1.0; + worldTransform.eM12 = 0.0; + worldTransform.eM21 = 0.0; + worldTransform.eM22 = 1.0; + worldTransform.eDx = 0.0; + worldTransform.eDy = 0.0; + cur = point32_set( 0, 0 ); + }; + SPStyle style; + char *font_name; + int clip_id; // 0 if none, else 1 + index into clips + bool stroke_set; + int stroke_mode; // enumeration from drawmode, not used if fill_set is not True + int stroke_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill + int stroke_recidx;// record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change + bool fill_set; + int fill_mode; // enumeration from drawmode, not used if fill_set is not True + int fill_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill + int fill_recidx; // record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change + int dirty; // holds the dirty bits for text, stroke, fill + U_SIZEL sizeWnd; + U_SIZEL sizeView; + U_POINTL winorg; + U_POINTL vieworg; + double ScaleInX, ScaleInY; + double ScaleOutX, ScaleOutY; + uint16_t bkMode; + U_COLORREF bkColor; + U_COLORREF textColor; + uint32_t textAlign; + U_XFORM worldTransform; + U_POINTL cur; +}; +using PEMF_DEVICE_CONTEXT = EMF_DEVICE_CONTEXT *; + +#define EMF_MAX_DC 128 + +struct EMF_CALLBACK_DATA { + + EMF_CALLBACK_DATA() : + // dc: array, structure w/ constructor + level(0), + E2IdirY(1.0), + D2PscaleX(1.0), D2PscaleY(1.0), + MM100InX(0), MM100InY(0), + PixelsInX(0), PixelsInY(0), + PixelsOutX(0), PixelsOutY(0), + ulCornerInX(0), ulCornerInY(0), + ulCornerOutX(0), ulCornerOutY(0), + mask(0), + arcdir(U_AD_COUNTERCLOCKWISE), + dwRop2(U_R2_COPYPEN), dwRop3(0), + MMX(0),MMY(0), + drawtype(0), + pDesc(nullptr), + // hatches, images, gradients, struct w/ constructor + tri(nullptr), + n_obj(0) + // emf_obj; + {}; + + Glib::ustring outsvg; + Glib::ustring path; + Glib::ustring outdef; + Glib::ustring defs; + + EMF_DEVICE_CONTEXT dc[EMF_MAX_DC+1]; // FIXME: This should be dynamic.. + int level; + + double E2IdirY; // EMF Y direction relative to Inkscape Y direction. Will be negative for MM_LOMETRIC etc. + double D2PscaleX,D2PscaleY; // EMF device to Inkscape Page scale. + float MM100InX, MM100InY; // size of the drawing in hundredths of a millimeter + float PixelsInX, PixelsInY; // size of the drawing, in EMF device pixels + float PixelsOutX, PixelsOutY; // size of the drawing, in Inkscape pixels + double ulCornerInX,ulCornerInY; // Upper left corner, from header rclBounds, in logical units + double ulCornerOutX,ulCornerOutY; // Upper left corner, in Inkscape pixels + uint32_t mask; // Draw properties + int arcdir; //U_AD_COUNTERCLOCKWISE 1 or U_AD_CLOCKWISE 2 + + uint32_t dwRop2; // Binary raster operation, 0 if none (use brush/pen unmolested) + uint32_t dwRop3; // Ternary raster operation, 0 if none (use brush/pen unmolested) + + float MMX; + float MMY; + + unsigned int drawtype; // one of 0 or U_EMR_FILLPATH, U_EMR_STROKEPATH, U_EMR_STROKEANDFILLPATH + char *pDesc; + // both of these end up in <defs> under the names shown here. These structures allow duplicates to be avoided. + EMF_STRINGS hatches; // hold pattern names, all like EMFhatch#_$$$$$$ where # is the EMF hatch code and $$$$$$ is the color + EMF_STRINGS images; // hold images, all like Image#, where # is the slot the image lives. + EMF_STRINGS gradients; // hold gradient names, all like EMF[HV]_$$$$$$_$$$$$$ where $$$$$$ are the colors + EMF_STRINGS clips; // hold clipping paths, referred to be the slot where the clipping path lives + TR_INFO *tri; // Text Reassembly data structure + + + int n_obj; + PEMF_OBJECT emf_obj; +}; +using PEMF_CALLBACK_DATA = EMF_CALLBACK_DATA *; + +class Emf : public Metafile +{ + +public: + + Emf(); // Empty constructor + + ~Emf() override;//Destructor + + bool check(Inkscape::Extension::Extension *module) override; //Can this module load (always yes for now) + + void save(Inkscape::Extension::Output *mod, // Save the given document to the given filename + SPDocument *doc, + gchar const *filename) override; + + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ) override; + + static void init();//Initialize the class + +private: + +protected: + static void print_document_to_file(SPDocument *doc, const gchar *filename); + static double current_scale(PEMF_CALLBACK_DATA d); + static std::string current_matrix(PEMF_CALLBACK_DATA d, double x, double y, int useoffset); + static double current_rotation(PEMF_CALLBACK_DATA d); + static void enlarge_hatches(PEMF_CALLBACK_DATA d); + static int in_hatches(PEMF_CALLBACK_DATA d, char *test); + static uint32_t add_hatch(PEMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor); + static void enlarge_images(PEMF_CALLBACK_DATA d); + static int in_images(PEMF_CALLBACK_DATA d, const char *test); + static uint32_t add_image(PEMF_CALLBACK_DATA d, void *pEmr, uint32_t cbBits, uint32_t cbBmi, + uint32_t iUsage, uint32_t offBits, uint32_t offBmi); + static void enlarge_gradients(PEMF_CALLBACK_DATA d); + static int in_gradients(PEMF_CALLBACK_DATA d, const char *test); + static uint32_t add_gradient(PEMF_CALLBACK_DATA d, uint32_t gradientType, U_TRIVERTEX tv1, U_TRIVERTEX tv2); + + static void enlarge_clips(PEMF_CALLBACK_DATA d); + static int in_clips(PEMF_CALLBACK_DATA d, const char *test); + static void add_clips(PEMF_CALLBACK_DATA d, const char *clippath, unsigned int logic); + + static void output_style(PEMF_CALLBACK_DATA d, int iType); + static double _pix_x_to_point(PEMF_CALLBACK_DATA d, double px); + static double _pix_y_to_point(PEMF_CALLBACK_DATA d, double py); + static double pix_to_x_point(PEMF_CALLBACK_DATA d, double px, double py); + static double pix_to_y_point(PEMF_CALLBACK_DATA d, double px, double py); + static double pix_to_abs_size(PEMF_CALLBACK_DATA d, double px); + static void snap_to_faraway_pair(double *x, double *y); + static std::string pix_to_xy(PEMF_CALLBACK_DATA d, double x, double y); + static void select_pen(PEMF_CALLBACK_DATA d, int index); + static void select_extpen(PEMF_CALLBACK_DATA d, int index); + static void select_brush(PEMF_CALLBACK_DATA d, int index); + static void select_font(PEMF_CALLBACK_DATA d, int index); + static void delete_object(PEMF_CALLBACK_DATA d, int index); + static void insert_object(PEMF_CALLBACK_DATA d, int index, int type, PU_ENHMETARECORD pObj); + static int AI_hack(PU_EMRHEADER pEmr); + static uint32_t *unknown_chars(size_t count); + static void common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr, + double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh, + uint32_t iUsage, uint32_t offBits, uint32_t cbBits, uint32_t offBmi, uint32_t cbBmi); + static int myEnhMetaFileProc(char *contents, unsigned int length, PEMF_CALLBACK_DATA d); + static void free_emf_strings(EMF_STRINGS name); + +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + + +#endif /* EXTENSION_INTERNAL_EMF_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/extension/internal/emf-print.cpp b/src/extension/internal/emf-print.cpp new file mode 100644 index 0000000..cf75020 --- /dev/null +++ b/src/extension/internal/emf-print.cpp @@ -0,0 +1,2217 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Enhanced Metafile printing + *//* + * Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * David Mathog + * + * Copyright (C) 2006-2009 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + * References: + * - How to Create & Play Enhanced Metafiles in Win32 + * http://support.microsoft.com/kb/q145999/ + * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles + * http://support.microsoft.com/kb/q66949/ + * - Metafile Functions + * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp + * - Metafile Structures + * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp + */ + +#include <cstring> +#include <glibmm/miscutils.h> +#include <3rdparty/libuemf/symbol_convert.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/rect.h> +#include <2geom/curves.h> + +#include "helper/geom.h" +#include "helper/geom-curves.h" +#include "util/units.h" + +#include "inkscape-version.h" + +#include "extension/system.h" +#include "extension/print.h" +#include "document.h" +#include "path-prefix.h" + +#include "object/sp-pattern.h" +#include "object/sp-image.h" +#include "object/sp-gradient.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-item.h" +#include "object/sp-root.h" +#include "object/sp-shape.h" +#include "object/sp-clippath.h" +#include "style.h" +#include "display/cairo-utils.h" + +#include "splivarot.h" // pieces for union on shapes +#include "2geom/svg-path-parser.h" // to get from SVG text to Geom::Path +#include "display/canvas-bpath.h" // for SPWindRule +#include "display/cairo-utils.h" // for Inkscape::Pixbuf::PF_CAIRO + +#include "emf-print.h" + + +namespace Inkscape { +namespace Extension { +namespace Internal { + +#define PXPERMETER 2835 + +/* globals */ +static double PX2WORLD; +static bool FixPPTCharPos, FixPPTDashLine, FixPPTGrad2Polys, FixPPTLinGrad, FixPPTPatternAsHatch, FixImageRot; +static EMFTRACK *et = nullptr; +static EMFHANDLES *eht = nullptr; + +void PrintEmf::smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale) +{ + float fdx; + int i; + uint32_t *ladx; + const char *cptr = &string[strlen(string) + 1]; // this works because of the first fake terminator + + *adx = nullptr; + *ky = 0.0; // set a default value + sscanf(cptr, "%7d", ndx); + if (!*ndx) { + return; // this could happen with an empty string + } + cptr += 7; + ladx = (uint32_t *) malloc(*ndx * sizeof(uint32_t)); + if (!ladx) { + g_message("Out of memory"); + } + *adx = ladx; + for (i = 0; i < *ndx; i++, cptr += 7, ladx++) { + sscanf(cptr, "%7f", &fdx); + *ladx = (uint32_t) round(fdx * scale); + } + cptr++; // skip 2nd fake terminator + sscanf(cptr, "%7f", &fdx); + *ky = fdx; + cptr += 7; // advance over ky and its space + sscanf(cptr, "%07d", rtl); +} + +PrintEmf::PrintEmf() +{ + // all of the class variables are initialized elsewhere, many in PrintEmf::Begin, +} + + +unsigned int PrintEmf::setup(Inkscape::Extension::Print * /*mod*/) +{ + return TRUE; +} + + +unsigned int PrintEmf::begin(Inkscape::Extension::Print *mod, SPDocument *doc) +{ + U_SIZEL szlDev, szlMm; + U_RECTL rclBounds, rclFrame; + char *rec; + gchar const *utf8_fn = mod->get_param_string("destination"); + + // Typically PX2WORLD is 1200/90, using inkscape's default dpi + PX2WORLD = 1200.0 / Inkscape::Util::Quantity::convert(1.0, "in", "px"); + FixPPTCharPos = mod->get_param_bool("FixPPTCharPos"); + FixPPTDashLine = mod->get_param_bool("FixPPTDashLine"); + FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys"); + FixPPTLinGrad = mod->get_param_bool("FixPPTLinGrad"); + FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch"); + FixImageRot = mod->get_param_bool("FixImageRot"); + + (void) emf_start(utf8_fn, 1000000, 250000, &et); // Initialize the et structure + (void) htable_create(128, 128, &eht); // Initialize the eht structure + + char *ansi_uri = (char *) utf8_fn; + + + // width and height in px + _width = doc->getWidth().value("px"); + _height = doc->getHeight().value("px"); + _doc_unit_scale = doc->getDocumentScale()[Geom::X]; + + // initialize a few global variables + hbrush = hbrushOld = hpen = 0; + htextalignment = U_TA_BASELINE | U_TA_LEFT; + use_stroke = use_fill = simple_shape = usebk = false; + + Inkscape::XML::Node *nv = doc->getReprNamedView(); + if (nv) { + const char *p1 = nv->attribute("pagecolor"); + char *p2; + uint32_t lc = strtoul(&p1[1], &p2, 16); // it looks like "#ABC123" + if (*p2) { + lc = 0; + } + gv.bgc = _gethexcolor(lc); + gv.rgb[0] = (float) U_RGBAGetR(gv.bgc) / 255.0; + gv.rgb[1] = (float) U_RGBAGetG(gv.bgc) / 255.0; + gv.rgb[2] = (float) U_RGBAGetB(gv.bgc) / 255.0; + } + + bool pageBoundingBox; + pageBoundingBox = mod->get_param_bool("pageBoundingBox"); + + Geom::Rect d; + if (pageBoundingBox) { + d = Geom::Rect::from_xywh(0, 0, _width, _height); + } else { + SPItem *doc_item = doc->getRoot(); + Geom::OptRect bbox = doc_item->desktopVisualBounds(); + if (bbox) { + d = *bbox; + } + } + + d *= Geom::Scale(Inkscape::Util::Quantity::convert(1, "px", "in")); + + float dwInchesX = d.width(); + float dwInchesY = d.height(); + + // dwInchesX x dwInchesY in micrometer units, 1200 dpi/25.4 -> dpmm + (void) drawing_size((int) ceil(dwInchesX * 25.4), (int) ceil(dwInchesY * 25.4),1200.0/25.4, &rclBounds, &rclFrame); + + // set up the reference device as 100 X A4 horizontal, (1200 dpi/25.4 -> dpmm). Extra digits maintain dpi better in EMF + int MMX = 216; + int MMY = 279; + (void) device_size(MMX, MMY, 1200.0 / 25.4, &szlDev, &szlMm); + int PixelsX = szlDev.cx; + int PixelsY = szlDev.cy; + + // set up the description: (version string)0(file)00 + char buff[1024]; + memset(buff, 0, sizeof(buff)); + char *p1 = strrchr(ansi_uri, '\\'); + char *p2 = strrchr(ansi_uri, '/'); + char *p = MAX(p1, p2); + if (p) { + p++; + } else { + p = ansi_uri; + } + snprintf(buff, sizeof(buff) - 1, "Inkscape %s \1%s\1", Inkscape::version_string, p); + uint16_t *Description = U_Utf8ToUtf16le(buff, 0, nullptr); + int cbDesc = 2 + wchar16len(Description); // also count the final terminator + (void) U_Utf16leEdit(Description, '\1', '\0'); // swap the temporary \1 characters for nulls + + // construct the EMRHEADER record and append it to the EMF in memory + rec = U_EMRHEADER_set(rclBounds, rclFrame, nullptr, cbDesc, Description, szlDev, szlMm, 0); + free(Description); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at EMRHEADER"); + } + + + // Simplest mapping mode, supply all coordinates in pixels + rec = U_EMRSETMAPMODE_set(U_MM_TEXT); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at EMRSETMAPMODE"); + } + + + // In earlier versions this was used to scale from inkscape's dpi of 90 to + // the files 1200 dpi, taking into account PX2WORLD which was 20. Now PX2WORLD + // is set so that this matrix is unitary. The usual value of PX2WORLD is 1200/90, + // but might be different if the internal dpi is changed. + + U_XFORM worldTransform; + worldTransform.eM11 = 1.0; + worldTransform.eM12 = 0.0; + worldTransform.eM21 = 0.0; + worldTransform.eM22 = 1.0; + worldTransform.eDx = 0; + worldTransform.eDy = 0; + + rec = U_EMRMODIFYWORLDTRANSFORM_set(worldTransform, U_MWT_LEFTMULTIPLY); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at EMRMODIFYWORLDTRANSFORM"); + } + + + if (true) { + snprintf(buff, sizeof(buff) - 1, "Screen=%dx%dpx, %dx%dmm", PixelsX, PixelsY, MMX, MMY); + rec = textcomment_set(buff); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at textcomment_set 1"); + } + + snprintf(buff, sizeof(buff) - 1, "Drawing=%.1fx%.1fpx, %.1fx%.1fmm", _width, _height, Inkscape::Util::Quantity::convert(dwInchesX, "in", "mm"), Inkscape::Util::Quantity::convert(dwInchesY, "in", "mm")); + rec = textcomment_set(buff); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at textcomment_set 1"); + } + } + + /* set some parameters, else the program that reads the EMF may default to other values */ + + rec = U_EMRSETBKMODE_set(U_TRANSPARENT); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETBKMODE_set"); + } + + hpolyfillmode = U_WINDING; + rec = U_EMRSETPOLYFILLMODE_set(U_WINDING); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETPOLYFILLMODE_set"); + } + + // Text alignment: (only changed if RTL text is encountered ) + // - (x,y) coordinates received by this filter are those of the point where the text + // actually starts, and already takes into account the text object's alignment; + // - for this reason, the EMF text alignment must always be TA_BASELINE|TA_LEFT. + htextalignment = U_TA_BASELINE | U_TA_LEFT; + rec = U_EMRSETTEXTALIGN_set(U_TA_BASELINE | U_TA_LEFT); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTALIGN_set"); + } + + htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0; + rec = U_EMRSETTEXTCOLOR_set(U_RGB(0, 0, 0)); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTCOLOR_set"); + } + + rec = U_EMRSETROP2_set(U_R2_COPYPEN); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::begin at U_EMRSETROP2_set"); + } + + /* miterlimit is set with eah pen, so no need to check for it changes as in WMF */ + + return 0; +} + + +unsigned int PrintEmf::finish(Inkscape::Extension::Print * /*mod*/) +{ + do_clip_if_present(nullptr); // Terminate any open clip. + char *rec; + if (!et) { + return 0; + } + + + // earlier versions had flush of fill here, but it never executed and was removed + + rec = U_EMREOF_set(0, nullptr, et); // generate the EOF record + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::finish"); + } + (void) emf_finish(et, eht); // Finalize and write out the EMF + emf_free(&et); // clean up + htable_free(&eht); // clean up + + return 0; +} + + +unsigned int PrintEmf::comment( + Inkscape::Extension::Print * /*module*/, + const char * /*comment*/) +{ + if (!et) { + return 0; + } + + // earlier versions had flush of fill here, but it never executed and was removed + + return 0; +} + + +// fcolor is defined when gradients are being expanded, it is the color of one stripe or ring. +int PrintEmf::create_brush(SPStyle const *style, PU_COLORREF fcolor) +{ + float rgb[3]; + char *rec; + U_LOGBRUSH lb; + uint32_t brush, fmode; + MFDrawMode fill_mode; + Inkscape::Pixbuf *pixbuf; + uint32_t brushStyle; + int hatchType; + U_COLORREF hatchColor; + U_COLORREF bkColor; + uint32_t width = 0; // quiets a harmless compiler warning, initialization not otherwise required. + uint32_t height = 0; + + if (!et) { + return 0; + } + + // set a default fill in case we can't figure out a better way to do it + fmode = U_ALTERNATE; + fill_mode = DRAW_PAINT; + brushStyle = U_BS_SOLID; + hatchType = U_HS_SOLIDCLR; + bkColor = U_RGB(0, 0, 0); + if (fcolor) { + hatchColor = *fcolor; + } else { + hatchColor = U_RGB(0, 0, 0); + } + + if (!fcolor && style) { + if (style->fill.isColor()) { + fill_mode = DRAW_PAINT; +#if 0 +// opacity not supported by EMF + float opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + if (opacity <= 0.0) { + opacity = 0.0; // basically the same as no fill + } +#endif + style->fill.value.color.get_rgb_floatv(rgb); + hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); + + fmode = style->fill_rule.computed == 0 ? U_WINDING : (style->fill_rule.computed == 2 ? U_ALTERNATE : U_ALTERNATE); + } else if (SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style))) { // must be paint-server + SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPPattern *pat = SP_PATTERN(paintserver); + double dwidth = pat->width(); + double dheight = pat->height(); + width = dwidth; + height = dheight; + brush_classify(pat, 0, &pixbuf, &hatchType, &hatchColor, &bkColor); + if (pixbuf) { + fill_mode = DRAW_IMAGE; + } else { // pattern + fill_mode = DRAW_PATTERN; + if (hatchType == -1) { // Not a standard hatch, so force it to something + hatchType = U_HS_CROSS; + hatchColor = U_RGB(0xFF, 0xC3, 0xC3); + } + } + if (FixPPTPatternAsHatch) { + if (hatchType == -1) { // image or unclassified + fill_mode = DRAW_PATTERN; + hatchType = U_HS_DIAGCROSS; + hatchColor = U_RGB(0xFF, 0xC3, 0xC3); + } + } + brushStyle = U_BS_HATCHED; + } else if (SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))) { // must be a gradient + // currently we do not do anything with gradients, the code below just sets the color to the average of the stops + SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPLinearGradient *lg = nullptr; + SPRadialGradient *rg = nullptr; + + if (SP_IS_LINEARGRADIENT(paintserver)) { + lg = SP_LINEARGRADIENT(paintserver); + SP_GRADIENT(lg)->ensureVector(); // when exporting from commandline, vector is not built + fill_mode = DRAW_LINEAR_GRADIENT; + } else if (SP_IS_RADIALGRADIENT(paintserver)) { + rg = SP_RADIALGRADIENT(paintserver); + SP_GRADIENT(rg)->ensureVector(); // when exporting from commandline, vector is not built + fill_mode = DRAW_RADIAL_GRADIENT; + } else { + // default fill + } + + if (rg) { + if (FixPPTGrad2Polys) { + return hold_gradient(rg, fill_mode); + } else { + hatchColor = avg_stop_color(rg); + } + } else if (lg) { + if (FixPPTGrad2Polys || FixPPTLinGrad) { + return hold_gradient(lg, fill_mode); + } else { + hatchColor = avg_stop_color(lg); + } + } + } + } else { // if (!style) + // default fill + } + + lb = logbrush_set(brushStyle, hatchColor, hatchType); + + switch (fill_mode) { + case DRAW_LINEAR_GRADIENT: // fill with average color unless gradients are converted to slices + case DRAW_RADIAL_GRADIENT: // ditto + case DRAW_PAINT: + case DRAW_PATTERN: + // SVG text has no background attribute, so OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output. + if (usebk) { + rec = U_EMRSETBKCOLOR_set(bkColor); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_brush at U_EMRSETBKCOLOR_set"); + } + rec = U_EMRSETBKMODE_set(U_OPAQUE); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_brush at U_EMRSETBKMODE_set"); + } + } + rec = createbrushindirect_set(&brush, eht, lb); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_brush at createbrushindirect_set"); + } + break; + case DRAW_IMAGE: + char *px; + char *rgba_px; + uint32_t cbPx; + uint32_t colortype; + PU_RGBQUAD ct; + int numCt; + U_BITMAPINFOHEADER Bmih; + PU_BITMAPINFO Bmi; + rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!! + colortype = U_BCBM_COLOR32; + (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1); + // pixbuf can be either PF_CAIRO or PF_GDK, and these have R and B bytes swapped + if (pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { swapRBinRGBA(px, width * height); } + Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0); + Bmi = bitmapinfo_set(Bmih, ct); + rec = createdibpatternbrushpt_set(&brush, eht, U_DIB_RGB_COLORS, Bmi, cbPx, px); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_brush at createdibpatternbrushpt_set"); + } + free(px); + free(Bmi); // ct will be NULL because of colortype + break; + } + + hbrush = brush; // need this later for destroy_brush + rec = selectobject_set(brush, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_brush at selectobject_set"); + } + + if (fmode != hpolyfillmode) { + hpolyfillmode = fmode; + rec = U_EMRSETPOLYFILLMODE_set(fmode); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_brush at U_EMRSETPOLYdrawmode_set"); + } + } + + return 0; +} + + +void PrintEmf::destroy_brush() +{ + char *rec; + // before an object may be safely deleted it must no longer be selected + // select in a stock object to deselect this one, the stock object should + // never be used because we always select in a new one before drawing anythingrestore previous brush, necessary??? Would using a default stock object not work? + rec = selectobject_set(U_NULL_BRUSH, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::destroy_brush at selectobject_set"); + } + if (hbrush) { + rec = deleteobject_set(&hbrush, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::destroy_brush"); + } + hbrush = 0; + } +} + + +int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform) +{ + U_EXTLOGPEN *elp; + U_NUM_STYLEENTRY n_dash = 0; + U_STYLEENTRY *dash = nullptr; + char *rec = nullptr; + int linestyle = U_PS_SOLID; + int linecap = 0; + int linejoin = 0; + uint32_t pen; + uint32_t brushStyle; + Inkscape::Pixbuf *pixbuf; + int hatchType; + U_COLORREF hatchColor; + U_COLORREF bkColor; + uint32_t width, height; + char *px = nullptr; + char *rgba_px; + uint32_t cbPx = 0; + uint32_t colortype; + PU_RGBQUAD ct = nullptr; + int numCt = 0; + U_BITMAPINFOHEADER Bmih; + PU_BITMAPINFO Bmi = nullptr; + + if (!et) { + return 0; + } + + // set a default stroke in case we can't figure out a better way to do it + brushStyle = U_BS_SOLID; + hatchColor = U_RGB(0, 0, 0); + hatchType = U_HS_HORIZONTAL; + bkColor = U_RGB(0, 0, 0); + + if (style) { + float rgb[3]; + + if (SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style))) { // must be paint-server + SPPaintServer *paintserver = style->stroke.value.href->getObject(); + SPPattern *pat = SP_PATTERN(paintserver); + double dwidth = pat->width(); + double dheight = pat->height(); + width = dwidth; + height = dheight; + brush_classify(pat, 0, &pixbuf, &hatchType, &hatchColor, &bkColor); + if (pixbuf) { + brushStyle = U_BS_DIBPATTERN; + rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!! + colortype = U_BCBM_COLOR32; + (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1); + // pixbuf can be either PF_CAIRO or PF_GDK, and these have R and B bytes swapped + if (pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { swapRBinRGBA(px, width * height); } + Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0); + Bmi = bitmapinfo_set(Bmih, ct); + } else { // pattern + brushStyle = U_BS_HATCHED; + if (usebk) { // OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output. + rec = U_EMRSETBKCOLOR_set(bkColor); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_pen at U_EMRSETBKCOLOR_set"); + } + rec = U_EMRSETBKMODE_set(U_OPAQUE); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_pen at U_EMRSETBKMODE_set"); + } + } + if (hatchType == -1) { // Not a standard hatch, so force it to something + hatchType = U_HS_CROSS; + hatchColor = U_RGB(0xFF, 0xC3, 0xC3); + } + } + if (FixPPTPatternAsHatch) { + if (hatchType == -1) { // image or unclassified + brushStyle = U_BS_HATCHED; + hatchType = U_HS_DIAGCROSS; + hatchColor = U_RGB(0xFF, 0xC3, 0xC3); + } + } + } else if (SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))) { // must be a gradient + // currently we do not do anything with gradients, the code below has no net effect. + + SPPaintServer *paintserver = style->stroke.value.href->getObject(); + if (SP_IS_LINEARGRADIENT(paintserver)) { + SPLinearGradient *lg = SP_LINEARGRADIENT(paintserver); + + SP_GRADIENT(lg)->ensureVector(); // when exporting from commandline, vector is not built + + Geom::Point p1(lg->x1.computed, lg->y1.computed); + Geom::Point p2(lg->x2.computed, lg->y2.computed); + + if (lg->gradientTransform_set) { + p1 = p1 * lg->gradientTransform; + p2 = p2 * lg->gradientTransform; + } + hatchColor = avg_stop_color(lg); + } else if (SP_IS_RADIALGRADIENT(paintserver)) { + SPRadialGradient *rg = SP_RADIALGRADIENT(paintserver); + + SP_GRADIENT(rg)->ensureVector(); // when exporting from commandline, vector is not built + double r = rg->r.computed; + + Geom::Point c(rg->cx.computed, rg->cy.computed); + Geom::Point xhandle_point(r, 0); + Geom::Point yhandle_point(0, -r); + yhandle_point += c; + xhandle_point += c; + if (rg->gradientTransform_set) { + c = c * rg->gradientTransform; + yhandle_point = yhandle_point * rg->gradientTransform; + xhandle_point = xhandle_point * rg->gradientTransform; + } + hatchColor = avg_stop_color(rg); + } else { + // default fill + } + } else if (style->stroke.isColor()) { // test last, always seems to be set, even for other types above + style->stroke.value.color.get_rgb_floatv(rgb); + brushStyle = U_BS_SOLID; + hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); + hatchType = U_HS_SOLIDCLR; + } else { + // default fill + } + + + + using Geom::X; + using Geom::Y; + + Geom::Point zero(0, 0); + Geom::Point one(1, 1); + Geom::Point p0(zero * transform); + Geom::Point p1(one * transform); + Geom::Point p(p1 - p0); + + double scale = sqrt((p[X] * p[X]) + (p[Y] * p[Y])) / sqrt(2); + + if (!style->stroke_width.computed) { + return 0; //if width is 0 do not (reset) the pen, it should already be NULL_PEN + } + uint32_t linewidth = MAX(1, (uint32_t) round(scale * style->stroke_width.computed * PX2WORLD)); + + if (style->stroke_linecap.computed == 0) { + linecap = U_PS_ENDCAP_FLAT; + } else if (style->stroke_linecap.computed == 1) { + linecap = U_PS_ENDCAP_ROUND; + } else if (style->stroke_linecap.computed == 2) { + linecap = U_PS_ENDCAP_SQUARE; + } + + if (style->stroke_linejoin.computed == 0) { + linejoin = U_PS_JOIN_MITER; + } else if (style->stroke_linejoin.computed == 1) { + linejoin = U_PS_JOIN_ROUND; + } else if (style->stroke_linejoin.computed == 2) { + linejoin = U_PS_JOIN_BEVEL; + } + + if (!style->stroke_dasharray.values.empty()) { + if (FixPPTDashLine) { // will break up line into many smaller lines. Override gradient if that was set, cannot do both. + brushStyle = U_BS_SOLID; + hatchType = U_HS_HORIZONTAL; + } else { + unsigned i = 0; + while ((linestyle != U_PS_USERSTYLE) && (i < style->stroke_dasharray.values.size())) { + if (style->stroke_dasharray.values[i].value > 0.00000001) { + linestyle = U_PS_USERSTYLE; + } + i++; + } + + if (linestyle == U_PS_USERSTYLE) { + n_dash = style->stroke_dasharray.values.size(); + dash = new uint32_t[n_dash]; + for (i = 0; i < n_dash; i++) { + dash[i] = MAX(1, (uint32_t)round(scale * style->stroke_dasharray.values[i].value * PX2WORLD)); + } + } + } + } + + elp = extlogpen_set( + U_PS_GEOMETRIC | linestyle | linecap | linejoin, + linewidth, + brushStyle, + hatchColor, + hatchType, + n_dash, + dash); + + } else { // if (!style) + linejoin = 0; + elp = extlogpen_set( + linestyle, + 1, + U_BS_SOLID, + U_RGB(0, 0, 0), + U_HS_HORIZONTAL, + 0, + nullptr); + } + + rec = extcreatepen_set(&pen, eht, Bmi, cbPx, px, elp); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_pen at extcreatepen_set"); + } + free(elp); + if (Bmi) { + free(Bmi); + } + if (px) { + free(px); // ct will always be NULL + } + + rec = selectobject_set(pen, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_pen at selectobject_set"); + } + hpen = pen; // need this later for destroy_pen + + if (linejoin == U_PS_JOIN_MITER) { + float miterlimit = style->stroke_miterlimit.value; // This is a ratio. + + if (miterlimit < 1) { + miterlimit = 1; + } + + rec = U_EMRSETMITERLIMIT_set((uint32_t) miterlimit); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::create_pen at U_EMRSETMITERLIMIT_set"); + } + } + + if (n_dash) { + delete[] dash; + } + return 0; +} + +// set the current pen to the stock object NULL_PEN and then delete the defined pen object, if there is one. +void PrintEmf::destroy_pen() +{ + char *rec = nullptr; + // before an object may be safely deleted it must no longer be selected + // select in a stock object to deselect this one, the stock object should + // never be used because we always select in a new one before drawing anythingrestore previous brush, necessary??? Would using a default stock object not work? + rec = selectobject_set(U_NULL_PEN, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::destroy_pen at selectobject_set"); + } + if (hpen) { + rec = deleteobject_set(&hpen, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::destroy_pen"); + } + hpen = 0; + } +} + +/* Return a Path consisting of just the corner points of the single path in a PathVector. If the +PathVector has more than one path, or that one path is open, or any of its segments are curved, then the +returned PathVector is an empty path. If the input path is already just straight lines and vertices the output will be the +same as the sole path in the input. */ + +Geom::Path PrintEmf::pathv_to_simple_polygon(Geom::PathVector const &pathv, int *vertices) +{ + Geom::Point P1_trail; + Geom::Point P1; + Geom::Point P1_lead; + Geom::Point v1,v2; + Geom::Path output; + Geom::Path bad; + Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv); + Geom::PathVector::const_iterator pit = pv.begin(); + Geom::PathVector::const_iterator pit2 = pv.begin(); + int first_seg=1; + ++pit2; + *vertices = 0; + if(pit->end_closed() != pit->end_default())return(bad); // path must be closed + if(pit2 != pv.end())return(bad); // there may only be one path + P1_trail = pit->finalPoint(); + Geom::Path::const_iterator cit = pit->begin(); + P1 = cit->initialPoint(); + for(;cit != pit->end_closed();++cit) { + if (!is_straight_curve(*cit)) { + *vertices = 0; + return(bad); + } + P1_lead = cit->finalPoint(); + if(Geom::are_near(P1_lead, P1, 1e-5))continue; // duplicate points at the same coordinate + v1 = unit_vector(P1 - P1_trail); + v2 = unit_vector(P1_lead - P1 ); + if(Geom::are_near(dot(v1,v2), 1.0, 1e-5)){ // P1 is within a straight line + P1 = P1_lead; + continue; + } + // P1 is the center point of a turn of some angle + if(!*vertices){ + output.start( P1 ); + output.close( pit->closed() ); + } + if(!Geom::are_near(P1, P1_trail, 1e-5)){ // possible for P1 to start on the end point + Geom::LineSegment ls(P1_trail, P1); + output.append(ls); + if(first_seg){ + *vertices += 2; + first_seg=0; + } + else { + *vertices += 1; + } + } + P1_trail = P1; + P1 = P1_lead; + } + return(output); +} + +/* Returns the simplified PathVector (no matter what). + Sets is_rect if it is a rectangle. + Sets angle that will rotate side closest to horizontal onto horizontal. +*/ +Geom::Path PrintEmf::pathv_to_rect(Geom::PathVector const &pathv, bool *is_rect, double *angle) +{ + Geom::Point P1_trail; + Geom::Point P1; + Geom::Point v1,v2; + int vertices; + Geom::Path pR = pathv_to_simple_polygon(pathv, &vertices); + *is_rect = false; + if(vertices==4){ // or else it cannot be a rectangle + int vertex_count=0; + /* Get the ends of the LAST line segment. + Find minimum rotation to align rectangle with X,Y axes. (Very degenerate if it is rotated 45 degrees.) */ + *angle = 10.0; /* must be > than the actual angle in radians. */ + for(Geom::Path::iterator cit = pR.begin();; ++cit){ + P1_trail = cit->initialPoint(); + P1 = cit->finalPoint(); + v1 = unit_vector(P1 - P1_trail); + if(v1[Geom::X] > 0){ // only check the 1 or 2 points on vectors aimed the same direction as unit X + double ang = asin(v1[Geom::Y]); // because component is rotation by ang of {1,0| vector + if(fabs(ang) < fabs(*angle))*angle = -ang; // y increases down, flips sign on angle + } + if(cit == pR.end_open())break; + } + + /* For increased numerical stability, snap the angle to the nearest 1/100th of a degree. */ + double convert = 36000.0/ (2.0 * M_PI); + *angle = round(*angle * convert)/convert; + + // at this stage v1 holds the last vector in the path, whichever direction it points. + for(Geom::Path::iterator cit = pR.begin(); ;++cit) { + v2 = v1; + P1_trail = cit->initialPoint(); + P1 = cit->finalPoint(); + v1 = unit_vector(P1 - P1_trail); + // P1 is center of a turn that is not 90 degrees. Limit comes from cos(89.9) = .001745 + if(!Geom::are_near(dot(v1,v2), 0.0, 2e-3))break; + vertex_count++; + if(cit == pR.end_open())break; + } + if(vertex_count == 4){ + *is_rect=true; + } + } + return(pR); +} + +/* Compare a vector with a rectangle's orientation (angle needed to rotate side(s) + closest to horizontal to exactly horizontal) and return: + 0 none of the following + 1 parallel to horizontal + 2 parallel to vertical + 3 antiparallel to horizontal + 4 antiparallel to vertical +*/ +int PrintEmf::vector_rect_alignment(double angle, Geom::Point vtest){ + int stat = 0; + Geom::Point v1 = Geom::unit_vector(vtest); // unit vector to test alignment + Geom::Point v2 = Geom::Point(1,0) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN) + Geom::Point v3 = Geom::Point(0,1) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN) + if( Geom::are_near(dot(v1,v2), 1.0, 1e-5)){ stat = 1; } + else if(Geom::are_near(dot(v1,v2),-1.0, 1e-5)){ stat = 2; } + else if(Geom::are_near(dot(v1,v3), 1.0, 1e-5)){ stat = 3; } + else if(Geom::are_near(dot(v1,v3),-1.0, 1e-5)){ stat = 4; } + return(stat); +} + +/* retrieve the point at the indicated corner: + 0 UL (and default) + 1 UR + 2 LR + 3 LL + Needed because the start can be any point, and the direction could run either + clockwise or counterclockwise. This should work even if the corners of the rectangle + are slightly displaced. +*/ +Geom::Point PrintEmf::get_pathrect_corner(Geom::Path pathRect, double angle, int corner){ + Geom::Point center(0,0); + for(Geom::Path::iterator cit = pathRect.begin(); ; ++cit) { + center += cit->initialPoint()/4.0; + if(cit == pathRect.end_open())break; + } + + int LR; // 1 if Left, 0 if Right + int UL; // 1 if Lower, 0 if Upper (as viewed on screen, y coordinates increase downwards) + switch(corner){ + case 1: //UR + LR = 0; + UL = 0; + break; + case 2: //LR + LR = 0; + UL = 1; + break; + case 3: //LL + LR = 1; + UL = 1; + break; + default: //UL + LR = 1; + UL = 0; + break; + } + + Geom::Point v1 = Geom::Point(1,0) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN) + Geom::Point v2 = Geom::Point(0,1) * Geom::Rotate(-angle); // unit vertical side (sign change because Y increases DOWN) + Geom::Point P1; + for(Geom::Path::iterator cit = pathRect.begin(); ; ++cit) { + P1 = cit->initialPoint(); + + if ( ( LR == (dot(P1 - center,v1) > 0 ? 0 : 1) ) + && ( UL == (dot(P1 - center,v2) > 0 ? 1 : 0) ) ) break; + if(cit == pathRect.end_open())break; + } + return(P1); +} + +U_TRIVERTEX PrintEmf::make_trivertex(Geom::Point Pt, U_COLORREF uc){ + U_TRIVERTEX tv; + using Geom::X; + using Geom::Y; + tv.x = (int32_t) round(Pt[X]); + tv.y = (int32_t) round(Pt[Y]); + tv.Red = uc.Red << 8; + tv.Green = uc.Green << 8; + tv.Blue = uc.Blue << 8; + tv.Alpha = uc.Reserved << 8; // EMF will ignore this + return(tv); +} + +/* Examine clip. If there is a (new) one then apply it. If there is one and it is the + same as the preceding one, leave the preceding one active. If style is NULL + terminate the current clip, if any, and return. +*/ +void PrintEmf::do_clip_if_present(SPStyle const *style){ + char *rec; + static SPClipPath *scpActive = nullptr; + if(!style){ + if(scpActive){ // clear the existing clip + rec = U_EMRRESTOREDC_set(-1); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::fill at U_EMRRESTOREDC_set"); + } + scpActive=nullptr; + } + } else { + /* The current implementation converts only one level of clipping. If there were more + clips further up the stack they should be combined with the pathvector using "and". Since this + comes up rarely, and would involve a lot of searching (all the way up the stack for every + draw operation), it has not yet been implemented. + + Note, to debug this section of code use print statements on sp_svg_write_path(combined_pathvector). + */ + /* find the first clip_ref at object or up the stack. There may not be one. */ + SPClipPath *scp = nullptr; + SPItem *item = SP_ITEM(style->object); + while(true) { + scp = item->getClipObject(); + if(scp)break; + item = SP_ITEM(item->parent); + if(!item || SP_IS_ROOT(item))break; // this will never be a clipping path + } + + if(scp != scpActive){ // change or remove the clipping + if(scpActive){ // clear the existing clip + rec = U_EMRRESTOREDC_set(-1); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::fill at U_EMRRESTOREDC_set"); + } + scpActive = nullptr; + } + + if (scp) { // set the new clip + /* because of units and who knows what other transforms that might be applied above we + need the full transform all the way to the root. + */ + Geom::Affine tf = item->transform; + SPItem *scan_item = item; + while(true) { + scan_item = SP_ITEM(scan_item->parent); + if(!scan_item)break; + tf *= scan_item->transform; + } + tf *= Geom::Scale(_doc_unit_scale);; // Transform must be in PIXELS, no matter what the document unit is. + + /* find the clipping path */ + Geom::PathVector combined_pathvector; + Geom::Affine tfc; // clipping transform, generally not the same as item transform + for (auto& child: scp->children) { + item = SP_ITEM(&child); + if (!item) { + break; + } + if (SP_IS_GROUP(item)) { // not implemented + // return sp_group_render(item); + combined_pathvector = merge_PathVector_with_group(combined_pathvector, item, tfc); + } else if (SP_IS_SHAPE(item)) { + combined_pathvector = merge_PathVector_with_shape(combined_pathvector, item, tfc); + } else { // not implemented + } + } + + if (!combined_pathvector.empty()) { // if clipping path isn't empty, define EMF clipping record + scpActive = scp; // remember for next time + // the sole purpose of this SAVEDC is to let us clear the clipping region later. + rec = U_EMRSAVEDC_set(); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at U_EMRSAVEDC_set"); + } + (void) draw_pathv_to_EMF(combined_pathvector, tf); + rec = U_EMRSELECTCLIPPATH_set(U_RGN_COPY); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::do_clip_if_present at U_EMRSELECTCLIPPATH_set"); + } + } + else { + scpActive = nullptr; // no valid path available to draw, so no DC was saved, so no signal to restore + } + } // change or remove clipping + } // scp exists + } // style exists +} + +Geom::PathVector PrintEmf::merge_PathVector_with_group(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform) +{ + Geom::PathVector new_combined_pathvector; + if(!SP_IS_GROUP(item))return(new_combined_pathvector); // sanity test, only a group should be passed in, return empty if something else happens + + new_combined_pathvector = combined_pathvector; + SPGroup *group = SP_GROUP(item); + Geom::Affine tfc = item->transform * transform; + for (auto& child: group->children) { + item = SP_ITEM(&child); + if (!item) { + break; + } + if (SP_IS_GROUP(item)) { + new_combined_pathvector = merge_PathVector_with_group(new_combined_pathvector, item, tfc); // could be endlessly recursive on a badly formed SVG + } else if (SP_IS_SHAPE(item)) { + new_combined_pathvector = merge_PathVector_with_shape(new_combined_pathvector, item, tfc); + } else { // not implemented + } + } + return new_combined_pathvector; +} + +Geom::PathVector PrintEmf::merge_PathVector_with_shape(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform) +{ + Geom::PathVector new_combined_pathvector; + if(!SP_IS_SHAPE(item))return(new_combined_pathvector); // sanity test, only a shape should be passed in, return empty if something else happens + + Geom::Affine tfc = item->transform * transform; + SPShape *shape = SP_SHAPE(item); + if (shape->_curve) { + Geom::PathVector const & new_vect = shape->_curve->get_pathvector(); + if(combined_pathvector.empty()){ + new_combined_pathvector = new_vect * tfc; + } + else { + new_combined_pathvector = sp_pathvector_boolop(new_vect * tfc, combined_pathvector, bool_op_union , (FillRule) fill_oddEven, (FillRule) fill_oddEven); + } + } + return new_combined_pathvector; +} + +unsigned int PrintEmf::fill( + Inkscape::Extension::Print * /*mod*/, + Geom::PathVector const &pathv, Geom::Affine const & /*transform*/, SPStyle const *style, + Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/) +{ + char *rec; + using Geom::X; + using Geom::Y; + Geom::Affine tf = m_tr_stack.top(); + + do_clip_if_present(style); // If clipping is needed set it up + + use_fill = true; + use_stroke = false; + + fill_transform = tf; + + int brush_stat = create_brush(style, nullptr); + + /* native linear gradients are only used if the object is a rectangle AND the gradient is parallel to the sides of the object */ + bool is_Rect = false; + double angle; + int rectDir=0; + Geom::Path pathRect; + if(FixPPTLinGrad && brush_stat && gv.mode == DRAW_LINEAR_GRADIENT){ + Geom::PathVector pvr = pathv * fill_transform; + pathRect = pathv_to_rect(pvr, &is_Rect, &angle); + if(is_Rect){ + /* Gradientfill records can only be used if the gradient is parallel to the sides of the rectangle. + That must be checked here so that we can fall back to another form of gradient fill if it is not + the case. */ + rectDir = vector_rect_alignment(angle, (gv.p2 - gv.p1) * fill_transform); + if(!rectDir)is_Rect = false; + } + if(!is_Rect && !FixPPTGrad2Polys)brush_stat=0; // fall all the way back to a solid fill + } + + if (brush_stat) { // only happens if the style is a gradient + /* + Handle gradients. Uses modified livarot as 2geom boolops is currently broken. + Can handle gradients with multiple stops. + + The overlap is needed to avoid antialiasing artifacts when edges are not strictly aligned on pixel boundaries. + There is an inevitable loss of accuracy saving through an EMF file because of the integer coordinate system. + Keep the overlap quite large so that loss of accuracy does not remove an overlap. + */ + destroy_pen(); //this sets the NULL_PEN, otherwise gradient slices may display with boundaries, see longer explanation below + Geom::Path cutter; + float rgb[3]; + U_COLORREF wc, c1, c2; + FillRule frb = SPWR_to_LVFR((SPWindRule) style->fill_rule.computed); + double doff, doff_base, doff_range; + double divisions = 128.0; + int nstops; + int istop = 1; + float opa; // opacity at stop + + SPRadialGradient *tg = (SPRadialGradient *)(gv.grad); // linear/radial are the same here + nstops = tg->vector.stops.size(); + tg->vector.stops[0].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[0].opacity; // first stop + c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + tg->vector.stops[nstops - 1].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[nstops - 1].opacity; // last stop + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + + doff = 0.0; + doff_base = 0.0; + doff_range = tg->vector.stops[1].offset; // next or last stop + + if (gv.mode == DRAW_RADIAL_GRADIENT) { + Geom::Point xv = gv.p2 - gv.p1; // X' vector + Geom::Point yv = gv.p3 - gv.p1; // Y' vector + Geom::Point xuv = Geom::unit_vector(xv); // X' unit vector + double rx = hypot(xv[X], xv[Y]); + double ry = hypot(yv[X], yv[Y]); + double range = fmax(rx, ry); // length along the gradient + double step = range / divisions; // adequate approximation for gradient + double overlap = step / 4.0; // overlap slices slightly + double start; + double stop; + Geom::PathVector pathvc, pathvr; + + /* radial gradient might stop part way through the shape, fill with outer color from there to "infinity". + Do this first so that outer colored ring will overlay it. + */ + pathvc = center_elliptical_hole_as_SVG_PathV(gv.p1, rx * (1.0 - overlap / range), ry * (1.0 - overlap / range), asin(xuv[Y])); + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_oddEven, frb); + wc = weight_opacity(c2); + (void) create_brush(style, &wc); + print_pathv(pathvr, fill_transform); + + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + + for (start = 0.0; start < range; start += step, doff += 1. / divisions) { + stop = start + step + overlap; + if (stop > range) { + stop = range; + } + wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base)); + (void) create_brush(style, &wc); + + pathvc = center_elliptical_ring_as_SVG_PathV(gv.p1, rx * start / range, ry * start / range, rx * stop / range, ry * stop / range, asin(xuv[Y])); + + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); // show the intersection + + if (doff >= doff_range) { + istop++; + if (istop >= nstops) { + istop = nstops - 1; + continue; // could happen on a rounding error + } + doff_base = doff_range; + doff_range = tg->vector.stops[istop].offset; // next or last stop + c1 = c2; + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + } + } + } else if (gv.mode == DRAW_LINEAR_GRADIENT) { + if(is_Rect){ + int gMode; + Geom::Point ul, ur, lr; + Geom::Point outUL, outLR; // UL,LR corners of a stop rectangle, in OUTPUT coordinates + U_TRIVERTEX ut[2]; + U_GRADIENT4 ug4; + U_RECTL rcb; + U_XFORM tmpTransform; + double wRect, hRect; + + /* coordinates: upper left, upper right, and lower right corners of the rectangle. + inkscape transform already applied, but needs to be scaled to EMF coordinates. */ + ul = get_pathrect_corner(pathRect, angle, 0) * PX2WORLD; + ur = get_pathrect_corner(pathRect, angle, 1) * PX2WORLD; + lr = get_pathrect_corner(pathRect, angle, 2) * PX2WORLD; + wRect = Geom::distance(ul,ur); + hRect = Geom::distance(ur,lr); + + /* The basic rectangle for all of these is placed with its UL corner at 0,0 with a size wRect,hRect. + Apply a world transform to place/scale it into the appropriate position on the drawing. + Actual gradientfill records are either this entire rectangle or slices of it as defined by the stops. + This rectangle has already been transformed by tf (whatever rotation/scale) Inkscape had applied to it. + */ + + Geom::Affine tf2 = Geom::Rotate(-angle); // the rectangle may be drawn skewed to the coordinate system + tmpTransform.eM11 = tf2[0]; + tmpTransform.eM12 = tf2[1]; + tmpTransform.eM21 = tf2[2]; + tmpTransform.eM22 = tf2[3]; + tmpTransform.eDx = round((ul)[Geom::X]); // use explicit round for better stability + tmpTransform.eDy = round((ul)[Geom::Y]); + + rec = U_EMRSAVEDC_set(); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at U_EMRSAVEDC_set"); + } + + rec = U_EMRMODIFYWORLDTRANSFORM_set(tmpTransform, U_MWT_LEFTMULTIPLY); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at EMRMODIFYWORLDTRANSFORM"); + } + + for(;istop<nstops;istop++){ + doff_range = tg->vector.stops[istop].offset; // next or last stop + if(rectDir == 1 || rectDir == 2){ + outUL = Geom::Point(doff_base *wRect, 0 ); + outLR = Geom::Point(doff_range*wRect, hRect); + gMode = U_GRADIENT_FILL_RECT_H; + } + else { + outUL = Geom::Point(0, doff_base *hRect); + outLR = Geom::Point(wRect,doff_range*hRect); + gMode = U_GRADIENT_FILL_RECT_V; + } + + doff_base = doff_range; + rcb.left = round(outUL[X]); // use explicit round for better stability + rcb.top = round(outUL[Y]); + rcb.right = round(outLR[X]); + rcb.bottom = round(outLR[Y]); + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + + if(rectDir == 2 || rectDir == 4){ // gradient is reversed, so swap colors + ut[0] = make_trivertex(outUL, c2); + ut[1] = make_trivertex(outLR, c1); + } + else { + ut[0] = make_trivertex(outUL, c1); + ut[1] = make_trivertex(outLR, c2); + } + c1 = c2; // for next stop + ug4.UpperLeft = 0; + ug4.LowerRight= 1; + rec = U_EMRGRADIENTFILL_set(rcb, 2, 1, gMode, ut, (uint32_t *) &ug4 ); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::fill at U_EMRGRADIENTFILL_set"); + } + } + + rec = U_EMRRESTOREDC_set(-1); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::fill at U_EMRRESTOREDC_set"); + } + } + else { + Geom::Point uv = Geom::unit_vector(gv.p2 - gv.p1); // unit vector + Geom::Point puv = uv.cw(); // perp. to unit vector + double range = Geom::distance(gv.p1, gv.p2); // length along the gradient + double step = range / divisions; // adequate approximation for gradient + double overlap = step / 4.0; // overlap slices slightly + double start; + double stop; + Geom::PathVector pathvc, pathvr; + + /* before lower end of gradient, overlap first slice position */ + wc = weight_opacity(c1); + (void) create_brush(style, &wc); + pathvc = rect_cutter(gv.p1, uv * (overlap), uv * (-50000.0), puv * 50000.0); + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); + + /* after high end of gradient, overlap last slice position */ + wc = weight_opacity(c2); + (void) create_brush(style, &wc); + pathvc = rect_cutter(gv.p2, uv * (-overlap), uv * (50000.0), puv * 50000.0); + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); + + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + + for (start = 0.0; start < range; start += step, doff += 1. / divisions) { + stop = start + step + overlap; + if (stop > range) { + stop = range; + } + pathvc = rect_cutter(gv.p1, uv * start, uv * stop, puv * 50000.0); + + wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base)); + (void) create_brush(style, &wc); + Geom::PathVector pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); // show the intersection + + if (doff >= doff_range) { + istop++; + if (istop >= nstops) { + istop = nstops - 1; + continue; // could happen on a rounding error + } + doff_base = doff_range; + doff_range = tg->vector.stops[istop].offset; // next or last stop + c1 = c2; + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + } + } + } + } else { + g_error("Fatal programming error in PrintEmf::fill, invalid gradient type detected"); + } + use_fill = false; // gradients handled, be sure stroke does not use stroke and fill + } else { + /* + Inkscape was not calling create_pen for objects with no border. + This was because it never called stroke() (next method). + PPT, and presumably others, pick whatever they want for the border if it is not specified, so no border can + become a visible border. + To avoid this force the pen to NULL_PEN if we can determine that no pen will be needed after the fill. + */ + if (style->stroke.noneSet || style->stroke_width.computed == 0.0) { + destroy_pen(); //this sets the NULL_PEN + } + + /* postpone fill in case stroke also required AND all stroke paths closed + Dashes converted to line segments will "open" a closed path. + */ + bool all_closed = true; + for (const auto & pit : pathv) { + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + if (pit.end_default() != pit.end_closed()) { + all_closed = false; + } + } + } + if ( + (style->stroke.isNone() || style->stroke.noneSet || style->stroke_width.computed == 0.0) || + (!style->stroke_dasharray.values.empty() && FixPPTDashLine) || + !all_closed + ) { + print_pathv(pathv, fill_transform); // do any fills. side effect: clears fill_pathv + use_fill = false; + } + } + + return 0; +} + + +unsigned int PrintEmf::stroke( + Inkscape::Extension::Print * /*mod*/, + Geom::PathVector const &pathv, const Geom::Affine &/*transform*/, const SPStyle *style, + Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/) +{ + + char *rec = nullptr; + Geom::Affine tf = m_tr_stack.top(); + do_clip_if_present(style); // If clipping is needed set it up + + use_stroke = true; + // use_fill was set in ::fill, if it is needed + + if (create_pen(style, tf)) { + return 0; + } + + if (!style->stroke_dasharray.values.empty() && FixPPTDashLine) { + // convert the path, gets its complete length, and then make a new path with parameter length instead of t + Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw; // pathv-> sbasis + Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw2; // sbasis using arc length parameter + Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw3; // new (discontinuous) path, composed of dots/dashes + Geom::Piecewise<Geom::D2<Geom::SBasis> > first_frag; // first fragment, will be appended at end + int n_dash = style->stroke_dasharray.values.size(); + int i = 0; //dash index + double tlength; // length of tmp_pathpw + double slength = 0.0; // start of gragment + double elength; // end of gragment + for (const auto & i : pathv) { + tmp_pathpw.concat(i.toPwSb()); + } + tlength = length(tmp_pathpw, 0.1); + tmp_pathpw2 = arc_length_parametrization(tmp_pathpw); + + // go around the dash array repeatedly until the entire path is consumed (but not beyond). + while (slength < tlength) { + elength = slength + style->stroke_dasharray.values[i++].value; + if (elength > tlength) { + elength = tlength; + } + Geom::Piecewise<Geom::D2<Geom::SBasis> > fragment(portion(tmp_pathpw2, slength, elength)); + if (slength) { + tmp_pathpw3.concat(fragment); + } else { + first_frag = fragment; + } + slength = elength; + slength += style->stroke_dasharray.values[i++].value; // the gap + if (i >= n_dash) { + i = 0; + } + } + tmp_pathpw3.concat(first_frag); // may merge line around start point + Geom::PathVector out_pathv = Geom::path_from_piecewise(tmp_pathpw3, 0.01); + print_pathv(out_pathv, tf); + } else { + print_pathv(pathv, tf); + } + + use_stroke = false; + use_fill = false; + + if (usebk) { // OPAQUE was set, revert to TRANSPARENT + usebk = false; + rec = U_EMRSETBKMODE_set(U_TRANSPARENT); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::stroke at U_EMRSETBKMODE_set"); + } + } + + return 0; +} + + +// Draws simple_shapes, those with closed EMR_* primitives, like polygons, rectangles and ellipses. +// These use whatever the current pen/brush are and need not be followed by a FILLPATH or STROKEPATH. +// For other paths it sets a few flags and returns. +bool PrintEmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Affine &transform) +{ + + Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv * transform); + + int nodes = 0; + int moves = 0; + int lines = 0; + int curves = 0; + char *rec = nullptr; + + for (auto & pit : pv) { + moves++; + nodes++; + + for (Geom::Path::iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + nodes++; + + if (is_straight_curve(*cit)) { + lines++; + } else if (dynamic_cast<Geom::CubicBezier const *>(&*cit)) { + curves++; + } + } + } + + if (!nodes) { + return false; + } + + U_POINT *lpPoints = new U_POINT[moves + lines + curves * 3]; + int i = 0; + + /** + * For all Subpaths in the <path> + */ + for (auto & pit : pv) { + using Geom::X; + using Geom::Y; + + Geom::Point p0 = pit.initialPoint(); + + p0[X] = (p0[X] * PX2WORLD); + p0[Y] = (p0[Y] * PX2WORLD); + + int32_t const x0 = (int32_t) round(p0[X]); + int32_t const y0 = (int32_t) round(p0[Y]); + + lpPoints[i].x = x0; + lpPoints[i].y = y0; + i = i + 1; + + /** + * For all segments in the subpath + */ + for (Geom::Path::iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + if (is_straight_curve(*cit)) { + //Geom::Point p0 = cit->initialPoint(); + Geom::Point p1 = cit->finalPoint(); + + //p0[X] = (p0[X] * PX2WORLD); + p1[X] = (p1[X] * PX2WORLD); + //p0[Y] = (p0[Y] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + + //int32_t const x0 = (int32_t) round(p0[X]); + //int32_t const y0 = (int32_t) round(p0[Y]); + int32_t const x1 = (int32_t) round(p1[X]); + int32_t const y1 = (int32_t) round(p1[Y]); + + lpPoints[i].x = x1; + lpPoints[i].y = y1; + i = i + 1; + } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) { + std::vector<Geom::Point> points = cubic->controlPoints(); + //Geom::Point p0 = points[0]; + Geom::Point p1 = points[1]; + Geom::Point p2 = points[2]; + Geom::Point p3 = points[3]; + + //p0[X] = (p0[X] * PX2WORLD); + p1[X] = (p1[X] * PX2WORLD); + p2[X] = (p2[X] * PX2WORLD); + p3[X] = (p3[X] * PX2WORLD); + //p0[Y] = (p0[Y] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + p2[Y] = (p2[Y] * PX2WORLD); + p3[Y] = (p3[Y] * PX2WORLD); + + //int32_t const x0 = (int32_t) round(p0[X]); + //int32_t const y0 = (int32_t) round(p0[Y]); + int32_t const x1 = (int32_t) round(p1[X]); + int32_t const y1 = (int32_t) round(p1[Y]); + int32_t const x2 = (int32_t) round(p2[X]); + int32_t const y2 = (int32_t) round(p2[Y]); + int32_t const x3 = (int32_t) round(p3[X]); + int32_t const y3 = (int32_t) round(p3[Y]); + + lpPoints[i].x = x1; + lpPoints[i].y = y1; + lpPoints[i + 1].x = x2; + lpPoints[i + 1].y = y2; + lpPoints[i + 2].x = x3; + lpPoints[i + 2].y = y3; + i = i + 3; + } + } + } + + bool done = false; + bool closed = (lpPoints[0].x == lpPoints[i - 1].x) && (lpPoints[0].y == lpPoints[i - 1].y); + bool polygon = false; + bool rectangle = false; + bool ellipse = false; + + if (moves == 1 && moves + lines == nodes && closed) { + polygon = true; + // if (nodes==5) { // disable due to LP Bug 407394 + // if (lpPoints[0].x == lpPoints[3].x && lpPoints[1].x == lpPoints[2].x && + // lpPoints[0].y == lpPoints[1].y && lpPoints[2].y == lpPoints[3].y) + // { + // rectangle = true; + // } + // } + } else if (moves == 1 && nodes == 5 && moves + curves == nodes && closed) { + // if (lpPoints[0].x == lpPoints[1].x && lpPoints[1].x == lpPoints[11].x && + // lpPoints[5].x == lpPoints[6].x && lpPoints[6].x == lpPoints[7].x && + // lpPoints[2].x == lpPoints[10].x && lpPoints[3].x == lpPoints[9].x && lpPoints[4].x == lpPoints[8].x && + // lpPoints[2].y == lpPoints[3].y && lpPoints[3].y == lpPoints[4].y && + // lpPoints[8].y == lpPoints[9].y && lpPoints[9].y == lpPoints[10].y && + // lpPoints[5].y == lpPoints[1].y && lpPoints[6].y == lpPoints[0].y && lpPoints[7].y == lpPoints[11].y) + // { // disable due to LP Bug 407394 + // ellipse = true; + // } + } + + if (polygon || ellipse) { + + if (use_fill && !use_stroke) { // only fill + rec = selectobject_set(U_NULL_PEN, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set pen"); + } + } else if (!use_fill && use_stroke) { // only stroke + rec = selectobject_set(U_NULL_BRUSH, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set brush"); + } + } + + if (polygon) { + if (rectangle) { + U_RECTL rcl = rectl_set((U_POINTL) { + lpPoints[0].x, lpPoints[0].y + }, (U_POINTL) { + lpPoints[2].x, lpPoints[2].y + }); + rec = U_EMRRECTANGLE_set(rcl); + } else { + rec = U_EMRPOLYGON_set(U_RCL_DEF, nodes, lpPoints); + } + } else if (ellipse) { + U_RECTL rcl = rectl_set((U_POINTL) { + lpPoints[6].x, lpPoints[3].y + }, (U_POINTL) { + lpPoints[0].x, lpPoints[9].y + }); + rec = U_EMRELLIPSE_set(rcl); + } + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_simple_shape at retangle/ellipse/polygon"); + } + + done = true; + + // replace the handle we moved above, assuming there was something set already + if (use_fill && !use_stroke && hpen) { // only fill + rec = selectobject_set(hpen, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set pen"); + } + } else if (!use_fill && use_stroke && hbrush) { // only stroke + rec = selectobject_set(hbrush, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set brush"); + } + } + } + + delete[] lpPoints; + + return done; +} + +/** Some parts based on win32.cpp by Lauris Kaplinski <lauris@kaplinski.com>. Was a part of Inkscape + in the past (or will be in the future?) Not in current trunk. (4/19/2012) + + Limitations of this code: + 1. Transparency is lost on export. (Apparently a limitation of the EMF format.) + 2. Probably messes up if row stride != w*4 + 3. There is still a small memory leak somewhere, possibly in a pixbuf created in a routine + that calls this one and passes px, but never removes the rest of the pixbuf. The first time + this is called it leaked 5M (in one test) and each subsequent call leaked around 200K more. + If this routine is reduced to + if(1)return(0); + and called for a single 1280 x 1024 image then the program leaks 11M per call, or roughly the + size of two bitmaps. +*/ + +unsigned int PrintEmf::image( + Inkscape::Extension::Print * /* module */, /** not used */ + unsigned char *rgba_px, /** array of pixel values, Gdk::Pixbuf bitmap format */ + unsigned int w, /** width of bitmap */ + unsigned int h, /** height of bitmap */ + unsigned int rs, /** row stride (normally w*4) */ + Geom::Affine const &tf_rect, /** affine transform only used for defining location and size of rect, for all other transforms, use the one from m_tr_stack */ + SPStyle const *style) /** provides indirect link to image object */ +{ + double x1, y1, dw, dh; + char *rec = nullptr; + Geom::Affine tf = m_tr_stack.top(); + + do_clip_if_present(style); // If clipping is needed set it up + + rec = U_EMRSETSTRETCHBLTMODE_set(U_COLORONCOLOR); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at EMRHEADER"); + } + + x1 = tf_rect[4]; + y1 = tf_rect[5]; + dw = ((double) w) * tf_rect[0]; + dh = ((double) h) * tf_rect[3]; + Geom::Point pLL(x1, y1); + Geom::Point pLL2 = pLL * tf; //location of LL corner in Inkscape coordinates + + char *px; + uint32_t cbPx; + uint32_t colortype; + PU_RGBQUAD ct; + int numCt; + U_BITMAPINFOHEADER Bmih; + PU_BITMAPINFO Bmi; + colortype = U_BCBM_COLOR32; + (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, (char *) rgba_px, w, h, w * 4, colortype, 0, 1); + Bmih = bitmapinfoheader_set(w, h, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0); + Bmi = bitmapinfo_set(Bmih, ct); + + U_POINTL Dest = pointl_set(round(pLL2[Geom::X] * PX2WORLD), round(pLL2[Geom::Y] * PX2WORLD)); + U_POINTL cDest = pointl_set(round(dw * PX2WORLD), round(dh * PX2WORLD)); + U_POINTL Src = pointl_set(0, 0); + U_POINTL cSrc = pointl_set(w, h); + /* map the integer Dest coordinates back into pLL2, so that the rounded part does not destabilize the transform offset below */ + pLL2[Geom::X] = Dest.x; + pLL2[Geom::Y] = Dest.y; + pLL2 /= PX2WORLD; + if (!FixImageRot) { /* Rotate images - some programs cannot read them in correctly if they are rotated */ + tf[4] = tf[5] = 0.0; // get rid of the offset in the transform + Geom::Point pLL2prime = pLL2 * tf; + U_XFORM tmpTransform; + tmpTransform.eM11 = tf[0]; + tmpTransform.eM12 = tf[1]; + tmpTransform.eM21 = tf[2]; + tmpTransform.eM22 = tf[3]; + tmpTransform.eDx = (pLL2[Geom::X] - pLL2prime[Geom::X]) * PX2WORLD; + tmpTransform.eDy = (pLL2[Geom::Y] - pLL2prime[Geom::Y]) * PX2WORLD; + + rec = U_EMRSAVEDC_set(); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at U_EMRSAVEDC_set"); + } + + rec = U_EMRMODIFYWORLDTRANSFORM_set(tmpTransform, U_MWT_LEFTMULTIPLY); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at EMRMODIFYWORLDTRANSFORM"); + } + } + rec = U_EMRSTRETCHDIBITS_set( + U_RCL_DEF, //! Bounding rectangle in device units + Dest, //! Destination UL corner in logical units + cDest, //! Destination W & H in logical units + Src, //! Source UL corner in logical units + cSrc, //! Source W & H in logical units + U_DIB_RGB_COLORS, //! DIBColors Enumeration + U_SRCCOPY, //! RasterOPeration Enumeration + Bmi, //! (Optional) bitmapbuffer (U_BITMAPINFO section) + h * rs, //! size in bytes of px + px //! (Optional) bitmapbuffer (U_BITMAPINFO section) + ); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at U_EMRSTRETCHDIBITS_set"); + } + free(px); + free(Bmi); + if (numCt) { + free(ct); + } + + if (!FixImageRot) { + rec = U_EMRRESTOREDC_set(-1); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::image at U_EMRRESTOREDC_set"); + } + } + + return 0; +} + +unsigned int PrintEmf::draw_pathv_to_EMF(Geom::PathVector const &pathv, const Geom::Affine &transform) { + char *rec; + + /* inkscape to EMF scaling is done below, but NOT the rotation/translation transform, + that is handled by the EMF MODIFYWORLDTRANSFORM record + */ + + Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv * transform); + + rec = U_EMRBEGINPATH_set(); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRBEGINPATH_set"); + } + + /** + * For all Subpaths in the <path> + */ + for (const auto & pit : pv) { + using Geom::X; + using Geom::Y; + + + Geom::Point p0 = pit.initialPoint(); + + p0[X] = (p0[X] * PX2WORLD); + p0[Y] = (p0[Y] * PX2WORLD); + + U_POINTL ptl = pointl_set((int32_t) round(p0[X]), (int32_t) round(p0[Y])); + rec = U_EMRMOVETOEX_set(ptl); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRMOVETOEX_set"); + } + + /** + * For all segments in the subpath + */ + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + if (is_straight_curve(*cit)) { + //Geom::Point p0 = cit->initialPoint(); + Geom::Point p1 = cit->finalPoint(); + + //p0[X] = (p0[X] * PX2WORLD); + p1[X] = (p1[X] * PX2WORLD); + //p0[Y] = (p0[Y] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + + //int32_t const x0 = (int32_t) round(p0[X]); + //int32_t const y0 = (int32_t) round(p0[Y]); + + ptl = pointl_set((int32_t) round(p1[X]), (int32_t) round(p1[Y])); + rec = U_EMRLINETO_set(ptl); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRLINETO_set"); + } + } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) { + std::vector<Geom::Point> points = cubic->controlPoints(); + //Geom::Point p0 = points[0]; + Geom::Point p1 = points[1]; + Geom::Point p2 = points[2]; + Geom::Point p3 = points[3]; + + //p0[X] = (p0[X] * PX2WORLD); + p1[X] = (p1[X] * PX2WORLD); + p2[X] = (p2[X] * PX2WORLD); + p3[X] = (p3[X] * PX2WORLD); + //p0[Y] = (p0[Y] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + p2[Y] = (p2[Y] * PX2WORLD); + p3[Y] = (p3[Y] * PX2WORLD); + + //int32_t const x0 = (int32_t) round(p0[X]); + //int32_t const y0 = (int32_t) round(p0[Y]); + int32_t const x1 = (int32_t) round(p1[X]); + int32_t const y1 = (int32_t) round(p1[Y]); + int32_t const x2 = (int32_t) round(p2[X]); + int32_t const y2 = (int32_t) round(p2[Y]); + int32_t const x3 = (int32_t) round(p3[X]); + int32_t const y3 = (int32_t) round(p3[Y]); + + U_POINTL pt[3]; + pt[0].x = x1; + pt[0].y = y1; + pt[1].x = x2; + pt[1].y = y2; + pt[2].x = x3; + pt[2].y = y3; + + rec = U_EMRPOLYBEZIERTO_set(U_RCL_DEF, 3, pt); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRPOLYBEZIERTO_set"); + } + } else { + g_warning("logical error, because pathv_to_linear_and_cubic_beziers was used"); + } + } + + if (pit.end_default() == pit.end_closed()) { // there may be multiples of this on a single path + rec = U_EMRCLOSEFIGURE_set(); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRCLOSEFIGURE_set"); + } + } + + } + + rec = U_EMRENDPATH_set(); // there may be only be one of these on a single path + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRENDPATH_set"); + } + return(0); +} + +// may also be called with a simple_shape or an empty path, whereupon it just returns without doing anything +unsigned int PrintEmf::print_pathv(Geom::PathVector const &pathv, const Geom::Affine &transform) +{ + Geom::Affine tf = transform; + char *rec = nullptr; + + simple_shape = print_simple_shape(pathv, tf); + if (simple_shape || pathv.empty()) { + if (use_fill) { + destroy_brush(); // these must be cleared even if nothing is drawn or hbrush,hpen fill up + } + if (use_stroke) { + destroy_pen(); + } + return TRUE; + } + + (void) draw_pathv_to_EMF(pathv, tf); + + // explicit FILL/STROKE commands are needed for each sub section of the path + if (use_fill && !use_stroke) { + rec = U_EMRFILLPATH_set(U_RCL_DEF); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::fill at U_EMRFILLPATH_set"); + } + } else if (use_fill && use_stroke) { + rec = U_EMRSTROKEANDFILLPATH_set(U_RCL_DEF); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::stroke at U_EMRSTROKEANDFILLPATH_set"); + } + } else if (!use_fill && use_stroke) { + rec = U_EMRSTROKEPATH_set(U_RCL_DEF); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::stroke at U_EMRSTROKEPATH_set"); + } + } + + // clean out brush and pen, but only after all parts of the draw complete + if (use_fill) { + destroy_brush(); + } + if (use_stroke) { + destroy_pen(); + } + + return TRUE; +} + + +unsigned int PrintEmf::text(Inkscape::Extension::Print * /*mod*/, char const *text, Geom::Point const &p, + SPStyle const *const style) +{ + if (!et || !text) { + return 0; + } + + do_clip_if_present(style); // If clipping is needed set it up + char *rec = nullptr; + int ccount, newfont; + int fix90n = 0; + uint32_t hfont = 0; + Geom::Affine tf = m_tr_stack.top(); + double rot = -1800.0 * std::atan2(tf[1], tf[0]) / M_PI; // 0.1 degree rotation, - sign for MM_TEXT + double rotb = -std::atan2(tf[1], tf[0]); // rotation for baseline offset for superscript/subscript, used below + double dx, dy; + double ky; + + // the dx array is smuggled in like: text<nul>w1 w2 w3 ...wn<nul><nul>, where the widths are floats 7 characters wide, including the space + int ndx, rtl; + uint32_t *adx; + smuggle_adxkyrtl_out(text, &adx, &ky, &rtl, &ndx, PX2WORLD * std::min(tf.expansionX(), tf.expansionY())); // side effect: free() adx + + uint32_t textalignment; + if (rtl > 0) { + textalignment = U_TA_BASELINE | U_TA_LEFT; + } else { + textalignment = U_TA_BASELINE | U_TA_RIGHT | U_TA_RTLREADING; + } + if (textalignment != htextalignment) { + htextalignment = textalignment; + rec = U_EMRSETTEXTALIGN_set(textalignment); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTALIGN_set"); + } + } + + char *text2 = strdup(text); // because U_Utf8ToUtf16le calls iconv which does not like a const char * + uint16_t *unicode_text = U_Utf8ToUtf16le(text2, 0, nullptr); + free(text2); + //translates Unicode to NonUnicode, if possible. If any translate, all will, and all to + //the same font, because of code in Layout::print + UnicodeToNon(unicode_text, &ccount, &newfont); + + //PPT gets funky with text within +-1 degree of a multiple of 90, but only for SOME fonts.Snap those to the central value + //Some funky ones: Arial, Times New Roman + //Some not funky ones: Symbol and Verdana. + //Without a huge table we cannot catch them all, so just the most common problem ones. + FontfixParams params; + + if (FixPPTCharPos) { + switch (newfont) { + case CVTSYM: + _lookup_ppt_fontfix("Convert To Symbol", params); + break; + case CVTZDG: + _lookup_ppt_fontfix("Convert To Zapf Dingbats", params); + break; + case CVTWDG: + _lookup_ppt_fontfix("Convert To Wingdings", params); + break; + default: //also CVTNON + _lookup_ppt_fontfix(style->font_family.value(), params); + break; + } + if (params.f2 != 0 || params.f3 != 0) { + int irem = ((int) round(rot)) % 900 ; + if (irem <= 9 && irem >= -9) { + fix90n = 1; //assume vertical + rot = (double)(((int) round(rot)) - irem); + rotb = rot * M_PI / 1800.0; + if (std::abs(rot) == 900.0) { + fix90n = 2; + } + } + } + } + + /* Note that text font sizes are stored into the EMF as fairly small integers and that limits their precision. + The EMF output files produced here have been designed so that the integer valued pt sizes + land right on an integer value in the EMF file, so those are exact. However, something like 18.1 pt will be + somewhat off, so that when it is read back in it becomes 18.11 pt. (For instance.) + */ + int textheight = round(-style->font_size.computed * PX2WORLD * std::min(tf.expansionX(), tf.expansionY())); + + if (!hfont) { + // Get font face name. Use changed font name if unicode mapped to one + // of the special fonts. + uint16_t *wfacename; + if (!newfont) { + wfacename = U_Utf8ToUtf16le(style->font_family.value(), 0, nullptr); + } else { + wfacename = U_Utf8ToUtf16le(FontName(newfont), 0, nullptr); + } + + // Scale the text to the minimum stretch. (It tends to stay within bounding rectangles even if + // it was streteched asymmetrically.) Few applications support text from EMF which is scaled + // differently by height/width, so leave lfWidth alone. + + U_LOGFONT lf = logfont_set( + textheight, + 0, + round(rot), + round(rot), + _translate_weight(style->font_weight.computed), + (style->font_style.computed == SP_CSS_FONT_STYLE_ITALIC), + style->text_decoration_line.underline, + style->text_decoration_line.line_through, + U_DEFAULT_CHARSET, + U_OUT_DEFAULT_PRECIS, + U_CLIP_DEFAULT_PRECIS, + U_DEFAULT_QUALITY, + U_DEFAULT_PITCH | U_FF_DONTCARE, + wfacename); + free(wfacename); + + rec = extcreatefontindirectw_set(&hfont, eht, (char *) &lf, nullptr); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::text at extcreatefontindirectw_set"); + } + } + + rec = selectobject_set(hfont, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::text at selectobject_set"); + } + + float rgb[3]; + style->fill.value.color.get_rgb_floatv(rgb); + // only change the text color when it needs to be changed + if (memcmp(htextcolor_rgb, rgb, 3 * sizeof(float))) { + memcpy(htextcolor_rgb, rgb, 3 * sizeof(float)); + rec = U_EMRSETTEXTCOLOR_set(U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2])); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTCOLOR_set"); + } + } + + Geom::Point p2 = p * tf; + + //Handle super/subscripts and vertical kerning + /* Previously used this, but vertical kerning was not supported + p2[Geom::X] -= style->baseline_shift.computed * std::sin( rotb ); + p2[Geom::Y] -= style->baseline_shift.computed * std::cos( rotb ); + */ + p2[Geom::X] += ky * std::sin(rotb); + p2[Geom::Y] += ky * std::cos(rotb); + + //Conditionally handle compensation for PPT EMF import bug (affects PPT 2003-2010, at least) + if (FixPPTCharPos) { + if (fix90n == 1) { //vertical + dx = 0.0; + dy = params.f3 * style->font_size.computed * std::cos(rotb); + } else if (fix90n == 2) { //horizontal + dx = params.f2 * style->font_size.computed * std::sin(rotb); + dy = 0.0; + } else { + dx = params.f1 * style->font_size.computed * std::sin(rotb); + dy = params.f1 * style->font_size.computed * std::cos(rotb); + } + p2[Geom::X] += dx; + p2[Geom::Y] += dy; + } + + p2[Geom::X] = (p2[Geom::X] * PX2WORLD); + p2[Geom::Y] = (p2[Geom::Y] * PX2WORLD); + + int32_t const xpos = (int32_t) round(p2[Geom::X]); + int32_t const ypos = (int32_t) round(p2[Geom::Y]); + + + // The number of characters in the string is a bit fuzzy. ndx, the number of entries in adx is + // the number of VISIBLE characters, since some may combine from the UTF (8 originally, + // now 16) encoding. Conversely strlen() or wchar16len() would give the absolute number of + // encoding characters. Unclear if emrtext wants the former or the latter but for now assume the former. + + // This is currently being smuggled in from caller as part of text, works + // MUCH better than the fallback hack below + // uint32_t *adx = dx_set(textheight, U_FW_NORMAL, slen); // dx is needed, this makes one up + char *rec2; + if (rtl > 0) { + rec2 = emrtext_set((U_POINTL) { + xpos, ypos + }, ndx, 2, unicode_text, U_ETO_NONE, U_RCL_DEF, adx); + } else { // RTL text, U_TA_RTLREADING should be enough, but set this one too just in case + rec2 = emrtext_set((U_POINTL) { + xpos, ypos + }, ndx, 2, unicode_text, U_ETO_RTLREADING, U_RCL_DEF, adx); + } + free(unicode_text); + free(adx); + rec = U_EMREXTTEXTOUTW_set(U_RCL_DEF, U_GM_COMPATIBLE, 1.0, 1.0, (PU_EMRTEXT)rec2); + free(rec2); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::text at U_EMREXTTEXTOUTW_set"); + } + + // Must deselect an object before deleting it. Put the default font (back) in. + rec = selectobject_set(U_DEVICE_DEFAULT_FONT, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::text at selectobject_set"); + } + + if (hfont) { + rec = deleteobject_set(&hfont, eht); + if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) { + g_error("Fatal programming error in PrintEmf::text at deleteobject_set"); + } + } + + return 0; +} + +void PrintEmf::init() +{ + /* EMF print */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>Enhanced Metafile Print</name>\n" + "<id>org.inkscape.print.emf</id>\n" + "<param gui-hidden=\"true\" name=\"destination\" type=\"string\"></param>\n" + "<param gui-hidden=\"true\" name=\"textToPath\" type=\"bool\">true</param>\n" + "<param gui-hidden=\"true\" name=\"pageBoundingBox\" type=\"bool\">true</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTCharPos\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTDashLine\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTGrad2Polys\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTLinGrad\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTPatternAsHatch\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixImageRot\" type=\"bool\">false</param>\n" + "<print/>\n" + "</inkscape-extension>", new PrintEmf()); + + return; +} + +} /* namespace Internal */ +} /* namespace Extension */ +} /* 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/emf-print.h b/src/extension/internal/emf-print.h new file mode 100644 index 0000000..e48e332 --- /dev/null +++ b/src/extension/internal/emf-print.h @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Enhanced Metafile printing - implementation + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * David Mathog + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_EMF_PRINT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_EMF_PRINT_H + +#include <3rdparty/libuemf/uemf.h> +#include "extension/internal/metafile-print.h" + +#include "splivarot.h" // pieces for union on shapes +#include "display/canvas-bpath.h" // for SPWindRule + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class PrintEmf : public PrintMetafile +{ + uint32_t hbrush, hbrushOld, hpen; + + unsigned int print_pathv (Geom::PathVector const &pathv, const Geom::Affine &transform); + bool print_simple_shape (Geom::PathVector const &pathv, const Geom::Affine &transform); + +public: + PrintEmf(); + + /* Print functions */ + unsigned int setup (Inkscape::Extension::Print * module) override; + + unsigned int begin (Inkscape::Extension::Print * module, SPDocument *doc) override; + unsigned int finish (Inkscape::Extension::Print * module) override; + + /* Rendering methods */ + unsigned int fill (Inkscape::Extension::Print *module, + Geom::PathVector const &pathv, + Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, + Geom::OptRect const &bbox) override; + unsigned int stroke (Inkscape::Extension::Print * module, + Geom::PathVector const &pathv, + Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, + Geom::OptRect const &bbox) override; + unsigned int image(Inkscape::Extension::Print *module, + unsigned char *px, + unsigned int w, + unsigned int h, + unsigned int rs, + Geom::Affine const &transform, + SPStyle const *style) override; + unsigned int comment(Inkscape::Extension::Print *module, const char * comment) override; + unsigned int text(Inkscape::Extension::Print *module, char const *text, + Geom::Point const &p, SPStyle const *style) override; + + static void init (); +protected: + static void smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale); + + void do_clip_if_present(SPStyle const *style); + Geom::PathVector merge_PathVector_with_group(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform); + Geom::PathVector merge_PathVector_with_shape(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform); + unsigned int draw_pathv_to_EMF(Geom::PathVector const &pathv, const Geom::Affine &transform); + Geom::Path pathv_to_simple_polygon(Geom::PathVector const &pathv, int *vertices); + Geom::Path pathv_to_rect(Geom::PathVector const &pathv, bool *is_rect, double *angle); + Geom::Point get_pathrect_corner(Geom::Path pathRect, double angle, int corner); + U_TRIVERTEX make_trivertex(Geom::Point Pt, U_COLORREF uc); + int vector_rect_alignment(double angle, Geom::Point vtest); + int create_brush(SPStyle const *style, PU_COLORREF fcolor) override; + void destroy_brush() override; + int create_pen(SPStyle const *style, const Geom::Affine &transform) override; + void destroy_pen() override; +}; + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + + +#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_EMF_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/extension/internal/filter/BUILD_YOUR_OWN b/src/extension/internal/filter/BUILD_YOUR_OWN new file mode 100644 index 0000000..ba6ca5a --- /dev/null +++ b/src/extension/internal/filter/BUILD_YOUR_OWN @@ -0,0 +1,2 @@ +This directory contains filter effects. They're designed to be simle. +Very, very simple. Here is how to build your own. diff --git a/src/extension/internal/filter/bevels.h b/src/extension/internal/filter/bevels.h new file mode 100644 index 0000000..2fce96c --- /dev/null +++ b/src/extension/internal/filter/bevels.h @@ -0,0 +1,283 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BEVELS_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BEVELS_H__ +/* Change the 'BEVELS' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Bevel filters + * Diffuse light + * Matte jelly + * Specular light + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Diffuse light filter. + + Basic diffuse bevel to use for building textures + + Filter's parameters: + * Smoothness (0.->10., default 6.) -> blur (stdDeviation) + * Elevation (0->360, default 25) -> feDistantLight (elevation) + * Azimuth (0->360, default 235) -> feDistantLight (azimuth) + * Lighting color (guint, default -1 [white]) -> diffuse (lighting-color) +*/ + +class DiffuseLight : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + DiffuseLight ( ) : Filter() { }; + ~DiffuseLight ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Diffuse Light") "</name>\n" + "<id>org.inkscape.effect.filter.DiffuseLight</id>\n" + "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"10\">6</param>\n" + "<param name=\"elevation\" gui-text=\"" N_("Elevation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">25</param>\n" + "<param name=\"azimuth\" gui-text=\"" N_("Azimuth (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">235</param>\n" + "<param name=\"color\" gui-text=\"" N_("Lighting color") "\" type=\"color\">-1</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Bevels") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Basic diffuse bevel to use for building textures") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new DiffuseLight()); + }; + +}; + +gchar const * +DiffuseLight::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream smooth; + std::ostringstream elevation; + std::ostringstream azimuth; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream a; + + smooth << ext->get_param_float("smooth"); + elevation << ext->get_param_int("elevation"); + azimuth << ext->get_param_int("azimuth"); + guint32 color = ext->get_param_color("color"); + + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Diffuse Light\">\n" + "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur\" />\n" + "<feDiffuseLighting diffuseConstant=\"1\" surfaceScale=\"10\" lighting-color=\"rgb(%s,%s,%s)\" result=\"diffuse\">\n" + "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n" + "</feDiffuseLighting>\n" + "<feComposite in=\"diffuse\" in2=\"diffuse\" operator=\"arithmetic\" k1=\"1\" result=\"composite1\" />\n" + "<feComposite in=\"composite1\" in2=\"SourceGraphic\" k1=\"%s\" operator=\"arithmetic\" k3=\"1\" result=\"composite2\" />\n" + "</filter>\n", smooth.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str()); + + return _filter; +}; /* DiffuseLight filter */ + +/** + \brief Custom predefined Matte jelly filter. + + Bulging, matte jelly covering + + Filter's parameters: + * Smoothness (0.0->10., default 7.) -> blur (stdDeviation) + * Brightness (0.0->5., default .9) -> specular (specularConstant) + * Elevation (0->360, default 60) -> feDistantLight (elevation) + * Azimuth (0->360, default 225) -> feDistantLight (azimuth) + * Lighting color (guint, default -1 [white]) -> specular (lighting-color) +*/ + +class MatteJelly : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + MatteJelly ( ) : Filter() { }; + ~MatteJelly ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Matte Jelly") "</name>\n" + "<id>org.inkscape.effect.filter.MatteJelly</id>\n" + "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"10.00\">7</param>\n" + "<param name=\"bright\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"5.00\">0.9</param>\n" + "<param name=\"elevation\" gui-text=\"" N_("Elevation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">60</param>\n" + "<param name=\"azimuth\" gui-text=\"" N_("Azimuth (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">225</param>\n" + "<param name=\"color\" gui-text=\"" N_("Lighting color") "\" type=\"color\">-1</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Bevels") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Bulging, matte jelly covering") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new MatteJelly()); + }; + +}; + +gchar const * +MatteJelly::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream smooth; + std::ostringstream bright; + std::ostringstream elevation; + std::ostringstream azimuth; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream a; + + smooth << ext->get_param_float("smooth"); + bright << ext->get_param_float("bright"); + elevation << ext->get_param_int("elevation"); + azimuth << ext->get_param_int("azimuth"); + guint32 color = ext->get_param_color("color"); + + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Matte Jelly\">\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.85 0\" result=\"color\" in=\"SourceGraphic\" />\n" + "<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"%s\" result=\"blur\" />\n" + "<feSpecularLighting in=\"blur\" specularExponent=\"25\" specularConstant=\"%s\" surfaceScale=\"5\" lighting-color=\"rgb(%s,%s,%s)\" result=\"specular\">\n" + "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n" + "</feSpecularLighting>\n" + "<feComposite in=\"specular\" in2=\"SourceGraphic\" k3=\"1\" k2=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n" + "<feComposite in=\"composite1\" in2=\"color\" operator=\"atop\" result=\"composite2\" />\n" + "</filter>\n", smooth.str().c_str(), bright.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str()); + + return _filter; +}; /* MatteJelly filter */ + +/** + \brief Custom predefined Specular light filter. + + Basic specular bevel to use for building textures + + Filter's parameters: + * Smoothness (0.0->10., default 6.) -> blur (stdDeviation) + * Brightness (0.0->5., default 1.) -> specular (specularConstant) + * Elevation (0->360, default 45) -> feDistantLight (elevation) + * Azimuth (0->360, default 235) -> feDistantLight (azimuth) + * Lighting color (guint, default -1 [white]) -> specular (lighting-color) +*/ + +class SpecularLight : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + SpecularLight ( ) : Filter() { }; + ~SpecularLight ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Specular Light") "</name>\n" + "<id>org.inkscape.effect.filter.SpecularLight</id>\n" + "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"10\">6</param>\n" + "<param name=\"bright\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"5\">1</param>\n" + "<param name=\"elevation\" gui-text=\"" N_("Elevation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">45</param>\n" + "<param name=\"azimuth\" gui-text=\"" N_("Azimuth (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">235</param>\n" + "<param name=\"color\" gui-text=\"" N_("Lighting color") "\" type=\"color\">-1</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Bevels") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Basic specular bevel to use for building textures") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new SpecularLight()); + }; + +}; + +gchar const * +SpecularLight::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream smooth; + std::ostringstream bright; + std::ostringstream elevation; + std::ostringstream azimuth; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream a; + + smooth << ext->get_param_float("smooth"); + bright << ext->get_param_float("bright"); + elevation << ext->get_param_int("elevation"); + azimuth << ext->get_param_int("azimuth"); + guint32 color = ext->get_param_color("color"); + + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Specular Light\">\n" + "<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"%s\" result=\"blur\" />\n" + "<feSpecularLighting in=\"blur\" specularExponent=\"25\" specularConstant=\"%s\" surfaceScale=\"10\" lighting-color=\"rgb(%s,%s,%s)\" result=\"specular\">\n" + "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n" + "</feSpecularLighting>\n" + "<feComposite in=\"specular\" in2=\"SourceGraphic\" k3=\"1\" k2=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n" + "<feComposite in=\"composite1\" in2=\"SourceAlpha\" operator=\"in\" result=\"composite2\" />\n" + "</filter>\n", smooth.str().c_str(), bright.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str()); + + return _filter; +}; /* SpecularLight filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'BEVELS' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BEVELS_H__ */ diff --git a/src/extension/internal/filter/blurs.h b/src/extension/internal/filter/blurs.h new file mode 100644 index 0000000..15b9c49 --- /dev/null +++ b/src/extension/internal/filter/blurs.h @@ -0,0 +1,420 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BLURS_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BLURS_H__ +/* Change the 'BLURS' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Blur filters + * Blur + * Clean edges + * Cross blur + * Feather + * Out of focus + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Blur filter. + + Simple horizontal and vertical blur + + Filter's parameters: + * Horizontal blur (0.01->100., default 2) -> blur (stdDeviation) + * Vertical blur (0.01->100., default 2) -> blur (stdDeviation) + * Blur content only (boolean, default false) -> +*/ + +class Blur : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Blur ( ) : Filter() { }; + ~Blur ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Blur") "</name>\n" + "<id>org.inkscape.effect.filter.Blur</id>\n" + "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">2</param>\n" + "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">2</param>\n" + "<param name=\"content\" gui-text=\"" N_("Blur content only") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Blurs") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Simple vertical and horizontal blur effect") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Blur()); + }; + +}; + +gchar const * +Blur::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream bbox; + std::ostringstream hblur; + std::ostringstream vblur; + std::ostringstream content; + + hblur << ext->get_param_float("hblur"); + vblur << ext->get_param_float("vblur"); + + if (ext->get_param_bool("content")) { + bbox << "height=\"1\" width=\"1\" y=\"0\" x=\"0\""; + content << "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 50 0 \" result=\"colormatrix\" />\n" + << "<feComposite in=\"colormatrix\" in2=\"SourceGraphic\" operator=\"in\" />\n"; + } else { + bbox << "" ; + content << "" ; + } + + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" %s style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Blur\">\n" + "<feGaussianBlur stdDeviation=\"%s %s\" result=\"blur\" />\n" + "%s" + "</filter>\n", bbox.str().c_str(), hblur.str().c_str(), vblur.str().c_str(), content.str().c_str() ); + + return _filter; +}; /* Blur filter */ + +/** + \brief Custom predefined Clean edges filter. + + Removes or decreases glows and jaggeries around objects edges after applying some filters + + Filter's parameters: + * Strength (0.01->2., default 0.4) -> blur (stdDeviation) +*/ + +class CleanEdges : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + CleanEdges ( ) : Filter() { }; + ~CleanEdges ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Clean Edges") "</name>\n" + "<id>org.inkscape.effect.filter.CleanEdges</id>\n" + "<param name=\"blur\" gui-text=\"" N_("Strength") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"2.00\">0.4</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Blurs") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Removes or decreases glows and jaggeries around objects edges after applying some filters") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new CleanEdges()); + }; + +}; + +gchar const * +CleanEdges::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream blur; + + blur << ext->get_param_float("blur"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Clean Edges\">\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n" + "<feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"in\" result=\"composite1\" />\n" + "<feComposite in=\"composite1\" in2=\"composite1\" k2=\"1\" operator=\"in\" result=\"composite2\" />\n" + "</filter>\n", blur.str().c_str()); + + return _filter; +}; /* CleanEdges filter */ + +/** + \brief Custom predefined Cross blur filter. + + Combine vertical and horizontal blur + + Filter's parameters: + * Brightness (0.->10., default 0) -> composite (k3) + * Fading (0.->1., default 0) -> composite (k4) + * Horizontal blur (0.01->20., default 5) -> blur (stdDeviation) + * Vertical blur (0.01->20., default 5) -> blur (stdDeviation) + * Blend mode (enum, default Darken) -> blend (mode) +*/ + +class CrossBlur : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + CrossBlur ( ) : Filter() { }; + ~CrossBlur ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Cross Blur") "</name>\n" + "<id>org.inkscape.effect.filter.CrossBlur</id>\n" + "<param name=\"bright\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"10.00\">0</param>\n" + "<param name=\"fade\" gui-text=\"" N_("Fading") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.00\">0</param>\n" + "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">5</param>\n" + "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">5</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Blurs") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Combine vertical and horizontal blur") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new CrossBlur()); + }; + +}; + +gchar const * +CrossBlur::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream bright; + std::ostringstream fade; + std::ostringstream hblur; + std::ostringstream vblur; + std::ostringstream blend; + + bright << ext->get_param_float("bright"); + fade << ext->get_param_float("fade"); + hblur << ext->get_param_float("hblur"); + vblur << ext->get_param_float("vblur"); + blend << ext->get_param_optiongroup("blend"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Cross Blur\">\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"colormatrix\" />\n" + "<feComposite in=\"SourceGraphic\" in2=\"colormatrix\" operator=\"arithmetic\" k2=\"1\" k3=\"%s\" k4=\"%s\" result=\"composite\" />\n" + "<feGaussianBlur stdDeviation=\"%s 0.01\" result=\"blur1\" />\n" + "<feGaussianBlur in=\"composite\" stdDeviation=\"0.01 %s\" result=\"blur2\" />\n" + "<feBlend in=\"blur2\" in2=\"blur1\" mode=\"%s\" result=\"blend\" />\n" + "</filter>\n", bright.str().c_str(), fade.str().c_str(), hblur.str().c_str(), vblur.str().c_str(), blend.str().c_str()); + + return _filter; +}; /* Cross blur filter */ + +/** + \brief Custom predefined Feather filter. + + Blurred mask on the edge without altering the contents + + Filter's parameters: + * Strength (0.01->100., default 5) -> blur (stdDeviation) +*/ + +class Feather : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Feather ( ) : Filter() { }; + ~Feather ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Feather") "</name>\n" + "<id>org.inkscape.effect.filter.Feather</id>\n" + "<param name=\"blur\" gui-text=\"" N_("Strength") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">5</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Blurs") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Blurred mask on the edge without altering the contents") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Feather()); + }; + +}; + +gchar const * +Feather::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream blur; + + blur << ext->get_param_float("blur"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Feather\">\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n" + "<feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"atop\" result=\"composite1\" />\n" + "<feComposite in2=\"composite1\" operator=\"in\" result=\"composite2\" />\n" + "<feComposite in2=\"composite2\" operator=\"in\" result=\"composite3\" />\n" + "</filter>\n", blur.str().c_str()); + + return _filter; +}; /* Feather filter */ + +/** + \brief Custom predefined Out of Focus filter. + + Blur eroded by white or transparency + + Filter's parameters: + * Horizontal blur (0.01->10., default 3) -> blur (stdDeviation) + * Vertical blur (0.01->10., default 3) -> blur (stdDeviation) + * Dilatation (n-1th value, 0.->100., default 6) -> colormatrix2 (matrix) + * Erosion (nth value, 0.->100., default 2) -> colormatrix2 (matrix) + * Opacity (0.->1., default 1.) -> composite1 (k2) + * Background color (guint, default -1) -> flood (flood-opacity, flood-color) + * Blend type (enum, default normal) -> blend (mode) + * Blend to background (boolean, default false) -> blend (false: in2="flood", true: in2="BackgroundImage") + +*/ + +class ImageBlur : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ImageBlur ( ) : Filter() { }; + ~ImageBlur ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Out of Focus") "</name>\n" + "<id>org.inkscape.effect.filter.ImageBlur</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"50.00\">3</param>\n" + "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"50.00\">3</param>\n" + "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">6</param>\n" + "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">2</param>\n" + "<param name=\"opacity\" gui-text=\"" N_("Opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n" + "</page>\n" + "<page name=\"backgroundtab\" gui-text=\"Background\">\n" + "<param name=\"color\" gui-text=\"" N_("Background color") "\" type=\"color\">-1</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Blend type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "</param>\n" + "<param name=\"background\" gui-text=\"" N_("Blend to background") "\" type=\"bool\" >false</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Blurs") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Blur eroded by white or transparency") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ImageBlur()); + }; + +}; + +gchar const * +ImageBlur::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream hblur; + std::ostringstream vblur; + std::ostringstream dilat; + std::ostringstream erosion; + std::ostringstream opacity; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream a; + std::ostringstream blend; + std::ostringstream background; + + hblur << ext->get_param_float("hblur"); + vblur << ext->get_param_float("vblur"); + dilat << ext->get_param_float("dilat"); + erosion << -ext->get_param_float("erosion"); + opacity << ext->get_param_float("opacity"); + + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + blend << ext->get_param_optiongroup("blend"); + + if (ext->get_param_bool("background")) { + background << "BackgroundImage" ; + } else { + background << "flood" ; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Out of Focus\">\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"colormatrix1\" />\n" + "<feGaussianBlur in=\"colormatrix1\" stdDeviation=\"%s %s\" result=\"blur\" />\n" + "<feColorMatrix in=\"blur\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix2\" />\n" + "<feBlend in=\"colormatrix2\" in2=\"%s\" mode=\"%s\" result=\"blend\" />\n" + "<feComposite in=\"blend\" in2=\"blend\" operator=\"arithmetic\" k2=\"%s\" result=\"composite1\" />\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" />\n" + "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + hblur.str().c_str(), vblur.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), + background.str().c_str(), blend.str().c_str(), opacity.str().c_str()); + + return _filter; +}; /* Out of Focus filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'BLURS' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BLURS_H__ */ diff --git a/src/extension/internal/filter/bumps.h b/src/extension/internal/filter/bumps.h new file mode 100644 index 0000000..21328f4 --- /dev/null +++ b/src/extension/internal/filter/bumps.h @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BUMPS_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BUMPS_H__ +/* Change the 'BUMPS' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Bump filters + * Bump + * Wax bump + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Bump filter. + + All purpose bump filter + + Filter's parameters: + Options + * Image simplification (0.01->10., default 0.01) -> blur1 (stdDeviation) + * Bump simplification (0.01->10., default 0.01) -> blur2 (stdDeviation) + * Crop (-50.->50., default 0.) -> composite1 (k3) + * Red (-50.->50., default 0.) -> colormatrix1 (values) + * Green (-50.->50., default 0.) -> colormatrix1 (values) + * Blue (-50.->50., default 0.) -> colormatrix1 (values) + * Bump from background (boolean, default false) -> colormatrix1 (false: in="SourceGraphic", true: in="BackgroundImage") + Lighting + * Lighting type (enum, default specular) -> lighting block + * Height (0.->50., default 5.) -> lighting (surfaceScale) + * Lightness (0.->5., default 1.) -> lighting [diffuselighting (diffuseConstant)|specularlighting (specularConstant)] + * Precision (1->128, default 15) -> lighting (specularExponent) + * Color (guint, default -1 (RGB:255,255,255))-> lighting (lighting-color) + Light source + * Azimuth (0->360, default 225) -> lightsOptions (distantAzimuth) + * Elevation (0->180, default 45) -> lightsOptions (distantElevation) + * X location [point] (-5000->5000, default 526) -> lightsOptions (x) + * Y location [point] (-5000->5000, default 372) -> lightsOptions (y) + * Z location [point] (0->5000, default 150) -> lightsOptions (z) + * X location [spot] (-5000->5000, default 526) -> lightsOptions (x) + * Y location [spot] (-5000->5000, default 372) -> lightsOptions (y) + * Z location [spot] (-5000->5000, default 150) -> lightsOptions (z) + * X target (-5000->5000, default 0) -> lightsOptions (pointsAtX) + * Y target (-5000->5000, default 0) -> lightsOptions (pointsAtX) + * Z target (-5000->0, default -1000) -> lightsOptions (pointsAtX) + * Specular exponent (1->100, default 1) -> lightsOptions (specularExponent) + * Cone angle (0->100, default 50) -> lightsOptions (limitingConeAngle) + Color bump + * Blend type (enum, default normal) -> blend (mode) + * Image color (guint, default -987158017 (RGB:197,41,41)) -> flood (flood-color) + * Color bump (boolean, default false) -> composite2 (false: in="diffuselighting", true in="flood") +*/ + +class Bump : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Bump ( ) : Filter() { }; + ~Bump ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Bump") "</name>\n" + "<id>org.inkscape.effect.filter.Bump</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"simplifyImage\" gui-text=\"" N_("Image simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">0.01</param>\n" + "<param name=\"simplifyBump\" gui-text=\"" N_("Bump simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">0.01</param>\n" + "<param name=\"crop\" gui-text=\"" N_("Crop") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n" + "<label appearance=\"header\">" N_("Bump source") "</label>\n" + "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n" + "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n" + "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n" + "<param name=\"background\" gui-text=\"" N_("Bump from background") "\" indent=\"1\" type=\"bool\">false</param>\n" + "</page>\n" + "<page name=\"lightingtab\" gui-text=\"Lighting\">\n" + "<param name=\"lightType\" gui-text=\"" N_("Lighting type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"specular\">" N_("Specular") "</option>\n" + "<option value=\"diffuse\">" N_("Diffuse") "</option>\n" + "</param>\n" + "<param name=\"height\" gui-text=\"" N_("Height") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"50.\">5</param>\n" + "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"5.\">1</param>\n" + "<param name=\"precision\" gui-text=\"" N_("Precision") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"128\">15</param>\n" + "<param name=\"lightingColor\" gui-text=\"" N_("Color") "\" type=\"color\">-1</param>\n" + "</page>\n" + "<page name=\"lightsourcetab\" gui-text=\"" N_("Light source") "\">\n" + "<param name=\"lightSource\" gui-text=\"" N_("Light source:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"distant\">" N_("Distant") "</option>\n" + "<option value=\"point\">" N_("Point") "</option>\n" + "<option value=\"spot\">" N_("Spot") "</option>\n" + "</param>\n" + "<label appearance=\"header\">" N_("Distant light options") "</label>\n" + "<param name=\"distantAzimuth\" gui-text=\"" N_("Azimuth") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"360\">225</param>\n" + "<param name=\"distantElevation\" gui-text=\"" N_("Elevation") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"180\">45</param>\n" + "<label appearance=\"header\">" N_("Point light options") "</label>\n" + "<param name=\"pointX\" gui-text=\"" N_("X location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">526</param>\n" + "<param name=\"pointY\" gui-text=\"" N_("Y location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">372</param>\n" + "<param name=\"pointZ\" gui-text=\"" N_("Z location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"5000\">150</param>\n" + "<label appearance=\"header\">" N_("Spot light options") "</label>\n" + "<param name=\"spotX\" gui-text=\"" N_("X location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">526</param>\n" + "<param name=\"spotY\" gui-text=\"" N_("Y location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">372</param>\n" + "<param name=\"spotZ\" gui-text=\"" N_("Z location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">150</param>\n" + "<param name=\"spotAtX\" gui-text=\"" N_("X target") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">0</param>\n" + "<param name=\"spotAtY\" gui-text=\"" N_("Y target") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">0</param>\n" + "<param name=\"spotAtZ\" gui-text=\"" N_("Z target") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"0\">-1000</param>\n" + "<param name=\"spotExponent\" gui-text=\"" N_("Specular exponent") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"100\">1</param>\n" + "<param name=\"spotConeAngle\" gui-text=\"" N_("Cone angle") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"100\">50</param>\n" + "</page>\n" + "<page name=\"colortab\" gui-text=\"Color bump\">\n" + "<param name=\"imageColor\" gui-text=\"" N_("Image color") "\" type=\"color\">-987158017</param>\n" + "<param name=\"colorize\" gui-text=\"" N_("Color bump") "\" type=\"bool\" >false</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Blend type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Bumps") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("All purposes bump filter") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Bump()); + }; + +}; + +gchar const * +Bump::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream simplifyImage; + std::ostringstream simplifyBump; + std::ostringstream red; + std::ostringstream green; + std::ostringstream blue; + std::ostringstream crop; + std::ostringstream bumpSource; + std::ostringstream blend; + + std::ostringstream lightStart; + std::ostringstream lightOptions; + std::ostringstream lightEnd; + + std::ostringstream floodRed; + std::ostringstream floodGreen; + std::ostringstream floodBlue; + std::ostringstream floodAlpha; + std::ostringstream colorize; + + + simplifyImage << ext->get_param_float("simplifyImage"); + simplifyBump << ext->get_param_float("simplifyBump"); + red << ext->get_param_float("red"); + green << ext->get_param_float("green"); + blue << ext->get_param_float("blue"); + crop << ext->get_param_float("crop"); + blend << ext->get_param_optiongroup("blend"); + + guint32 lightingColor = ext->get_param_color("lightingColor"); + guint32 imageColor = ext->get_param_color("imageColor"); + + if (ext->get_param_bool("background")) { + bumpSource << "BackgroundImage" ; + } else { + bumpSource << "blur1" ; + } + + const gchar *lightType = ext->get_param_optiongroup("lightType"); + if ((g_ascii_strcasecmp("specular", lightType) == 0)) { + // Specular + lightStart << "<feSpecularLighting lighting-color=\"rgb(" << ((lightingColor >> 24) & 0xff) << "," + << ((lightingColor >> 16) & 0xff) << "," << ((lightingColor >> 8) & 0xff) << ")\" surfaceScale=\"" + << ext->get_param_float("height") << "\" specularConstant=\"" << ext->get_param_float("lightness") + << "\" specularExponent=\"" << ext->get_param_int("precision") << "\" result=\"lighting\">"; + lightEnd << "</feSpecularLighting>"; + } else { + // Diffuse + lightStart << "<feDiffuseLighting lighting-color=\"rgb(" << ((lightingColor >> 24) & 0xff) << "," + << ((lightingColor >> 16) & 0xff) << "," << ((lightingColor >> 8) & 0xff) << ")\" surfaceScale=\"" + << ext->get_param_float("height") << "\" diffuseConstant=\"" << ext->get_param_float("lightness") + << "\" result=\"lighting\">"; + lightEnd << "</feDiffuseLighting>"; + } + + const gchar *lightSource = ext->get_param_optiongroup("lightSource"); + if ((g_ascii_strcasecmp("distant", lightSource) == 0)) { + // Distant + lightOptions << "<feDistantLight azimuth=\"" << ext->get_param_int("distantAzimuth") << "\" elevation=\"" + << ext->get_param_int("distantElevation") << "\" />"; + } else if ((g_ascii_strcasecmp("point", lightSource) == 0)) { + // Point + lightOptions << "<fePointLight z=\"" << ext->get_param_int("pointX") << "\" y=\"" << ext->get_param_int("pointY") + << "\" x=\"" << ext->get_param_int("pointZ") << "\" />"; + } else { + // Spot + lightOptions << "<feSpotLight x=\"" << ext->get_param_int("pointX") << "\" y=\"" << ext->get_param_int("pointY") + << "\" z=\"" << ext->get_param_int("pointZ") << "\" pointsAtX=\"" << ext->get_param_int("spotAtX") + << "\" pointsAtY=\"" << ext->get_param_int("spotAtY") << "\" pointsAtZ=\"" << ext->get_param_int("spotAtZ") + << "\" specularExponent=\"" << ext->get_param_int("spotExponent") + << "\" limitingConeAngle=\"" << ext->get_param_int("spotConeAngle") + << "\" />"; + } + + floodRed << ((imageColor >> 24) & 0xff); + floodGreen << ((imageColor >> 16) & 0xff); + floodBlue << ((imageColor >> 8) & 0xff); + floodAlpha << (imageColor & 0xff) / 255.0F; + + if (ext->get_param_bool("colorize")) { + colorize << "flood" ; + } else { + colorize << "blur1" ; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Bump\">\n" + "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feColorMatrix in=\"%s\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s %s 1 0 \" result=\"colormatrix1\" />\n" + "<feColorMatrix in=\"colormatrix1\" type=\"luminanceToAlpha\" result=\"colormatrix2\" />\n" + "<feComposite in2=\"blur1\" operator=\"arithmetic\" k2=\"1\" k3=\"%s\" result=\"composite1\" />\n" + "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur2\" />\n" + "%s\n" + "%s\n" + "%s\n" + "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood\" />\n" + "<feComposite in=\"lighting\" in2=\"%s\" operator=\"arithmetic\" k3=\"1\" k2=\"1\" result=\"composite2\" />\n" + "<feBlend in2=\"SourceGraphic\" mode=\"%s\" result=\"blend\" />\n" + "<feComposite in=\"blend\" in2=\"SourceGraphic\" operator=\"in\" k2=\"1\" result=\"composite3\" />\n" + "</filter>\n", simplifyImage.str().c_str(), bumpSource.str().c_str(), red.str().c_str(), green.str().c_str(), blue.str().c_str(), + crop.str().c_str(), simplifyBump.str().c_str(), + lightStart.str().c_str(), lightOptions.str().c_str(), lightEnd.str().c_str(), + floodRed.str().c_str(), floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(), + colorize.str().c_str(), blend.str().c_str()); + + return _filter; + +}; /* Bump filter */ + +/** + \brief Custom predefined Wax Bump filter. + + Turns an image to jelly + + Filter's parameters: + Options + * Image simplification (0.01->10., default 1.5) -> blur1 (stdDeviation) + * Bump simplification (0.01->10., default 1) -> blur2 (stdDeviation) + * Crop (-10.->10., default 1.) -> colormatrix2 (4th value of the last line) + * Red (-10.->10., default 0.) -> colormatrix2 (values, substract 0.21) + * Green (-10.->10., default 0.) -> colormatrix2 (values, substract 0.72) + * Blue (-10.->10., default 0.) -> colormatrix2 (values, substract 0.07) + * Background (enum, default color) -> + * color: colormatrix1 (in="flood1") + * image: colormatrix1 (in="SourceGraphic") + * blurred image: colormatrix1 (in="blur1") + * Background opacity (0.->1., default 0) -> colormatrix1 (last value) + Lighting (specular, distant light) + * Color (guint, default -1 (RGB:255,255,255))-> lighting (lighting-color) + * Height (-50.->50., default 5.) -> lighting (surfaceScale) + * Lightness (0.->10., default 1.4) -> lighting [diffuselighting (diffuseConstant)|specularlighting (specularConstant)] + * Precision (0->50, default 35) -> lighting (specularExponent) + * Azimuth (0->360, default 225) -> lightsOptions (distantAzimuth) + * Elevation (0->180, default 60) -> lightsOptions (distantElevation) + * Lighting blend (enum, default screen) -> blend1 (mode) + * Highlight blend (enum, default screen) -> blend2 (mode) + Bump + * Trasparency type (enum [in,atop], default atop) -> composite2 (operator) + * Color (guint, default -520083713 (RGB:225,0,38)) -> flood2 (flood-color) + * Revert bump (boolean, default false) -> composite1 (false: operator="out", true operator="in") +*/ + +class WaxBump : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + WaxBump ( ) : Filter() { }; + ~WaxBump ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Wax Bump") "</name>\n" + "<id>org.inkscape.effect.filter.WaxBump</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"simplifyImage\" gui-text=\"" N_("Image simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">1.5</param>\n" + "<param name=\"simplifyBump\" gui-text=\"" N_("Bump simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">1</param>\n" + "<param name=\"crop\" gui-text=\"" N_("Crop") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1</param>\n" + "<label appearance=\"header\">" N_("Bump source") "</label>\n" + "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0</param>\n" + "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0</param>\n" + "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0</param>\n" + "<param name=\"background\" gui-text=\"" N_("Background:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"flood1\">" N_("Color") "</option>\n" + "<option value=\"SourceGraphic\">" N_("Image") "</option>\n" + "<option value=\"blur1\">" N_("Blurred image") "</option>\n" + "</param>\n" + "<param name=\"bgopacity\" gui-text=\"" N_("Background opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">0</param>\n" + "</page>\n" + "<page name=\"lightingtab\" gui-text=\"" N_("Lighting") "\">\n" + "<param name=\"lightingColor\" gui-text=\"" N_("Color") "\" type=\"color\">-1</param>\n" + "<param name=\"height\" gui-text=\"" N_("Height") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">5</param>\n" + "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10.\">1.4</param>\n" + "<param name=\"precision\" gui-text=\"" N_("Precision") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"50\">35</param>\n" + "<param name=\"distantAzimuth\" gui-text=\"" N_("Azimuth") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"360\">225</param>\n" + "<param name=\"distantElevation\" gui-text=\"" N_("Elevation") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"180\">60</param>\n" + "<param name=\"lightingblend\" gui-text=\"" N_("Lighting blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "</param>\n" + "<param name=\"highlightblend\" gui-text=\"" N_("Highlight blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "</param>\n" + "</page>\n" + "<page name=\"colortab\" gui-text=\"Bump\">\n" + "<param name=\"imageColor\" gui-text=\"" N_("Bump color") "\" type=\"color\">-520083713</param>\n" + "<param name=\"revert\" gui-text=\"" N_("Revert bump") "\" type=\"bool\" >false</param>\n" + "<param name=\"transparency\" gui-text=\"" N_("Transparency type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"atop\">" N_("Atop") "</option>\n" + "<option value=\"in\">" N_("In") "</option>\n" + "</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Bumps") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Turns an image to jelly") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new WaxBump()); + }; + +}; + +gchar const * +WaxBump::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream simplifyImage; + std::ostringstream simplifyBump; + std::ostringstream crop; + + std::ostringstream red; + std::ostringstream green; + std::ostringstream blue; + + std::ostringstream background; + std::ostringstream bgopacity; + + std::ostringstream height; + std::ostringstream lightness; + std::ostringstream precision; + std::ostringstream distantAzimuth; + std::ostringstream distantElevation; + + std::ostringstream lightRed; + std::ostringstream lightGreen; + std::ostringstream lightBlue; + + std::ostringstream floodRed; + std::ostringstream floodGreen; + std::ostringstream floodBlue; + std::ostringstream floodAlpha; + + std::ostringstream revert; + std::ostringstream lightingblend; + std::ostringstream highlightblend; + std::ostringstream transparency; + + simplifyImage << ext->get_param_float("simplifyImage"); + simplifyBump << ext->get_param_float("simplifyBump"); + crop << ext->get_param_float("crop"); + + red << ext->get_param_float("red") - 0.21; + green << ext->get_param_float("green") - 0.72; + blue << ext->get_param_float("blue") - 0.07; + + background << ext->get_param_optiongroup("background"); + bgopacity << ext->get_param_float("bgopacity"); + + height << ext->get_param_float("height"); + lightness << ext->get_param_float("lightness"); + precision << ext->get_param_int("precision"); + distantAzimuth << ext->get_param_int("distantAzimuth"); + distantElevation << ext->get_param_int("distantElevation"); + + guint32 lightingColor = ext->get_param_color("lightingColor"); + lightRed << ((lightingColor >> 24) & 0xff); + lightGreen << ((lightingColor >> 16) & 0xff); + lightBlue << ((lightingColor >> 8) & 0xff); + + guint32 imageColor = ext->get_param_color("imageColor"); + floodRed << ((imageColor >> 24) & 0xff); + floodGreen << ((imageColor >> 16) & 0xff); + floodBlue << ((imageColor >> 8) & 0xff); + floodAlpha << (imageColor & 0xff) / 255.0F; + + if (ext->get_param_bool("revert")) { + revert << "in" ; + } else { + revert << "out" ; + } + + lightingblend << ext->get_param_optiongroup("lightingblend"); + highlightblend << ext->get_param_optiongroup("highlightblend"); + transparency << ext->get_param_optiongroup("transparency"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Wax Bump\">\n" + "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feFlood flood-opacity=\"1\" flood-color=\"rgb(255,255,255)\" result=\"flood1\" />\n" + "<feColorMatrix in=\"%s\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 %s \" result=\"colormatrix1\" />\n" + "<feColorMatrix in=\"blur1\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 %s %s %s %s 0 \" result=\"colormatrix2\" />\n" + "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood2\" />\n" + "<feComposite in=\"flood2\" in2=\"colormatrix2\" operator=\"%s\" result=\"composite1\" />\n" + "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur2\" />\n" + "<feSpecularLighting in=\"blur2\" lighting-color=\"rgb(%s,%s,%s)\" specularConstant=\"%s\" surfaceScale=\"%s\" specularExponent=\"%s\" result=\"specular\">\n" + "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n" + "</feSpecularLighting>\n" + "<feBlend in=\"specular\" in2=\"blur2\" specularConstant=\"1\" mode=\"%s\" result=\"blend1\" />\n" + "<feComposite in=\"blend1\" in2=\"blur2\" k2=\"0\" operator=\"%s\" k1=\"0.5\" k3=\"0.5\" k4=\"0\" result=\"composite2\" />\n" + "<feMerge result=\"merge\">\n" + "<feMergeNode in=\"colormatrix1\" />\n" + "<feMergeNode in=\"composite2\" />\n" + "</feMerge>\n" + "<feBlend in2=\"composite2\" mode=\"%s\" result=\"blend2\" />\n" + "<feComposite in=\"blend2\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite3\" />\n" + "</filter>\n", simplifyImage.str().c_str(), background.str().c_str(), bgopacity.str().c_str(), + red.str().c_str(), green.str().c_str(), blue.str().c_str(), crop.str().c_str(), + floodRed.str().c_str(), floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(), + revert.str().c_str(), simplifyBump.str().c_str(), + lightRed.str().c_str(), lightGreen.str().c_str(), lightBlue.str().c_str(), + lightness.str().c_str(), height.str().c_str(), precision.str().c_str(), + distantElevation.str().c_str(), distantAzimuth.str().c_str(), + lightingblend.str().c_str(), transparency.str().c_str(), highlightblend.str().c_str() ); + + return _filter; + +}; /* Wax bump filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'BUMPS' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BUMPS_H__ */ diff --git a/src/extension/internal/filter/color.h b/src/extension/internal/filter/color.h new file mode 100644 index 0000000..1b0b9e2 --- /dev/null +++ b/src/extension/internal/filter/color.h @@ -0,0 +1,1886 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_COLOR_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_COLOR_H__ +/* Change the 'COLOR' above to be your file name */ + +/* + * Copyright (C) 2013-2015 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Color filters + * Brilliance + * Channel painting + * Color blindness + * Color shift + * Colorize + * Component transfer + * Duochrome + * Extract channel + * Fade to black or white + * Greyscale + * Invert + * Lighting + * Lightness-contrast + * Nudge RGB + * Nudge CMY + * Quadritone + * Simple blend + * Solarize + * Tritone + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Brilliance filter. + + Brilliance filter. + + Filter's parameters: + * Brilliance (1.->10., default 2.) -> colorMatrix (RVB entries) + * Over-saturation (0.->10., default 0.5) -> colorMatrix (6 other entries) + * Lightness (-10.->10., default 0.) -> colorMatrix (last column) + * Inverted (boolean, default false) -> colorMatrix + + Matrix: + St Vi Vi 0 Li + Vi St Vi 0 Li + Vi Vi St 0 Li + 0 0 0 1 0 +*/ +class Brilliance : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Brilliance ( ) : Filter() { }; + ~Brilliance ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Brilliance") "</name>\n" + "<id>org.inkscape.effect.filter.Brilliance</id>\n" + "<param name=\"brightness\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"10.00\">2</param>\n" + "<param name=\"sat\" gui-text=\"" N_("Over-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.0\" max=\"10.00\">0.5</param>\n" + "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0</param>\n" + "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Brightness filter") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Brilliance()); + }; +}; + +gchar const * +Brilliance::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream brightness; + std::ostringstream sat; + std::ostringstream lightness; + + if (ext->get_param_bool("invert")) { + brightness << -ext->get_param_float("brightness"); + sat << 1 + ext->get_param_float("sat"); + lightness << -ext->get_param_float("lightness"); + } else { + brightness << ext->get_param_float("brightness"); + sat << -ext->get_param_float("sat"); + lightness << ext->get_param_float("lightness"); + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Brilliance\">\n" + "<feColorMatrix values=\"%s %s %s 0 %s %s %s %s 0 %s %s %s %s 0 %s 0 0 0 1 0 \" />\n" + "</filter>\n", brightness.str().c_str(), sat.str().c_str(), sat.str().c_str(), + lightness.str().c_str(), sat.str().c_str(), brightness.str().c_str(), + sat.str().c_str(), lightness.str().c_str(), sat.str().c_str(), + sat.str().c_str(), brightness.str().c_str(), lightness.str().c_str() ); + + return _filter; +}; /* Brilliance filter */ + +/** + \brief Custom predefined Channel Painting filter. + + Channel Painting filter. + + Filter's parameters: + * Saturation (0.->1., default 1.) -> colormatrix1 (values) + * Red (-10.->10., default -1.) -> colormatrix2 (values) + * Green (-10.->10., default 0.5) -> colormatrix2 (values) + * Blue (-10.->10., default 0.5) -> colormatrix2 (values) + * Alpha (-10.->10., default 1.) -> colormatrix2 (values) + * Flood colors (guint, default 16777215) -> flood (flood-opacity, flood-color) + * Inverted (boolean, default false) -> composite1 (operator, true='in', false='out') + + Matrix: + 1 0 0 0 0 + 0 1 0 0 0 + 0 0 1 0 0 + R G B A 0 +*/ +class ChannelPaint : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ChannelPaint ( ) : Filter() { }; + ~ChannelPaint ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Channel Painting") "</name>\n" + "<id>org.inkscape.effect.filter.ChannelPaint</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"saturation\" gui-text=\"" N_("Saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">1</param>\n" + "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">-1</param>\n" + "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n" + "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n" + "<param name=\"alpha\" gui-text=\"" N_("Alpha") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1</param>\n" + "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n" + "</page>\n" + "<page name=\"colortab\" gui-text=\"Color\">\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">16777215</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Replace RGB by any color") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ChannelPaint()); + }; +}; + +gchar const * +ChannelPaint::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream saturation; + std::ostringstream red; + std::ostringstream green; + std::ostringstream blue; + std::ostringstream alpha; + std::ostringstream invert; + std::ostringstream floodRed; + std::ostringstream floodGreen; + std::ostringstream floodBlue; + std::ostringstream floodAlpha; + + saturation << ext->get_param_float("saturation"); + red << ext->get_param_float("red"); + green << ext->get_param_float("green"); + blue << ext->get_param_float("blue"); + alpha << ext->get_param_float("alpha"); + + guint32 color = ext->get_param_color("color"); + floodRed << ((color >> 24) & 0xff); + floodGreen << ((color >> 16) & 0xff); + floodBlue << ((color >> 8) & 0xff); + floodAlpha << (color & 0xff) / 255.0F; + + if (ext->get_param_bool("invert")) { + invert << "in"; + } else { + invert << "out"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Channel Painting\">\n" + "<feColorMatrix values=\"%s\" type=\"saturate\" result=\"colormatrix1\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s %s %s 0 \" in=\"SourceGraphic\" result=\"colormatrix2\" />\n" + "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood\" />\n" + "<feComposite in2=\"colormatrix2\" operator=\"%s\" result=\"composite1\" />\n" + "<feMerge result=\"merge\">\n" + "<feMergeNode in=\"colormatrix1\" />\n" + "<feMergeNode in=\"composite1\" />\n" + "</feMerge>\n" + "<feComposite in=\"merge\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n" + "</filter>\n", saturation.str().c_str(), red.str().c_str(), green.str().c_str(), + blue.str().c_str(), alpha.str().c_str(), floodRed.str().c_str(), + floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(), + invert.str().c_str() ); + + return _filter; +}; /* Channel Painting filter */ + +/** + \brief Custom predefined Color Blindness filter. + + Color Blindness filter. + Based on https://openclipart.org/detail/22299/Color%20Blindness%20filters + + Filter's parameters: + * Blindness type (enum, default Achromatomaly) -> colormatrix +*/ +class ColorBlindness : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ColorBlindness ( ) : Filter() { }; + ~ColorBlindness ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Color Blindness") "</name>\n" + "<id>org.inkscape.effect.filter.ColorBlindness</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"type\" gui-text=\"" N_("Blindness type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"0.618 0.32 0.062 0 0 0.163 0.775 0.062 0 0 0.163 0.32 0.516 0 0 0 0 0 1 0 \">" N_("Rod monochromacy (atypical achromatopsia)") "</option>\n" + "<option value=\"0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0 0 0 1 0 \">" N_("Cone monochromacy (typical achromatopsia)") "</option>\n" + "<option value=\"0.8 0.2 0 0 0 0.2583 0.74167 0 0 0 0 0.14167 0.85833 0 0 0 0 0 1 0 \">" N_("Green weak (deuteranomaly)") "</option>\n" + "<option value=\"0.625 0.375 0 0 0 0.7 0.3 0 0 0 0 0.3 0.7 0 0 0 0 0 1 0 \">" N_("Green blind (deuteranopia)") "</option>\n" + "<option value=\"0.8166 0.1833 0 0 0 0.333 0.666 0 0 0 0 0.125 0.875 0 0 0 0 0 1 0 \">" N_("Red weak (protanomaly)") "</option>\n" + "<option value=\"0.566 0.43333 0 0 0 0.55833 0.4416 0 0 0 0 0.24167 0.75833 0 0 0 0 0 1 0 \">" N_("Red blind (protanopia)") "</option>\n" + "<option value=\"0.966 0.033 0 0 0 0 0.733 0.266 0 0 0 0.1833 0.816 0 0 0 0 0 1 0 \">" N_("Blue weak (tritanomaly)") "</option>\n" + "<option value=\"0.95 0.05 0 0 0 0.2583 0.4333 0.5667 0 0 0 0.475 0.525 0 0 0 0 0 1 0 \">" N_("Blue blind (tritanopia)") "</option>\n" + "</param>\n" + "</page>\n" + "<page name=\"helptab\" gui-text=\"Help\">\n" + "<label xml:space=\"preserve\">\n" +"Filters based on https://openclipart.org/detail/22299/Color%20Blindness%20filters\n" +"\n" +"These filters don't correctly reflect actual color blindness for two main reasons:\n" +" * Everyone is different, and is not affected exactly the same way.\n" +" * The filters are in the RGB color space, and ignore confusion lines.\n" + "</label>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Simulate color blindness") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ColorBlindness()); + }; +}; + +gchar const * +ColorBlindness::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream type; + type << ext->get_param_optiongroup("type"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" height=\"1\" width=\"1\" y=\"0\" x=\"0\" inkscape:label=\"Color Blindness\">\n" + "<feColorMatrix values=\"%s\" type=\"matrix\" result=\"colormatrix1\" />\n" + "</filter>\n", type.str().c_str()); + + return _filter; +}; /* Color Blindness filter */ + +/** + \brief Custom predefined Color shift filter. + + Rotate and desaturate hue + + Filter's parameters: + * Shift (0->360, default 330) -> color1 (values) + * Saturation (0.->1., default 0.6) -> color2 (values) +*/ + +class ColorShift : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ColorShift ( ) : Filter() { }; + ~ColorShift ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Color Shift") "</name>\n" + "<id>org.inkscape.effect.filter.ColorShift</id>\n" + "<param name=\"shift\" gui-text=\"" N_("Shift (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">330</param>\n" + "<param name=\"sat\" gui-text=\"" N_("Saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1\">0.6</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Rotate and desaturate hue") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ColorShift()); + }; + +}; + +gchar const * +ColorShift::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream shift; + std::ostringstream sat; + + shift << ext->get_param_int("shift"); + sat << ext->get_param_float("sat"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Color Shift\">\n" + "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"color1\" />\n" + "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"color2\" />\n" + "</filter>\n", shift.str().c_str(), sat.str().c_str() ); + + return _filter; +}; /* ColorShift filter */ + +/** + \brief Custom predefined Colorize filter. + + Blend image or object with a flood color. + + Filter's parameters: + * Harsh light (0.->10., default 0) -> composite1 (k1) + * Normal light (0.->10., default 1) -> composite2 (k2) + * Duotone (boolean, default false) -> colormatrix1 (values="0") + * Filtered greys (boolean, default false) -> colormatrix2 (values="0") + * Blend mode 1 (enum, default Multiply) -> blend1 (mode) + * Blend mode 2 (enum, default Screen) -> blend2 (mode) +*/ + +class Colorize : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Colorize ( ) : Filter() { }; + ~Colorize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Colorize") "</name>\n" + "<id>org.inkscape.effect.filter.Colorize</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"hlight\" gui-text=\"" N_("Harsh light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">0</param>\n" + "<param name=\"nlight\" gui-text=\"" N_("Normal light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">1</param>\n" + "<param name=\"duotone\" gui-text=\"" N_("Duotone") "\" type=\"bool\" >false</param>\n" + "<param name=\"blend1\" gui-text=\"" N_("Blend 1:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "</param>\n" + "<param name=\"blend2\" gui-text=\"" N_("Blend 2:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "</param>\n" + "</page>\n" + "<page name=\"colortab\" gui-text=\"Color\">\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">-1639776001</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Blend image or object with a flood color") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Colorize()); + }; + +}; + +gchar const * +Colorize::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream a; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream hlight; + std::ostringstream nlight; + std::ostringstream duotone; + std::ostringstream blend1; + std::ostringstream blend2; + + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + + hlight << ext->get_param_float("hlight"); + nlight << ext->get_param_float("nlight"); + blend1 << ext->get_param_optiongroup("blend1"); + blend2 << ext->get_param_optiongroup("blend2"); + if (ext->get_param_bool("duotone")) { + duotone << "0"; + } else { + duotone << "1"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Colorize\">\n" + "<feComposite in2=\"SourceGraphic\" operator=\"arithmetic\" k1=\"%s\" k2=\"%s\" result=\"composite1\" />\n" + "<feColorMatrix in=\"composite1\" values=\"%s\" type=\"saturate\" result=\"colormatrix1\" />\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood1\" />\n" + "<feBlend in=\"flood1\" in2=\"colormatrix1\" mode=\"%s\" result=\"blend1\" />\n" + "<feBlend in2=\"blend1\" mode=\"%s\" result=\"blend2\" />\n" + "<feColorMatrix in=\"blend2\" values=\"1\" type=\"saturate\" result=\"colormatrix2\" />\n" + "<feComposite in=\"colormatrix2\" in2=\"SourceGraphic\" operator=\"in\" k2=\"1\" result=\"composite2\" />\n" + "</filter>\n", hlight.str().c_str(), nlight.str().c_str(), duotone.str().c_str(), + a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + blend1.str().c_str(), blend2.str().c_str() ); + + return _filter; +}; /* Colorize filter */ + +/** + \brief Custom predefined ComponentTransfer filter. + + Basic component transfer structure. + + Filter's parameters: + * Type (enum, default identity) -> component function + +*/ +class ComponentTransfer : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ComponentTransfer ( ) : Filter() { }; + ~ComponentTransfer ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Component Transfer") "</name>\n" + "<id>org.inkscape.effect.filter.ComponentTransfer</id>\n" + "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"identity\">" N_("Identity") "</option>\n" + "<option value=\"table\">" N_("Table") "</option>\n" + "<option value=\"discrete\">" N_("Discrete") "</option>\n" + "<option value=\"linear\">" N_("Linear") "</option>\n" + "<option value=\"gamma\">" N_("Gamma") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Basic component transfer structure") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ComponentTransfer()); + }; +}; + +gchar const * +ComponentTransfer::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream CTfunction; + const gchar *type = ext->get_param_optiongroup("type"); + + if ((g_ascii_strcasecmp("identity", type) == 0)) { + CTfunction << "<feFuncR type=\"identity\" tableValues=\"1 0\" />\n" + << "<feFuncG type=\"identity\" tableValues=\"1 0\" />\n" + << "<feFuncB type=\"identity\" tableValues=\"1 0\" />\n" + << "<feFuncA type=\"identity\" tableValues=\"0 1\" />\n"; + } else if ((g_ascii_strcasecmp("table", type) == 0)) { + CTfunction << "<feFuncR type=\"table\" tableValues=\"0 1 0\" />\n" + << "<feFuncG type=\"table\" tableValues=\"0 1 0\" />\n" + << "<feFuncB type=\"table\" tableValues=\"0 1 0\" />\n"; + } else if ((g_ascii_strcasecmp("discrete", type) == 0)) { + CTfunction << "<feFuncR tableValues=\"0 0.2 0.4 0.6 0.8 1 1\" type=\"discrete\" />\n" + << "<feFuncG tableValues=\"0 0.2 0.4 0.6 0.8 1 1\" type=\"discrete\" />\n" + << "<feFuncB tableValues=\"0 0.2 0.4 0.6 0.8 1 1\" type=\"discrete\" />\n"; + } else if ((g_ascii_strcasecmp("linear", type) == 0)) { + CTfunction << "<feFuncR type=\"linear\" slope=\".5\" intercept=\".10\" />\n" + << "<feFuncG type=\"linear\" slope=\".5\" intercept=\".10\" />\n" + << "<feFuncB type=\"linear\" slope=\".5\" intercept=\".10\" />\n"; + } else { //Gamma + CTfunction << "<feFuncR type=\"gamma\" amplitude=\"3\" exponent=\"3\" offset=\"0.1\" />\n" + << "<feFuncG type=\"gamma\" amplitude=\"3\" exponent=\"3\" offset=\"0.1\" />\n" + << "<feFuncB type=\"gamma\" amplitude=\"3\" exponent=\"3\" offset=\"0.1\" />\n"; + } + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Component Transfer\">\n" + "<feComponentTransfer>\n" + "%s\n" + "</feComponentTransfer>\n" + "</filter>\n", CTfunction.str().c_str()); + + return _filter; +}; /* ComponentTransfer filter */ + +/** + \brief Custom predefined Duochrome filter. + + Convert luminance values to a duochrome palette. + + Filter's parameters: + * Fluorescence level (0.->2., default 0) -> composite4 (k2) + * Swap (enum, default "No swap") -> composite1, composite2 (operator) + * Color 1 (guint, default 1364325887) -> flood1 (flood-opacity, flood-color) + * Color 2 (guint, default -65281) -> flood2 (flood-opacity, flood-color) +*/ + +class Duochrome : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Duochrome ( ) : Filter() { }; + ~Duochrome ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Duochrome") "</name>\n" + "<id>org.inkscape.effect.filter.Duochrome</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"fluo\" gui-text=\"" N_("Fluorescence level") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"2\">0</param>\n" + "<param name=\"swap\" gui-text=\"" N_("Swap:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"none\">" N_("No swap") "</option>\n" + "<option value=\"full\">" N_("Color and alpha") "</option>\n" + "<option value=\"color\">" N_("Color only") "</option>\n" + "<option value=\"alpha\">" N_("Alpha only") "</option>\n" + "</param>\n" + "</page>\n" + "<page name=\"co11tab\" gui-text=\"Color 1\">\n" + "<param name=\"color1\" gui-text=\"" N_("Color 1") "\" type=\"color\">1364325887</param>\n" + "</page>\n" + "<page name=\"co12tab\" gui-text=\"Color 2\">\n" + "<param name=\"color2\" gui-text=\"" N_("Color 2") "\" type=\"color\">-65281</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Convert luminance values to a duochrome palette") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Duochrome()); + }; + +}; + +gchar const * +Duochrome::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream a1; + std::ostringstream r1; + std::ostringstream g1; + std::ostringstream b1; + std::ostringstream a2; + std::ostringstream r2; + std::ostringstream g2; + std::ostringstream b2; + std::ostringstream fluo; + std::ostringstream swap1; + std::ostringstream swap2; + guint32 color1 = ext->get_param_color("color1"); + guint32 color2 = ext->get_param_color("color2"); + float fluorescence = ext->get_param_float("fluo"); + const gchar *swaptype = ext->get_param_optiongroup("swap"); + + r1 << ((color1 >> 24) & 0xff); + g1 << ((color1 >> 16) & 0xff); + b1 << ((color1 >> 8) & 0xff); + r2 << ((color2 >> 24) & 0xff); + g2 << ((color2 >> 16) & 0xff); + b2 << ((color2 >> 8) & 0xff); + fluo << fluorescence; + + if ((g_ascii_strcasecmp("full", swaptype) == 0)) { + swap1 << "in"; + swap2 << "out"; + a1 << (color1 & 0xff) / 255.0F; + a2 << (color2 & 0xff) / 255.0F; + } else if ((g_ascii_strcasecmp("color", swaptype) == 0)) { + swap1 << "in"; + swap2 << "out"; + a1 << (color2 & 0xff) / 255.0F; + a2 << (color1 & 0xff) / 255.0F; + } else if ((g_ascii_strcasecmp("alpha", swaptype) == 0)) { + swap1 << "out"; + swap2 << "in"; + a1 << (color2 & 0xff) / 255.0F; + a2 << (color1 & 0xff) / 255.0F; + } else { + swap1 << "out"; + swap2 << "in"; + a1 << (color1 & 0xff) / 255.0F; + a2 << (color2 & 0xff) / 255.0F; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Duochrome\">\n" + "<feColorMatrix type=\"luminanceToAlpha\" result=\"colormatrix1\" />\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood1\" />\n" + "<feComposite in2=\"colormatrix1\" operator=\"%s\" result=\"composite1\" />\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood2\" />\n" + "<feComposite in2=\"colormatrix1\" result=\"composite2\" operator=\"%s\" />\n" + "<feComposite in=\"composite2\" in2=\"composite1\" k2=\"1\" k3=\"1\" operator=\"arithmetic\" result=\"composite3\" />\n" + "<feColorMatrix in=\"composite3\" type=\"matrix\" values=\"2 -1 0 0 0 0 2 -1 0 0 -1 0 2 0 0 0 0 0 1 0 \" result=\"colormatrix2\" />\n" + "<feComposite in=\"colormatrix2\" in2=\"composite3\" operator=\"arithmetic\" k2=\"%s\" result=\"composite4\" />\n" + "<feBlend in=\"composite4\" in2=\"composite3\" mode=\"normal\" result=\"blend\" />\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" />\n" + "</filter>\n", a1.str().c_str(), r1.str().c_str(), g1.str().c_str(), b1.str().c_str(), swap1.str().c_str(), + a2.str().c_str(), r2.str().c_str(), g2.str().c_str(), b2.str().c_str(), swap2.str().c_str(), + fluo.str().c_str() ); + + return _filter; +}; /* Duochrome filter */ + +/** + \brief Custom predefined Extract Channel filter. + + Extract color channel as a transparent image. + + Filter's parameters: + * Channel (enum, all colors, default Red) -> colormatrix (values) + * Background blend (enum, Normal, Multiply, Screen, default Normal) -> blend (mode) + * Channel to alpha (boolean, default false) -> colormatrix (values) + +*/ +class ExtractChannel : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ExtractChannel ( ) : Filter() { }; + ~ExtractChannel ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Extract Channel") "</name>\n" + "<id>org.inkscape.effect.filter.ExtractChannel</id>\n" + "<param name=\"source\" gui-text=\"" N_("Channel:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"r\">" N_("Red") "</option>\n" + "<option value=\"g\">" N_("Green") "</option>\n" + "<option value=\"b\">" N_("Blue") "</option>\n" + "<option value=\"c\">" N_("Cyan") "</option>\n" + "<option value=\"m\">" N_("Magenta") "</option>\n" + "<option value=\"y\">" N_("Yellow") "</option>\n" + "</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Background blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "</param>\n" + "<param name=\"alpha\" gui-text=\"" N_("Channel to alpha") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Extract color channel as a transparent image") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ExtractChannel()); + }; +}; + +gchar const * +ExtractChannel::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream blend; + std::ostringstream colors; + + blend << ext->get_param_optiongroup("blend"); + + const gchar *channel = ext->get_param_optiongroup("source"); + if (ext->get_param_bool("alpha")) { + if ((g_ascii_strcasecmp("r", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0"; + } else if ((g_ascii_strcasecmp("g", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0"; + } else if ((g_ascii_strcasecmp("b", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0"; + } else if ((g_ascii_strcasecmp("c", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 0 0 1 0"; + } else if ((g_ascii_strcasecmp("m", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 0 1 0"; + } else { + colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 1 0"; + } + } else { + if ((g_ascii_strcasecmp("r", channel) == 0)) { + colors << "0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0"; + } else if ((g_ascii_strcasecmp("g", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0"; + } else if ((g_ascii_strcasecmp("b", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0"; + } else if ((g_ascii_strcasecmp("c", channel) == 0)) { + colors << "0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 -1 0 0 1 0"; + } else if ((g_ascii_strcasecmp("m", channel) == 0)) { + colors << "0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 -1 0 1 0"; + } else { + colors << "0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 -1 1 0"; + } + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Extract Channel\">\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"%s 0 \" result=\"colormatrix\" />\n" + "<feBlend in2=\"BackgroundImage\" mode=\"%s\" result=\"blend\" />\n" + "</filter>\n", colors.str().c_str(), blend.str().c_str() ); + + return _filter; +}; /* ExtractChannel filter */ + +/** + \brief Custom predefined Fade to Black or White filter. + + Fade to black or white. + + Filter's parameters: + * Level (0.->1., default 1.) -> colorMatrix (RVB entries) + * Fade to (enum [black|white], default black) -> colorMatrix (RVB entries) + + Matrix + black white + Lv 0 0 0 0 Lv 0 0 1-lv 0 + 0 Lv 0 0 0 0 Lv 0 1-lv 0 + 0 0 Lv 0 0 0 0 Lv 1-lv 0 + 0 0 0 1 0 0 0 0 1 0 +*/ +class FadeToBW : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + FadeToBW ( ) : Filter() { }; + ~FadeToBW ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Fade to Black or White") "</name>\n" + "<id>org.inkscape.effect.filter.FadeToBW</id>\n" + "<param name=\"level\" gui-text=\"" N_("Level") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n" + "<param name=\"fadeto\" gui-text=\"" N_("Fade to:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"black\">" N_("Black") "</option>\n" + "<option value=\"white\">" N_("White") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Fade to black or white") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new FadeToBW()); + }; +}; + +gchar const * +FadeToBW::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream level; + std::ostringstream wlevel; + + level << ext->get_param_float("level"); + + const gchar *fadeto = ext->get_param_optiongroup("fadeto"); + if ((g_ascii_strcasecmp("white", fadeto) == 0)) { + // White + wlevel << (1 - ext->get_param_float("level")); + } else { + // Black + wlevel << "0"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Fade to Black or White\">\n" + "<feColorMatrix values=\"%s 0 0 0 %s 0 %s 0 0 %s 0 0 %s 0 %s 0 0 0 1 0\" />\n" + "</filter>\n", level.str().c_str(), wlevel.str().c_str(), + level.str().c_str(), wlevel.str().c_str(), + level.str().c_str(), wlevel.str().c_str() ); + + return _filter; +}; /* Fade to black or white filter */ + +/** + \brief Custom predefined Greyscale filter. + + Customize greyscale components. + + Filter's parameters: + * Red (-10.->10., default .21) -> colorMatrix (values) + * Green (-10.->10., default .72) -> colorMatrix (values) + * Blue (-10.->10., default .072) -> colorMatrix (values) + * Lightness (-10.->10., default 0.) -> colorMatrix (values) + * Transparent (boolean, default false) -> matrix structure + + Matrix: + normal transparency + R G B St 0 0 0 0 0 0 + R G B St 0 0 0 0 0 0 + R G B St 0 0 0 0 0 0 + 0 0 0 1 0 R G B 1-St 0 +*/ +class Greyscale : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Greyscale ( ) : Filter() { }; + ~Greyscale ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Greyscale") "</name>\n" + "<id>org.inkscape.effect.filter.Greyscale</id>\n" + "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0.21</param>\n" + "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0.72</param>\n" + "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0.072</param>\n" + "<param name=\"strength\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0</param>\n" + "<param name=\"transparent\" gui-text=\"" N_("Transparent") "\" type=\"bool\" >false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Customize greyscale components") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Greyscale()); + }; +}; + +gchar const * +Greyscale::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream red; + std::ostringstream green; + std::ostringstream blue; + std::ostringstream strength; + std::ostringstream redt; + std::ostringstream greent; + std::ostringstream bluet; + std::ostringstream strengtht; + std::ostringstream transparency; + std::ostringstream line; + + red << ext->get_param_float("red"); + green << ext->get_param_float("green"); + blue << ext->get_param_float("blue"); + strength << ext->get_param_float("strength"); + + redt << - ext->get_param_float("red"); + greent << - ext->get_param_float("green"); + bluet << - ext->get_param_float("blue"); + strengtht << 1 - ext->get_param_float("strength"); + + if (ext->get_param_bool("transparent")) { + line << "0 0 0 0"; + transparency << redt.str().c_str() << " " << greent.str().c_str() << " " << bluet.str().c_str() << " " << strengtht.str().c_str(); + } else { + line << red.str().c_str() << " " << green.str().c_str() << " " << blue.str().c_str() << " " << strength.str().c_str(); + transparency << "0 0 0 1"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Greyscale\">\n" + "<feColorMatrix values=\"%s 0 %s 0 %s 0 %s 0 \" />\n" + "</filter>\n", line.str().c_str(), line.str().c_str(), line.str().c_str(), transparency.str().c_str() ); + return _filter; +}; /* Greyscale filter */ + +/** + \brief Custom predefined Invert filter. + + Manage hue, lightness and transparency inversions + + Filter's parameters: + * Invert hue (boolean, default false) -> color1 (values, true: 180, false: 0) + * Invert lightness (boolean, default false) -> color1 (values, true: 180, false: 0; XOR with Invert hue), + color2 (values: from a00 to a22, if 1, set -1 and set 1 in ax4, if -1, set 1 and set 0 in ax4) + * Invert transparency (boolean, default false) -> color2 (values: negate a30, a31 and a32, substract 1 from a33) + * Invert channels (enum, default Red and blue) -> color2 (values -for R&B: swap ax0 and ax2 in the first 3 lines) + * Light transparency (0.->1., default 0.) -> color2 (values: a33=a33-x) +*/ + +class Invert : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Invert ( ) : Filter() { }; + ~Invert ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Invert") "</name>\n" + "<id>org.inkscape.effect.filter.Invert</id>\n" + "<param name=\"channels\" gui-text=\"" N_("Invert channels:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"0\">" N_("No inversion") "</option>\n" + "<option value=\"1\">" N_("Red and blue") "</option>\n" + "<option value=\"2\">" N_("Red and green") "</option>\n" + "<option value=\"3\">" N_("Green and blue") "</option>\n" + "</param>\n" + "<param name=\"opacify\" gui-text=\"" N_("Light transparency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1\">0</param>\n" + "<param name=\"hue\" gui-text=\"" N_("Invert hue") "\" type=\"bool\" >false</param>\n" + "<param name=\"lightness\" gui-text=\"" N_("Invert lightness") "\" type=\"bool\" >false</param>\n" + "<param name=\"transparency\" gui-text=\"" N_("Invert transparency") "\" type=\"bool\" >false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Manage hue, lightness and transparency inversions") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Invert()); + }; + +}; + +gchar const * +Invert::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream line1; + std::ostringstream line2; + std::ostringstream line3; + + std::ostringstream col5; + std::ostringstream transparency; + std::ostringstream hue; + + if (ext->get_param_bool("hue") ^ ext->get_param_bool("lightness")) { + hue << "<feColorMatrix type=\"hueRotate\" values=\"180\" result=\"color1\" />\n"; + } else { + hue << ""; + } + + if (ext->get_param_bool("transparency")) { + transparency << "0.21 0.72 0.07 " << 1 - ext->get_param_float("opacify"); + } else { + transparency << "-0.21 -0.72 -0.07 " << 2 - ext->get_param_float("opacify"); + } + + if (ext->get_param_bool("lightness")) { + switch (atoi(ext->get_param_optiongroup("channels"))) { + case 1: + line1 << "0 0 -1"; + line2 << "0 -1 0"; + line3 << "-1 0 0"; + break; + case 2: + line1 << "0 -1 0"; + line2 << "-1 0 0"; + line3 << "0 0 -1"; + break; + case 3: + line1 << "-1 0 0"; + line2 << "0 0 -1"; + line3 << "0 -1 0"; + break; + default: + line1 << "-1 0 0"; + line2 << "0 -1 0"; + line3 << "0 0 -1"; + break; + } + col5 << "1"; + } else { + switch (atoi(ext->get_param_optiongroup("channels"))) { + case 1: + line1 << "0 0 1"; + line2 << "0 1 0"; + line3 << "1 0 0"; + break; + case 2: + line1 << "0 1 0"; + line2 << "1 0 0"; + line3 << "0 0 1"; + break; + case 3: + line1 << "1 0 0"; + line2 << "0 0 1"; + line3 << "0 1 0"; + break; + default: + line1 << "1 0 0"; + line2 << "0 1 0"; + line3 << "0 0 1"; + break; + } + col5 << "0"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Invert\">\n" + "%s" + "<feColorMatrix values=\"%s 0 %s %s 0 %s %s 0 %s %s 0 \" result=\"color2\" />\n" + "</filter>\n", hue.str().c_str(), + line1.str().c_str(), col5.str().c_str(), + line2.str().c_str(), col5.str().c_str(), + line3.str().c_str(), col5.str().c_str(), + transparency.str().c_str() ); + + return _filter; +}; /* Invert filter */ + +/** + \brief Custom predefined Lighting filter. + + Modify lights and shadows separately. + + Filter's parameters: + * Lightness (0.->20., default 1.) -> component (amplitude) + * Shadow (0.->20., default 1.) -> component (exponent) + * Offset (-1.->1., default 0.) -> component (offset) +*/ +class Lighting : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Lighting ( ) : Filter() { }; + ~Lighting ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Lighting") "</name>\n" + "<id>org.inkscape.effect.filter.Lighting</id>\n" + "<param name=\"amplitude\" gui-text=\"" N_("Lights") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"20.00\">1</param>\n" + "<param name=\"exponent\" gui-text=\"" N_("Shadows") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"20.00\">1</param>\n" + "<param name=\"offset\" gui-text=\"" N_("Offset") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-1.00\" max=\"1.00\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Modify lights and shadows separately") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Lighting()); + }; +}; + +gchar const * +Lighting::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream amplitude; + std::ostringstream exponent; + std::ostringstream offset; + + amplitude << ext->get_param_float("amplitude"); + exponent << ext->get_param_float("exponent"); + offset << ext->get_param_float("offset"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Lighting\">\n" + "<feComponentTransfer in=\"blur\" result=\"component\" >\n" + "<feFuncR type=\"gamma\" amplitude=\"%s\" exponent=\"%s\" offset=\"%s\" />\n" + "<feFuncG type=\"gamma\" amplitude=\"%s\" exponent=\"%s\" offset=\"%s\" />\n" + "<feFuncB type=\"gamma\" amplitude=\"%s\" exponent=\"%s\" offset=\"%s\" />\n" + "</feComponentTransfer>\n" + "</filter>\n", amplitude.str().c_str(), exponent.str().c_str(), offset.str().c_str(), + amplitude.str().c_str(), exponent.str().c_str(), offset.str().c_str(), + amplitude.str().c_str(), exponent.str().c_str(), offset.str().c_str() ); + + return _filter; +}; /* Lighting filter */ + +/** + \brief Custom predefined Lightness-Contrast filter. + + Modify lightness and contrast separately. + + Filter's parameters: + * Lightness (0.->100., default 0.) -> colorMatrix + * Contrast (0.->100., default 0.) -> colorMatrix + + Matrix: + Co/10 0 0 1+(Co-1)*Li/2000 -(Co-1)/20 + 0 Co/10 0 1+(Co-1)*Li/2000 -(Co-1)/20 + 0 0 Co/10 1+(Co-1)*Li/2000 -(Co-1)/20 + 0 0 0 1 0 +*/ +class LightnessContrast : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + LightnessContrast ( ) : Filter() { }; + ~LightnessContrast ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Lightness-Contrast") "</name>\n" + "<id>org.inkscape.effect.filter.LightnessContrast</id>\n" + "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-100\" max=\"100\">0</param>\n" + "<param name=\"contrast\" gui-text=\"" N_("Contrast") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-100\" max=\"100\">0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Modify lightness and contrast separately") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new LightnessContrast()); + }; +}; + +gchar const * +LightnessContrast::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream lightness; + std::ostringstream contrast; + std::ostringstream contrast5; + + gfloat c5; + if (ext->get_param_float("contrast") > 0) { + contrast << (1 + ext->get_param_float("contrast") / 10); + c5 = (- ext->get_param_float("contrast") / 20); + } else { + contrast << (1 + ext->get_param_float("contrast") / 100); + c5 =(- ext->get_param_float("contrast") / 200); + } + + contrast5 << c5; + lightness << ((1 - c5) * ext->get_param_float("lightness") / 100); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Lightness-Contrast\">\n" + "<feColorMatrix values=\"%s 0 0 %s %s 0 %s 0 %s %s 0 0 %s %s %s 0 0 0 1 0\" />\n" + "</filter>\n", contrast.str().c_str(), lightness.str().c_str(), contrast5.str().c_str(), + contrast.str().c_str(), lightness.str().c_str(), contrast5.str().c_str(), + contrast.str().c_str(), lightness.str().c_str(), contrast5.str().c_str() ); + + return _filter; +}; /* Lightness-Contrast filter */ + +/** + \brief Custom predefined Nudge RGB filter. + + Nudge RGB channels separately and blend them to different types of backgrounds + + Filter's parameters: + Offsets + * Red + * x (-100.->100., default -6) -> offset1 (dx) + * y (-100.->100., default -6) -> offset1 (dy) + * Green + * x (-100.->100., default 6) -> offset2 (dx) + * y (-100.->100., default 7) -> offset2 (dy) + * Blue + * x (-100.->100., default 1) -> offset3 (dx) + * y (-100.->100., default -16) -> offset3 (dy) + Color + * Background color (guint, default 255)-> flood (flood-color, flood-opacity) + +*/ +class NudgeRGB : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + NudgeRGB ( ) : Filter() { }; + ~NudgeRGB ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Nudge RGB") "</name>\n" + "<id>org.inkscape.effect.filter.NudgeRGB</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"offsettab\" gui-text=\"Offset\">\n" + "<label appearance=\"header\">" N_("Red offset") "</label>\n" + "<param name=\"rx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n" + "<param name=\"ry\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n" + "<label appearance=\"header\">" N_("Green offset") "</label>\n" + "<param name=\"gx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">6</param>\n" + "<param name=\"gy\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">7</param>\n" + "<label appearance=\"header\">" N_("Blue offset") "</label>\n" + "<param name=\"bx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">1</param>\n" + "<param name=\"by\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-16</param>\n" + "</page>\n" + "<page name=\"coltab\" gui-text=\"Color\">\n" + "<param name=\"color\" gui-text=\"" N_("Background color") "\" type=\"color\">255</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Nudge RGB channels separately and blend them to different types of backgrounds") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new NudgeRGB()); + }; +}; + +gchar const * +NudgeRGB::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream rx; + std::ostringstream ry; + std::ostringstream gx; + std::ostringstream gy; + std::ostringstream bx; + std::ostringstream by; + + std::ostringstream a; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + + rx << ext->get_param_float("rx"); + ry << ext->get_param_float("ry"); + gx << ext->get_param_float("gx"); + gy << ext->get_param_float("gy"); + bx << ext->get_param_float("bx"); + by << ext->get_param_float("by"); + + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Nudge RGB\">\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 \" result=\"colormatrix1\" />\n" + "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset1\" />\n" + "<feBlend in2=\"flood\" mode=\"screen\" result=\"blend1\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 \" result=\"colormatrix2\" />\n" + "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset2\" />\n" + "<feBlend in2=\"blend1\" mode=\"screen\" result=\"blend2\" />\n" + "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset3\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 \" result=\"colormatrix3\" />\n" + "<feBlend in2=\"offset3\" mode=\"screen\" result=\"blend3\" />\n" + "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + rx.str().c_str(), ry.str().c_str(), + gx.str().c_str(), gy.str().c_str(), + bx.str().c_str(), by.str().c_str() ); + + return _filter; + +}; /* Nudge RGB filter */ + +/** + \brief Custom predefined Nudge CMY filter. + + Nudge CMY channels separately and blend them to different types of backgrounds + + Filter's parameters: + Offsets + * Cyan + * x (-100.->100., default -6) -> offset1 (dx) + * y (-100.->100., default -6) -> offset1 (dy) + * Magenta + * x (-100.->100., default 6) -> offset2 (dx) + * y (-100.->100., default 7) -> offset2 (dy) + * Yellow + * x (-100.->100., default 1) -> offset3 (dx) + * y (-100.->100., default -16) -> offset3 (dy) + Color + * Background color (guint, default -1)-> flood (flood-color, flood-opacity) +*/ +class NudgeCMY : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + NudgeCMY ( ) : Filter() { }; + ~NudgeCMY ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Nudge CMY") "</name>\n" + "<id>org.inkscape.effect.filter.NudgeCMY</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"offsettab\" gui-text=\"Offset\">\n" + "<label appearance=\"header\">" N_("Cyan offset") "</label>\n" + "<param name=\"cx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n" + "<param name=\"cy\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n" + "<label appearance=\"header\">" N_("Magenta offset") "</label>\n" + "<param name=\"mx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">6</param>\n" + "<param name=\"my\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">7</param>\n" + "<label appearance=\"header\">" N_("Yellow offset") "</label>\n" + "<param name=\"yx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">1</param>\n" + "<param name=\"yy\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-16</param>\n" + "</page>\n" + "<page name=\"coltab\" gui-text=\"Color\">\n" + "<param name=\"color\" gui-text=\"" N_("Background color") "\" type=\"color\">-1</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Nudge CMY channels separately and blend them to different types of backgrounds") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new NudgeCMY()); + }; +}; + +gchar const * +NudgeCMY::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream cx; + std::ostringstream cy; + std::ostringstream mx; + std::ostringstream my; + std::ostringstream yx; + std::ostringstream yy; + + std::ostringstream a; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + + cx << ext->get_param_float("cx"); + cy << ext->get_param_float("cy"); + mx << ext->get_param_float("mx"); + my << ext->get_param_float("my"); + yx << ext->get_param_float("yx"); + yy << ext->get_param_float("yy"); + + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Nudge CMY\">\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 -1 0 0 1 0 \" result=\"colormatrix1\" />\n" + "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset1\" />\n" + "<feBlend in2=\"flood\" mode=\"multiply\" result=\"blend1\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 -1 0 1 0 \" result=\"colormatrix2\" />\n" + "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset2\" />\n" + "<feBlend in2=\"blend1\" mode=\"multiply\" result=\"blend2\" />\n" + "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset3\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 -1 1 0 \" result=\"colormatrix3\" />\n" + "<feBlend in2=\"offset3\" mode=\"multiply\" result=\"blend3\" />\n" + "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + cx.str().c_str(), cy.str().c_str(), + mx.str().c_str(), my.str().c_str(), + yx.str().c_str(), yy.str().c_str() ); + + return _filter; + +}; /* Nudge CMY filter */ + +/** + \brief Custom predefined Quadritone filter. + + Replace hue by two colors. + + Filter's parameters: + * Hue distribution (0->360, default 280) -> colormatrix1 (values) + * Colors (0->360, default 100) -> colormatrix3 (values) + * Blend mode 1 (enum, default Normal) -> blend1 (mode) + * Over-saturation (0.->1., default 0) -> composite1 (k2) + * Blend mode 2 (enum, default Normal) -> blend2 (mode) +*/ + +class Quadritone : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Quadritone ( ) : Filter() { }; + ~Quadritone ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Quadritone Fantasy") "</name>\n" + "<id>org.inkscape.effect.filter.Quadritone</id>\n" + "<param name=\"dist\" gui-text=\"" N_("Hue distribution (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">280</param>\n" + "<param name=\"colors\" gui-text=\"" N_("Colors") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">100</param>\n" + "<param name=\"blend1\" gui-text=\"" N_("Blend 1:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "</param>\n" + "<param name=\"sat\" gui-text=\"" N_("Over-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"1.00\">0</param>\n" + "<param name=\"blend2\" gui-text=\"" N_("Blend 2:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Replace hue by two colors") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Quadritone()); + }; + +}; + +gchar const * +Quadritone::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream dist; + std::ostringstream colors; + std::ostringstream blend1; + std::ostringstream sat; + std::ostringstream blend2; + + dist << ext->get_param_int("dist"); + colors << ext->get_param_int("colors"); + blend1 << ext->get_param_optiongroup("blend1"); + sat << ext->get_param_float("sat"); + blend2 << ext->get_param_optiongroup("blend2"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Quadritone fantasy\">\n" + "<feColorMatrix in=\"SourceGraphic\" type=\"hueRotate\" values=\"%s\" result=\"colormatrix1\" />\n" + "<feColorMatrix type=\"matrix\" values=\"0.5 0 0.5 0 0 0 1 0 0 0 0.5 0 0.5 0 0 0 0 0 1 0 \" result=\"colormatrix2\" />\n" + "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"colormatrix3\" />\n" + "<feBlend in2=\"colormatrix3\" mode=\"%s\" result=\"blend1\" />\n" + "<feColorMatrix type=\"matrix\" values=\"2.5 -0.75 -0.75 0 0 -0.75 2.5 -0.75 0 0 -0.75 -0.75 2.5 0 0 0 0 0 1 0 \" result=\"colormatrix4\" />\n" + "<feComposite in=\"colormatrix4\" in2=\"blend1\" operator=\"arithmetic\" k2=\"%s\" result=\"composite1\" />\n" + "<feBlend in2=\"blend1\" mode=\"%s\" result=\"blend2\" />\n" + "</filter>\n", dist.str().c_str(), colors.str().c_str(), blend1.str().c_str(), sat.str().c_str(), blend2.str().c_str() ); + + return _filter; +}; /* Quadritone filter */ + + +/** + \brief Custom predefined Simple blend filter. + + Simple blend filter. + + Filter's parameters: + * Color (guint, default 16777215) -> flood1 (flood-opacity, flood-color) + * Blend mode (enum, default Hue) -> blend1 (mode) +*/ +class SimpleBlend : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + SimpleBlend ( ) : Filter() { }; + ~SimpleBlend ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Simple blend") "</name>\n" + "<id>org.inkscape.effect.filter.SimpleBlend</id>\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">16777215</param>\n" + "<param name=\"blendmode\" gui-text=\"" N_("Blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"saturation\">" N_("Saturation") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "<option value=\"difference\">" N_("Difference") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "<option value=\"luminosity\">" N_("Luminosity") "</option>\n" + "<option value=\"overlay\">" N_("Overlay") "</option>\n" + "<option value=\"color-dodge\">" N_("Color Dodge") "</option>\n" + "<option value=\"color-burn\">" N_("Color Burn") "</option>\n" + "<option value=\"color\">" N_("Color") "</option>\n" + "<option value=\"hard-light\">" N_("Hard Light") "</option>\n" + "<option value=\"hue\">" N_("Hue") "</option>\n" + "<option value=\"exclusion\">" N_("Exclusion") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Simple blend filter") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new SimpleBlend()); + }; +}; + +gchar const * +SimpleBlend::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream a; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream blend; + + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + blend << ext->get_param_optiongroup("blendmode"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Simple blend\">\n" + "<feFlood result=\"flood1\" flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" />\n" + "<feBlend result=\"blend1\" in=\"flood1\" in2=\"SourceGraphic\" mode=\"%s\" />\n" + "<feComposite operator=\"in\" in=\"blend1\" in2=\"SourceGraphic\" />\n" + "</filter>\n", r.str().c_str(), g.str().c_str(), b.str().c_str(), + a.str().c_str(), blend.str().c_str()); + + return _filter; +}; /* SimpleBlend filter */ + +/** + \brief Custom predefined Solarize filter. + + Classic photographic solarization effect. + + Filter's parameters: + * Type (enum, default "Solarize") -> + Solarize = blend1 (mode="darken"), blend2 (mode="screen") + Moonarize = blend1 (mode="lighten"), blend2 (mode="multiply") [No other access to the blend modes] + * Hue rotation (0->360, default 0) -> colormatrix1 (values) +*/ + + +class Solarize : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Solarize ( ) : Filter() { }; + ~Solarize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Solarize") "</name>\n" + "<id>org.inkscape.effect.filter.Solarize</id>\n" + "<param name=\"rotate\" gui-text=\"" N_("Hue rotation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">0</param>\n" + "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"solarize\">" N_("Solarize") "</option>\n" + "<option value=\"moonarize\">" N_("Moonarize") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Classic photographic solarization effect") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Solarize()); + }; + +}; + +gchar const * +Solarize::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream rotate; + std::ostringstream blend1; + std::ostringstream blend2; + + rotate << ext->get_param_int("rotate"); + const gchar *type = ext->get_param_optiongroup("type"); + if ((g_ascii_strcasecmp("solarize", type) == 0)) { + // Solarize + blend1 << "darken"; + blend2 << "screen"; + } else { + // Moonarize + blend1 << "lighten"; + blend2 << "multiply"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Solarize\">\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 1 \" />\n" + "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"colormatrix2\" />\n" + "<feColorMatrix in=\"colormatrix2\" values=\"-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 \" result=\"colormatrix3\" />\n" + "<feBlend in=\"colormatrix3\" in2=\"colormatrix2\" mode=\"%s\" result=\"blend1\" />\n" + "<feBlend in2=\"blend1\" mode=\"%s\" result=\"blend2\" />\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" />\n" + "</filter>\n", rotate.str().c_str(), blend1.str().c_str(), blend2.str().c_str() ); + + return _filter; +}; /* Solarize filter */ + +/** + \brief Custom predefined Tritone filter. + + Create a custom tritone palette with additional glow, blend modes and hue moving. + + Filter's parameters: + * Option (enum, default Normal) -> + Normal = composite1 (in2="flood"), composite2 (in="p", in2="blend6"), blend6 (in2="composite1") + Enhance hue = Normal + composite2 (in="SourceGraphic") + Phosphorescence = Normal + blend6 (in2="SourceGraphic") composite2 (in="blend6", in2="composite1") + PhosphorescenceB = Normal + blend6 (in2="flood") composite1 (in2="SourceGraphic") + Hue to background = Normal + composite1 (in2="BackgroundImage") [a template with an activated background is needed, or colors become black] + * Hue distribution (0->360, default 0) -> colormatrix1 (values) + * Colors (guint, default -73203457) -> flood (flood-opacity, flood-color) + * Global blend (enum, default Lighten) -> blend5 (mode) [Multiply, Screen, Darken, Lighten only!] + * Glow (0.01->10., default 0.01) -> blur (stdDeviation) + * Glow & blend (enum, default Normal) -> blend6 (mode) [Normal, Multiply and Darken only!] + * Local light (0.->10., default 0) -> composite2 (k1) + * Global light (0.->10., default 1) -> composite2 (k3) [k2 must be fixed to 1]. +*/ + +class Tritone : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Tritone ( ) : Filter() { }; + ~Tritone ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Tritone") "</name>\n" + "<id>org.inkscape.effect.filter.Tritone</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"enhue\">" N_("Enhance hue") "</option>\n" + "<option value=\"phospho\">" N_("Phosphorescence") "</option>\n" + "<option value=\"phosphoB\">" N_("Colored nights") "</option>\n" + "<option value=\"htb\">" N_("Hue to background") "</option>\n" + "</param>\n" + "<param name=\"globalblend\" gui-text=\"" N_("Global blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "</param>\n" + "<param name=\"glow\" gui-text=\"" N_("Glow") "\" type=\"float\" appearance=\"full\" min=\"0.01\" max=\"10\">0.01</param>\n" + "<param name=\"glowblend\" gui-text=\"" N_("Glow blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "</param>\n" + "<param name=\"llight\" gui-text=\"" N_("Local light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">0</param>\n" + "<param name=\"glight\" gui-text=\"" N_("Global light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">1</param>\n" + "</page>\n" + "<page name=\"co1tab\" gui-text=\"Color\">\n" + "<param name=\"dist\" gui-text=\"" N_("Hue distribution (°):") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">0</param>\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">-73203457</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Color") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Create a custom tritone palette with additional glow, blend modes and hue moving") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Tritone()); + }; + +}; + +gchar const * +Tritone::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream dist; + std::ostringstream a; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream globalblend; + std::ostringstream glow; + std::ostringstream glowblend; + std::ostringstream llight; + std::ostringstream glight; + std::ostringstream c1in2; + std::ostringstream c2in; + std::ostringstream c2in2; + std::ostringstream b6in2; + + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + globalblend << ext->get_param_optiongroup("globalblend"); + dist << ext->get_param_int("dist"); + glow << ext->get_param_float("glow"); + glowblend << ext->get_param_optiongroup("glowblend"); + llight << ext->get_param_float("llight"); + glight << ext->get_param_float("glight"); + + const gchar *type = ext->get_param_optiongroup("type"); + if ((g_ascii_strcasecmp("enhue", type) == 0)) { + // Enhance hue + c1in2 << "flood"; + c2in << "SourceGraphic"; + c2in2 << "blend6"; + b6in2 << "composite1"; + } else if ((g_ascii_strcasecmp("phospho", type) == 0)) { + // Phosphorescence + c1in2 << "flood"; + c2in << "blend6"; + c2in2 << "composite1"; + b6in2 << "SourceGraphic"; + } else if ((g_ascii_strcasecmp("phosphoB", type) == 0)) { + // Phosphorescence B + c1in2 << "SourceGraphic"; + c2in << "blend6"; + c2in2 << "composite1"; + b6in2 << "flood"; + } else if ((g_ascii_strcasecmp("htb", type) == 0)) { + // Hue to background + c1in2 << "BackgroundImage"; + c2in << "blend2"; + c2in2 << "blend6"; + b6in2 << "composite1"; + } else { + // Normal + c1in2 << "flood"; + c2in << "blend2"; + c2in2 << "blend6"; + b6in2 << "composite"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Tritone\">\n" + "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"colormatrix1\" />\n" + "<feColorMatrix in=\"colormatrix1\" type=\"matrix\" values=\"1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1 \" result=\"colormatrix2\" />\n" + "<feColorMatrix in=\"colormatrix1\" type=\"matrix\" values=\"0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 \" result=\"colormatrix3\" />\n" + "<feColorMatrix in=\"colormatrix1\" type=\"matrix\" values=\"0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 \" result=\"colormatrix4\" />\n" + "<feBlend in=\"colormatrix2\" in2=\"colormatrix3\" mode=\"darken\" result=\"blend1\" />\n" + "<feBlend in=\"blend1\" in2=\"colormatrix4\" mode=\"darken\" result=\"blend2\" />\n" + "<feBlend in=\"colormatrix2\" in2=\"colormatrix3\" mode=\"lighten\" result=\"blend3\" />\n" + "<feBlend in=\"blend3\" in2=\"colormatrix4\" mode=\"lighten\" result=\"blend4\" />\n" + "<feComponentTransfer in=\"blend4\" result=\"componentTransfer\">\n" + "<feFuncR type=\"linear\" slope=\"0\" />\n" + "</feComponentTransfer>\n" + "<feBlend in=\"blend2\" in2=\"componentTransfer\" mode=\"%s\" result=\"blend5\" />\n" + "<feColorMatrix in=\"blend5\" type=\"matrix\" values=\"-1 1 0 0 0 -1 1 0 0 0 -1 1 0 0 0 0 0 0 0 1 \" result=\"colormatrix5\" />\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feComposite in=\"colormatrix5\" in2=\"%s\" operator=\"arithmetic\" k1=\"1\" result=\"composite1\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n" + "<feBlend in2=\"%s\" mode=\"%s\" result=\"blend6\" />\n" + "<feComposite in=\"%s\" in2=\"%s\" operator=\"arithmetic\" k1=\"%s\" k2=\"1\" k3=\"%s\" k4=\"0\" result=\"composite2\" />\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite3\" />\n" + "</filter>\n", dist.str().c_str(), globalblend.str().c_str(), + a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + c1in2.str().c_str(), glow.str().c_str(), b6in2.str().c_str(), glowblend.str().c_str(), + c2in.str().c_str(), c2in2.str().c_str(), llight.str().c_str(), glight.str().c_str() ); + + return _filter; +}; /* Tritone filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'COLOR' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_COLOR_H__ */ diff --git a/src/extension/internal/filter/distort.h b/src/extension/internal/filter/distort.h new file mode 100644 index 0000000..b8f66b6 --- /dev/null +++ b/src/extension/internal/filter/distort.h @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_DISTORT_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_DISTORT_H__ +/* Change the 'DISTORT' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Distort filters + * Felt Feather + * Roughen + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined FeltFeather filter. + + Blur and displace edges of shapes and pictures + + Filter's parameters: + * Type (enum, default "In") -> + in = map (in="composite3") + out = map (in="blur") + * Horizontal blur (0.01->30., default 15) -> blur (stdDeviation) + * Vertical blur (0.01->30., default 15) -> blur (stdDeviation) + * Dilatation (n-1th value, 0.->100., default 1) -> colormatrix (matrix) + * Erosion (nth value, 0.->100., default 0) -> colormatrix (matrix) + * Stroke (enum, default "Normal") -> + Normal = composite4 (operator="atop") + Wide = composite4 (operator="over") + Narrow = composite4 (operator="in") + No fill = composite4 (operator="xor") + * Roughness (group) + * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type) + * Horizontal frequency (0.001->1., default 0.05) -> turbulence (baseFrequency [/100]) + * Vertical frequency (0.001->1., default 0.05) -> turbulence (baseFrequency [/100]) + * Complexity (1->5, default 3) -> turbulence (numOctaves) + * Variation (0->100, default 0) -> turbulence (seed) + * Intensity (0.0->100., default 30) -> displacement (scale) +*/ + +class FeltFeather : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + FeltFeather ( ) : Filter() { }; + ~FeltFeather ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Felt Feather") "</name>\n" + "<id>org.inkscape.effect.filter.FeltFeather</id>\n" + "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"in\">" N_("In") "</option>\n" + "<option value=\"out\">" N_("Out") "</option>\n" + "</param>\n" + "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">15</param>\n" + "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">15</param>\n" + "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">1</param>\n" + "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">0</param>\n" + "<param name=\"stroke\" gui-text=\"" N_("Stroke:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"atop\">" N_("Normal") "</option>\n" + "<option value=\"over\">" N_("Wide") "</option>\n" + "<option value=\"in\">" N_("Narrow") "</option>\n" + "<option value=\"xor\">" N_("No fill") "</option>\n" + "</param>\n" + "<param name=\"turbulence\" indent=\"1\" gui-text=\"" N_("Turbulence:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n" + "<option value=\"turbulence\">" N_("Turbulence") "</option>\n" + "</param>\n" + "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.\">5</param>\n" + "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.\">5</param>\n" + "<param name=\"complexity\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">3</param>\n" + "<param name=\"variation\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"100\">0</param>\n" + "<param name=\"intensity\" gui-text=\"" N_("Intensity") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"100\">30</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Distort") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Blur and displace edges of shapes and pictures") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new FeltFeather()); + }; + +}; + +gchar const * +FeltFeather::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + + std::ostringstream hblur; + std::ostringstream vblur; + std::ostringstream dilat; + std::ostringstream erosion; + + std::ostringstream turbulence; + std::ostringstream hfreq; + std::ostringstream vfreq; + std::ostringstream complexity; + std::ostringstream variation; + std::ostringstream intensity; + + std::ostringstream map; + std::ostringstream stroke; + + hblur << ext->get_param_float("hblur"); + vblur << ext->get_param_float("vblur"); + dilat << ext->get_param_float("dilat"); + erosion << -ext->get_param_float("erosion"); + + turbulence << ext->get_param_optiongroup("turbulence"); + hfreq << ext->get_param_float("hfreq") / 100; + vfreq << ext->get_param_float("vfreq") / 100; + complexity << ext->get_param_int("complexity"); + variation << ext->get_param_int("variation"); + intensity << ext->get_param_float("intensity"); + + stroke << ext->get_param_optiongroup("stroke"); + + const gchar *maptype = ext->get_param_optiongroup("type"); + if (g_ascii_strcasecmp("in", maptype) == 0) { + map << "composite3"; + } else { + map << "blur"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" x=\"-0.3\" width=\"1.6\" y=\"-0.3\" height=\"1.6\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Felt Feather\">\n" + "<feGaussianBlur stdDeviation=\"%s %s\" result=\"blur\" />\n" + "<feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"atop\" result=\"composite1\" />\n" + "<feComposite in2=\"composite1\" operator=\"in\" result=\"composite2\" />\n" + "<feComposite in2=\"composite2\" operator=\"in\" result=\"composite3\" />\n" + "<feTurbulence type=\"%s\" numOctaves=\"%s\" seed=\"%s\" baseFrequency=\"%s %s\" result=\"turbulence\" />\n" + "<feDisplacementMap in=\"%s\" in2=\"turbulence\" xChannelSelector=\"R\" scale=\"%s\" yChannelSelector=\"G\" result=\"map\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix\" />\n" + "<feComposite in=\"composite3\" in2=\"colormatrix\" operator=\"%s\" result=\"composite4\" />\n" + "</filter>\n", hblur.str().c_str(), vblur.str().c_str(), + turbulence.str().c_str(), complexity.str().c_str(), variation.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), + map.str().c_str(), intensity.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), stroke.str().c_str() ); + + return _filter; +}; /* Felt feather filter */ + +/** + \brief Custom predefined Roughen filter. + + Small-scale roughening to edges and content + + Filter's parameters: + * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type) + * Horizontal frequency (0.001->10., default 0.013) -> turbulence (baseFrequency [/100]) + * Vertical frequency (0.001->10., default 0.013) -> turbulence (baseFrequency [/100]) + * Complexity (1->5, default 5) -> turbulence (numOctaves) + * Variation (1->360, default 1) -> turbulence (seed) + * Intensity (0.0->50., default 6.6) -> displacement (scale) +*/ + +class Roughen : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Roughen ( ) : Filter() { }; + ~Roughen ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Roughen") "</name>\n" + "<id>org.inkscape.effect.filter.Roughen</id>\n" + "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n" + "<option value=\"turbulence\">" N_("Turbulence") "</option>\n" + "</param>\n" + "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"1000.00\">1.3</param>\n" + "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"1000.00\">1.3</param>\n" + "<param name=\"complexity\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">5</param>\n" + "<param name=\"variation\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"360\">0</param>\n" + "<param name=\"intensity\" gui-text=\"" N_("Intensity") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"50\">6.6</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Distort") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Small-scale roughening to edges and content") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Roughen()); + }; + +}; + +gchar const * +Roughen::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream type; + std::ostringstream hfreq; + std::ostringstream vfreq; + std::ostringstream complexity; + std::ostringstream variation; + std::ostringstream intensity; + + type << ext->get_param_optiongroup("type"); + hfreq << ext->get_param_float("hfreq") / 100; + vfreq << ext->get_param_float("vfreq") / 100; + complexity << ext->get_param_int("complexity"); + variation << ext->get_param_int("variation"); + intensity << ext->get_param_float("intensity"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Roughen\">\n" + "<feTurbulence type=\"%s\" numOctaves=\"%s\" seed=\"%s\" baseFrequency=\"%s %s\" result=\"turbulence\" />\n" + "<feDisplacementMap in=\"SourceGraphic\" in2=\"turbulence\" scale=\"%s\" yChannelSelector=\"G\" xChannelSelector=\"R\" />\n" + "</filter>\n", type.str().c_str(), complexity.str().c_str(), variation.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), intensity.str().c_str()); + + return _filter; +}; /* Roughen filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'DISTORT' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_DISTORT_H__ */ diff --git a/src/extension/internal/filter/filter-all.cpp b/src/extension/internal/filter/filter-all.cpp new file mode 100644 index 0000000..5aa3900 --- /dev/null +++ b/src/extension/internal/filter/filter-all.cpp @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2008 Authors: + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "filter.h" + +/* Put your filter here */ +#include "bevels.h" +#include "blurs.h" +#include "bumps.h" +#include "color.h" +#include "distort.h" +#include "image.h" +#include "morphology.h" +#include "overlays.h" +#include "paint.h" +#include "protrusions.h" +#include "shadows.h" +#include "textures.h" +#include "transparency.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + + +void +Filter::filters_all ( ) +{ + // Here come the filters which are coded in C++ in order to present a parameters dialog + + /* Experimental custom predefined filters */ + + // Bevels + DiffuseLight::init(); + MatteJelly::init(); + SpecularLight::init(); + + // Blurs + Blur::init(); + CleanEdges::init(); + CrossBlur::init(); + Feather::init(); + ImageBlur::init(); + + // Bumps + Bump::init(); + WaxBump::init(); + + // Color + Brilliance::init(); + ChannelPaint::init(); + ColorBlindness::init(); + ColorShift::init(); + Colorize::init(); + ComponentTransfer::init(); + Duochrome::init(); + ExtractChannel::init(); + FadeToBW::init(); + Greyscale::init(); + Invert::init(); + Lighting::init(); + LightnessContrast::init(); + NudgeRGB::init(); + NudgeCMY::init(); + Quadritone::init(); + SimpleBlend::init(); + Solarize::init(); + Tritone::init(); + + // Distort + FeltFeather::init(); + Roughen::init(); + + // Image effect + EdgeDetect::init(); + + // Image paint and draw + Chromolitho::init(); + CrossEngraving::init(); + Drawing::init(); + Electrize::init(); + NeonDraw::init(); + PointEngraving::init(); + Posterize::init(); + PosterizeBasic::init(); + + // Morphology + Crosssmooth::init(); + Outline::init(); + + // Overlays + NoiseFill::init(); + + // Protrusions + Snow::init(); + + // Shadows and glows + ColorizableDropShadow::init(); + + // Textures + InkBlot::init(); + + // Fill and transparency + Blend::init(); + ChannelTransparency::init(); + LightEraser::init(); + Opacity::init(); + Silhouette::init(); + + // Here come the rest of the filters that are read from SVG files in share/filters and + // .config/Inkscape/filters + /* This should always be last, don't put stuff below this + * line. */ + Filter::filters_all_files(); + + return; +} + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/internal/filter/filter-file.cpp b/src/extension/internal/filter/filter-file.cpp new file mode 100644 index 0000000..afa979f --- /dev/null +++ b/src/extension/internal/filter/filter-file.cpp @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2008 Authors: + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "filter.h" + +#include "io/sys.h" +#include "io/resource.h" +#include "io/stream/inkscapestream.h" + +/* Directory includes */ +#include "path-prefix.h" +#include "inkscape.h" + +/* Extension */ +#include "extension/extension.h" +#include "extension/system.h" + +/* System includes */ +#include <glibmm/i18n.h> +#include <glibmm/fileutils.h> + +using namespace Inkscape::IO::Resource; + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +void +filters_load_file (Glib::ustring filename, gchar * menuname) +{ + Inkscape::XML::Document *doc = sp_repr_read_file(filename.c_str(), INKSCAPE_EXTENSION_URI); + if (doc == nullptr) { + g_warning("File (%s) is not parseable as XML. Ignored.", filename.c_str()); + return; + } + + Inkscape::XML::Node * root = doc->root(); + if (strcmp(root->name(), "svg:svg")) { + Inkscape::GC::release(doc); + g_warning("File (%s) is not SVG. Ignored.", filename.c_str()); + return; + } + + for (Inkscape::XML::Node * child = root->firstChild(); + child != nullptr; child = child->next()) { + if (!strcmp(child->name(), "svg:defs")) { + for (Inkscape::XML::Node * defs = child->firstChild(); + defs != nullptr; defs = defs->next()) { + if (!strcmp(defs->name(), "svg:filter")) { + Filter::filters_load_node(defs, menuname); + } // oh! a filter + } //defs + } // is defs + } // children of root + + Inkscape::GC::release(doc); + return; +} + +void Filter::filters_all_files() +{ + for(auto &filename: get_filenames(USER, FILTERS, {".svg"})) { + filters_load_file(filename, _("Personal")); + } + for(auto &filename: get_filenames(SYSTEM, FILTERS, {".svg"})) { + filters_load_file(filename, _("Bundled")); + } +} + + +#include "extension/internal/clear-n_.h" + +class mywriter : public Inkscape::IO::BasicWriter { + Glib::ustring _str; +public: + void close() override; + void flush() override; + void put (char ch) override; + gchar const * c_str () { return _str.c_str(); } +}; + +void mywriter::close () { return; } +void mywriter::flush () { return; } +void mywriter::put (char ch) { _str += ch; } + + +void +Filter::filters_load_node (Inkscape::XML::Node *node, gchar * menuname) +{ + gchar const * label = node->attribute("inkscape:label"); + gchar const * menu = node->attribute("inkscape:menu"); + gchar const * menu_tooltip = node->attribute("inkscape:menu-tooltip"); + gchar const * id = node->attribute("id"); + + if (label == nullptr) { + label = id; + } + + gchar * xml_str = g_strdup_printf( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>%s</name>\n" + "<id>org.inkscape.effect.filter.%s</id>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"%s\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>%s</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", label, id, menu? menu : menuname, menu_tooltip? menu_tooltip : label); + + // FIXME: Bad hack: since we pull out a single filter node out of SVG file and + // serialize it, it loses the namespace declarations from the root, so we must provide + // one right here for our inkscape attributes + node->setAttribute("xmlns:inkscape", SP_INKSCAPE_NS_URI); + + mywriter writer; + sp_repr_write_stream(node, writer, 0, FALSE, g_quark_from_static_string("svg"), 0, 0); + + Inkscape::Extension::build_from_mem(xml_str, new Filter(g_strdup(writer.c_str()))); + g_free(xml_str); + return; +} + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + diff --git a/src/extension/internal/filter/filter.cpp b/src/extension/internal/filter/filter.cpp new file mode 100644 index 0000000..45f2d0a --- /dev/null +++ b/src/extension/internal/filter/filter.cpp @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "desktop.h" +#include "selection.h" +#include "extension/extension.h" +#include "extension/effect.h" +#include "extension/system.h" +#include "xml/repr.h" +#include "xml/simple-node.h" +#include "xml/attribute-record.h" +#include "object/sp-defs.h" + +#include "filter.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +Filter::Filter() : + Inkscape::Extension::Implementation::Implementation(), + _filter(nullptr) { + return; +} + +Filter::Filter(gchar const * filter) : + Inkscape::Extension::Implementation::Implementation(), + _filter(filter) { + return; +} + +Filter::~Filter () { + if (_filter != nullptr) { + _filter = nullptr; + } + + return; +} + +bool Filter::load(Inkscape::Extension::Extension * /*module*/) +{ + return true; +} + +Inkscape::Extension::Implementation::ImplementationDocumentCache *Filter::newDocCache(Inkscape::Extension::Extension * /*ext*/, + Inkscape::UI::View::View * /*doc*/) +{ + return nullptr; +} + +gchar const *Filter::get_filter_text(Inkscape::Extension::Extension * /*ext*/) +{ + return _filter; +} + +Inkscape::XML::Document * +Filter::get_filter (Inkscape::Extension::Extension * ext) { + gchar const * filter = get_filter_text(ext); + return sp_repr_read_mem(filter, strlen(filter), nullptr); +} + +void +Filter::merge_filters( Inkscape::XML::Node * to, Inkscape::XML::Node * from, + Inkscape::XML::Document * doc, + gchar const * srcGraphic, gchar const * srcGraphicAlpha) +{ + if (from == nullptr) return; + + // copy attributes + for ( Inkscape::Util::List<Inkscape::XML::AttributeRecord const> iter = from->attributeList() ; + iter ; ++iter ) { + gchar const * attr = g_quark_to_string(iter->key); + //printf("Attribute List: %s\n", attr); + if (!strcmp(attr, "id")) continue; // nope, don't copy that one! + to->setAttribute(attr, from->attribute(attr)); + + if (!strcmp(attr, "in") || !strcmp(attr, "in2") || !strcmp(attr, "in3")) { + if (srcGraphic != nullptr && !strcmp(from->attribute(attr), "SourceGraphic")) { + to->setAttribute(attr, srcGraphic); + } + + if (srcGraphicAlpha != nullptr && !strcmp(from->attribute(attr), "SourceAlpha")) { + to->setAttribute(attr, srcGraphicAlpha); + } + } + } + + // for each child call recursively + for (Inkscape::XML::Node * from_child = from->firstChild(); + from_child != nullptr ; from_child = from_child->next()) { + Glib::ustring name = "svg:"; + name += from_child->name(); + + Inkscape::XML::Node * to_child = doc->createElement(name.c_str()); + to->appendChild(to_child); + merge_filters(to_child, from_child, doc, srcGraphic, srcGraphicAlpha); + + if (from_child == from->firstChild() && !strcmp("filter", from->name()) && srcGraphic != nullptr && to_child->attribute("in") == nullptr) { + to_child->setAttribute("in", srcGraphic); + } + Inkscape::GC::release(to_child); + } +} + +#define FILTER_SRC_GRAPHIC "fbSourceGraphic" +#define FILTER_SRC_GRAPHIC_ALPHA "fbSourceGraphicAlpha" + +void Filter::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, + Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + Inkscape::XML::Document *filterdoc = get_filter(module); + if (filterdoc == nullptr) { + return; // could not parse the XML source of the filter; typically parser will stderr a warning + } + + //printf("Calling filter effect\n"); + Inkscape::Selection * selection = ((SPDesktop *)document)->selection; + + // TODO need to properly refcount the items, at least + std::vector<SPItem*> items(selection->items().begin(), selection->items().end()); + + Inkscape::XML::Document * xmldoc = document->doc()->getReprDoc(); + Inkscape::XML::Node * defsrepr = document->doc()->getDefs()->getRepr(); + + for(auto spitem : items) { + Inkscape::XML::Node *node = spitem->getRepr(); + + SPCSSAttr * css = sp_repr_css_attr(node, "style"); + gchar const * filter = sp_repr_css_property(css, "filter", nullptr); + + if (filter == nullptr) { + + Inkscape::XML::Node * newfilterroot = xmldoc->createElement("svg:filter"); + merge_filters(newfilterroot, filterdoc->root(), xmldoc); + defsrepr->appendChild(newfilterroot); + document->doc()->resources_changed_signals[g_quark_from_string("filter")].emit(); + + Glib::ustring url = "url(#"; url += newfilterroot->attribute("id"); url += ")"; + + + Inkscape::GC::release(newfilterroot); + + sp_repr_css_set_property(css, "filter", url.c_str()); + sp_repr_css_set(node, css, "style"); + } else { + if (strncmp(filter, "url(#", strlen("url(#")) || filter[strlen(filter) - 1] != ')') { + // This is not url(#id) -- we can't handle it + continue; + } + + gchar * lfilter = g_strndup(filter + 5, strlen(filter) - 6); + Inkscape::XML::Node * filternode = nullptr; + for (Inkscape::XML::Node * child = defsrepr->firstChild(); child != nullptr; child = child->next()) { + if (!strcmp(lfilter, child->attribute("id"))) { + filternode = child; + break; + } + } + g_free(lfilter); + + // no filter + if (filternode == nullptr) { + g_warning("no assigned filter found!"); + continue; + } + + if (filternode->lastChild() == nullptr) { + // empty filter, we insert + merge_filters(filternode, filterdoc->root(), xmldoc); + } else { + // existing filter, we merge + filternode->lastChild()->setAttribute("result", FILTER_SRC_GRAPHIC); + Inkscape::XML::Node * alpha = xmldoc->createElement("svg:feColorMatrix"); + alpha->setAttribute("result", FILTER_SRC_GRAPHIC_ALPHA); + alpha->setAttribute("in", FILTER_SRC_GRAPHIC); // not required, but we're being explicit + alpha->setAttribute("values", "0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"); + + filternode->appendChild(alpha); + + merge_filters(filternode, filterdoc->root(), xmldoc, FILTER_SRC_GRAPHIC, FILTER_SRC_GRAPHIC_ALPHA); + + Inkscape::GC::release(alpha); + } + } + } + + return; +} + +#include "extension/internal/clear-n_.h" + +void +Filter::filter_init (gchar const * id, gchar const * name, gchar const * submenu, gchar const * tip, gchar const * filter) +{ + gchar * xml_str = g_strdup_printf( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>%s</name>\n" + "<id>org.inkscape.effect.filter.%s</id>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\" />\n" + "<submenu name=\"%s\"/>\n" + "</effects-menu>\n" + "<menu-tip>%s</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", name, id, submenu, tip); + Inkscape::Extension::build_from_mem(xml_str, new Filter(filter)); + g_free(xml_str); + return; +} + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/filter/filter.h b/src/extension/internal/filter/filter.h new file mode 100644 index 0000000..cb3ed36 --- /dev/null +++ b/src/extension/internal/filter/filter.h @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INKSCAPE_EXTENSION_INTERNAL_FILTER_FILTER_H +#define INKSCAPE_EXTENSION_INTERNAL_FILTER_FILTER_H + +/* + * Copyright (C) 2008 Authors: + * Ted Gould <ted@gould.cx> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/i18n.h> + +#include "extension/implementation/implementation.h" + +namespace Inkscape { + +namespace XML { + struct Document; +} + +namespace Extension { + +class Effect; +class Extension; + +namespace Internal { +namespace Filter { + +class Filter : public Inkscape::Extension::Implementation::Implementation { +protected: + gchar const * _filter; + virtual gchar const * get_filter_text (Inkscape::Extension::Extension * ext); + +private: + Inkscape::XML::Document * get_filter (Inkscape::Extension::Extension * ext); + void merge_filters (Inkscape::XML::Node * to, Inkscape::XML::Node * from, Inkscape::XML::Document * doc, gchar const * srcGraphic = nullptr, gchar const * srcGraphicAlpha = nullptr); + +public: + Filter(); + Filter(gchar const * filter); + ~Filter() override; + + bool load(Inkscape::Extension::Extension *module) override; + Inkscape::Extension::Implementation::ImplementationDocumentCache * newDocCache (Inkscape::Extension::Extension * ext, Inkscape::UI::View::View * doc) override; + void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + + static void filter_init(gchar const * id, gchar const * name, gchar const * submenu, gchar const * tip, gchar const * filter); + static void filters_all(); + + /* File loader related */ + static void filters_all_files(); + static void filters_load_node(Inkscape::XML::Node *node, gchar * menuname); + +}; + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +#endif // INKSCAPE_EXTENSION_INTERNAL_FILTER_FILTER_H diff --git a/src/extension/internal/filter/image.h b/src/extension/internal/filter/image.h new file mode 100644 index 0000000..2a0e334 --- /dev/null +++ b/src/extension/internal/filter/image.h @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_IMAGE_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_IMAGE_H__ +/* Change the 'IMAGE' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Image filters + * Edge detect + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Edge detect filter. + + Detect color edges in object. + + Filter's parameters: + * Detection type (enum, default Full) -> convolve (kernelMatrix) + * Level (0.01->10., default 1.) -> convolve (divisor) + * Inverted (boolean, default false) -> convolve (bias) +*/ +class EdgeDetect : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + EdgeDetect ( ) : Filter() { }; + ~EdgeDetect ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Edge Detect") "</name>\n" + "<id>org.inkscape.effect.filter.EdgeDetect</id>\n" + "<param name=\"type\" gui-text=\"" N_("Detect:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value=\"all\">" N_("All") "</option>\n" + "<option value=\"vertical\">" N_("Vertical lines") "</option>\n" + "<option value=\"horizontal\">" N_("Horizontal lines") "</option>\n" + "</param>\n" + "<param name=\"level\" gui-text=\"" N_("Level") "\" type=\"float\" appearance=\"full\" min=\"0.1\" max=\"100.0\">1.0</param>\n" + "<param name=\"inverted\" gui-text=\"" N_("Invert colors") "\" type=\"bool\" >false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Effects") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Detect color edges in object") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new EdgeDetect()); + }; + +}; + +gchar const * +EdgeDetect::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream matrix; + std::ostringstream inverted; + std::ostringstream level; + + const gchar *type = ext->get_param_optiongroup("type"); + + level << 1 / ext->get_param_float("level"); + + if ((g_ascii_strcasecmp("vertical", type) == 0)) { + matrix << "0 0 0 1 -2 1 0 0 0"; + } else if ((g_ascii_strcasecmp("horizontal", type) == 0)) { + matrix << "0 1 0 0 -2 0 0 1 0"; + } else { + matrix << "0 1 0 1 -4 1 0 1 0"; + } + + if (ext->get_param_bool("inverted")) { + inverted << "1"; + } else { + inverted << "0"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Edge Detect\">\n" + "<feConvolveMatrix in=\"SourceGraphic\" kernelMatrix=\"%s\" order=\"3 3\" bias=\"%s\" divisor=\"%s\" targetX=\"1\" targetY=\"1\" preserveAlpha=\"true\" result=\"convolve\" />\n" + "</filter>\n", matrix.str().c_str(), inverted.str().c_str(), level.str().c_str()); + + return _filter; +}; + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'IMAGE' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_IMAGE_H__ */ diff --git a/src/extension/internal/filter/morphology.h b/src/extension/internal/filter/morphology.h new file mode 100644 index 0000000..9c29153 --- /dev/null +++ b/src/extension/internal/filter/morphology.h @@ -0,0 +1,326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_MORPHOLOGY_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_MORPHOLOGY_H__ +/* Change the 'MORPHOLOGY' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Morphology filters + * Cross-smooth + * Outline + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Cross-smooth filter. + + Smooth the outside of shapes and pictures. + + Filter's parameters: + * Type (enum, default "Smooth edges") -> + Inner = composite1 (operator="in") + Outer = composite1 (operator="over") + Open = composite1 (operator="XOR") + * Width (0.01->30., default 10.) -> blur (stdDeviation) + * Level (0.2->2., default 1.) -> composite2 (k2) + * Dilatation (1.->100., default 10.) -> colormatrix1 (last-1 value) + * Erosion (1.->100., default 1.) -> colormatrix1 (last value) + * Antialiasing (0.01->1., default 1) -> blur2 (stdDeviation) + * Blur content (boolean, default false) -> blend (true: in="colormatrix2", false: in="SourceGraphic") +*/ + +class Crosssmooth : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Crosssmooth ( ) : Filter() { }; + ~Crosssmooth ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Cross-smooth") "</name>\n" + "<id>org.inkscape.effect.filter.crosssmooth</id>\n" + "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"in\">" N_("Inner") "</option>\n" + "<option value=\"over\">" N_("Outer") "</option>\n" + "<option value=\"xor\">" N_("Open") "</option>\n" + "</param>\n" + "<param name=\"width\" gui-text=\"" N_("Width") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.\">10</param>\n" + "<param name=\"level\" gui-text=\"" N_("Level") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.2\" max=\"2\">1</param>\n" + "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">10</param>\n" + "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">1</param>\n" + "<param name=\"antialias\" gui-text=\"" N_("Antialiasing") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"1\">1</param>\n" + "<param name=\"content\" gui-text=\"" N_("Blur content") "\" type=\"bool\" >false</param>\n" + + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Morphology") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Smooth edges and angles of shapes") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Crosssmooth()); + }; + +}; + +gchar const * +Crosssmooth::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream type; + std::ostringstream width; + std::ostringstream level; + std::ostringstream dilat; + std::ostringstream erosion; + std::ostringstream antialias; + std::ostringstream content; + + type << ext->get_param_optiongroup("type"); + width << ext->get_param_float("width"); + level << ext->get_param_float("level"); + dilat << ext->get_param_float("dilat"); + erosion << (1 - ext->get_param_float("erosion")); + antialias << ext->get_param_float("antialias"); + + if (ext->get_param_bool("content")) { + content << "colormatrix2"; + } else { + content << "SourceGraphic"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Cross-smooth\">\n" + "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feComposite in=\"blur1\" in2=\"blur1\" operator=\"%s\" result=\"composite1\" />\n" + "<feComposite in=\"composite1\" in2=\"composite1\" k2=\"%s\" operator=\"arithmetic\" result=\"composite2\" />\n" + "<feColorMatrix in=\"composite2\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix1\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur2\" />\n" + "<feColorMatrix in=\"blur2\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 5 -1 \" result=\"colormatrix2\" />\n" + "<feBlend in=\"%s\" in2=\"colormatrix2\" stdDeviation=\"17\" mode=\"normal\" result=\"blend\" />\n" + "<feComposite in=\"blend\" in2=\"colormatrix2\" operator=\"in\" result=\"composite3\" />\n" + "</filter>\n", width.str().c_str(), type.str().c_str(), level.str().c_str(), + dilat.str().c_str(), erosion.str().c_str(), antialias.str().c_str(), + content.str().c_str()); + + return _filter; +}; /* Cross-smooth filter */ + +/** + \brief Custom predefined Outline filter. + + Adds a colorizable outline + + Filter's parameters: + * Fill image (boolean, default false) -> true: composite2 (in="SourceGraphic"), false: composite2 (in="blur2") + * Hide image (boolean, default false) -> true: composite4 (in="composite3"), false: composite4 (in="SourceGraphic") + * Stroke type (enum, default over) -> composite2 (operator) + * Stroke position (enum, default inside) + * inside -> composite1 (operator="out", in="SourceGraphic", in2="blur1") + * outside -> composite1 (operator="out", in="blur1", in2="SourceGraphic") + * overlayed -> composite1 (operator="xor", in="blur1", in2="SourceGraphic") + * Width 1 (0.01->20., default 4) -> blur1 (stdDeviation) + * Dilatation 1 (1.->100., default 100) -> colormatrix1 (n-1th value) + * Erosion 1 (0.->100., default 1) -> colormatrix1 (nth value 0->-100) + * Width 2 (0.01->20., default 0.5) -> blur2 (stdDeviation) + * Dilatation 2 (1.->100., default 50) -> colormatrix2 (n-1th value) + * Erosion 2 (0.->100., default 5) -> colormatrix2 (nth value 0->-100) + * Antialiasing (0.01->1., default 1) -> blur3 (stdDeviation) + * Color (guint, default 0,0,0,255) -> flood (flood-color, flood-opacity) + * Fill opacity (0.->1., default 1) -> composite5 (k2) + * Stroke opacity (0.->1., default 1) -> composite5 (k3) + +*/ + +class Outline : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Outline ( ) : Filter() { }; + ~Outline ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Outline") "</name>\n" + "<id>org.inkscape.effect.filter.Outline</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"fill\" gui-text=\"" N_("Fill image") "\" type=\"bool\" >false</param>\n" + "<param name=\"outline\" gui-text=\"" N_("Hide image") "\" type=\"bool\" >false</param>\n" + "<param name=\"type\" gui-text=\"" N_("Composite type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"over\">" N_("Over") "</option>\n" + "<option value=\"in\">" N_("In") "</option>\n" + "<option value=\"out\">" N_("Out") "</option>\n" + "<option value=\"atop\">" N_("Atop") "</option>\n" + "<option value=\"xor\">" N_("XOR") "</option>\n" + "</param>\n" + "<param name=\"position\" gui-text=\"" N_("Position:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"inside\">" N_("Inside") "</option>\n" + "<option value=\"outside\">" N_("Outside") "</option>\n" + "<option value=\"overlayed\">" N_("Overlayed") "</option>\n" + "</param>\n" + "<param name=\"width1\" gui-text=\"" N_("Width 1") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">4</param>\n" + "<param name=\"dilat1\" gui-text=\"" N_("Dilatation 1") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">100</param>\n" + "<param name=\"erosion1\" gui-text=\"" N_("Erosion 1") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">1</param>\n" + "<param name=\"width2\" gui-text=\"" N_("Width 2") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">0.5</param>\n" + "<param name=\"dilat2\" gui-text=\"" N_("Dilatation 2") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">50</param>\n" + "<param name=\"erosion2\" gui-text=\"" N_("Erosion 2") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">5</param>\n" + "<param name=\"antialias\" gui-text=\"" N_("Antialiasing") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"1\">1</param>\n" + "<param name=\"smooth\" gui-text=\"" N_("Smooth") "\" type=\"bool\" >false</param>\n" + "</page>\n" + "<page name=\"co11tab\" gui-text=\"Color\">\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">255</param>\n" + "<param name=\"fopacity\" gui-text=\"" N_("Fill opacity:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n" + "<param name=\"sopacity\" gui-text=\"" N_("Stroke opacity:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Morphology") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Adds a colorizable outline") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Outline()); + }; + +}; + +gchar const * +Outline::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream width1; + std::ostringstream dilat1; + std::ostringstream erosion1; + std::ostringstream width2; + std::ostringstream dilat2; + std::ostringstream erosion2; + std::ostringstream antialias; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream a; + std::ostringstream fopacity; + std::ostringstream sopacity; + std::ostringstream smooth; + + std::ostringstream c1in; + std::ostringstream c1in2; + std::ostringstream c1op; + std::ostringstream c2in; + std::ostringstream c2op; + std::ostringstream c4in; + + + width1 << ext->get_param_float("width1"); + dilat1 << ext->get_param_float("dilat1"); + erosion1 << (- ext->get_param_float("erosion1")); + width2 << ext->get_param_float("width2"); + dilat2 << ext->get_param_float("dilat2"); + erosion2 << (- ext->get_param_float("erosion2")); + antialias << ext->get_param_float("antialias"); + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + + fopacity << ext->get_param_float("fopacity"); + sopacity << ext->get_param_float("sopacity"); + + const gchar *position = ext->get_param_optiongroup("position"); + if((g_ascii_strcasecmp("inside", position) == 0)) { + // Indide + c1in << "SourceGraphic"; + c1in2 << "blur1"; + c1op << "out"; + } else if((g_ascii_strcasecmp("outside", position) == 0)) { + // Outside + c1in << "blur1"; + c1in2 << "SourceGraphic"; + c1op << "out"; + } else { + // Overlayed + c1in << "blur1"; + c1in2 << "SourceGraphic"; + c1op << "xor"; + } + + if (ext->get_param_bool("fill")) { + c2in << "SourceGraphic"; + } else { + c2in << "blur2"; + } + + c2op << ext->get_param_optiongroup("type"); + + if (ext->get_param_bool("outline")) { + c4in << "composite3"; + } else { + c4in << "SourceGraphic"; + } + + if (ext->get_param_bool("smooth")) { + smooth << "1 0"; + } else { + smooth << "5 -1"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" height=\"1.4\" width=\"1.4\" y=\"-0.2\" x=\"-0.2\" inkscape:label=\"Outline\">\n" + "<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feComposite in=\"%s\" in2=\"%s\" operator=\"%s\" result=\"composite1\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix1\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur2\" />\n" + "<feComposite in=\"%s\" in2=\"blur2\" operator=\"%s\" result=\"composite2\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix2\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur3\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s \" result=\"colormatrix3\" />\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feComposite in=\"flood\" in2=\"colormatrix3\" k2=\"1\" operator=\"in\" result=\"composite3\" />\n" + "<feComposite in=\"%s\" in2=\"colormatrix3\" operator=\"out\" result=\"composite4\" />\n" + "<feComposite in=\"composite4\" in2=\"composite3\" k2=\"%s\" k3=\"%s\" operator=\"arithmetic\" result=\"composite5\" />\n" + "</filter>\n", width1.str().c_str(), c1in.str().c_str(), c1in2.str().c_str(), c1op.str().c_str(), + dilat1.str().c_str(), erosion1.str().c_str(), + width2.str().c_str(), c2in.str().c_str(), c2op.str().c_str(), + dilat2.str().c_str(), erosion2.str().c_str(), antialias.str().c_str(), smooth.str().c_str(), + a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + c4in.str().c_str(), fopacity.str().c_str(), sopacity.str().c_str() ); + + return _filter; +}; /* Outline filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'MORPHOLOGY' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_MORPHOLOGY_H__ */ diff --git a/src/extension/internal/filter/overlays.h b/src/extension/internal/filter/overlays.h new file mode 100644 index 0000000..fd88460 --- /dev/null +++ b/src/extension/internal/filter/overlays.h @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_OVERLAYS_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_OVERLAYS_H__ +/* Change the 'OVERLAYS' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Overlays filters + * Noise fill + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Noise fill filter. + + Basic noise fill and transparency texture + + Filter's parameters: + * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type) + * Horizontal frequency (*1000) (0.01->10000., default 20) -> turbulence (baseFrequency [/1000]) + * Vertical frequency (*1000) (0.01->10000., default 40) -> turbulence (baseFrequency [/1000]) + * Complexity (1->5, default 5) -> turbulence (numOctaves) + * Variation (1->360, default 1) -> turbulence (seed) + * Dilatation (1.->50., default 3) -> color (n-1th value) + * Erosion (0.->50., default 1) -> color (nth value 0->-50) + * Color (guint, default 148,115,39,255) -> flood (flood-color, flood-opacity) + * Inverted (boolean, default false) -> composite1 (operator, true="in", false="out") +*/ + +class NoiseFill : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + NoiseFill ( ) : Filter() { }; + ~NoiseFill ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Noise Fill") "</name>\n" + "<id>org.inkscape.effect.filter.NoiseFill</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"" N_("Options") "\">\n" + "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n" + "<option value=\"turbulence\">" N_("Turbulence") "</option>\n" + "</param>\n" + "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10000.00\">20</param>\n" + "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10000.00\">40</param>\n" + "<param name=\"complexity\" gui-text=\"" N_("Complexity:") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">5</param>\n" + "<param name=\"variation\" gui-text=\"" N_("Variation:") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"360\">0</param>\n" + "<param name=\"dilat\" gui-text=\"" N_("Dilatation:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"50\">3</param>\n" + "<param name=\"erosion\" gui-text=\"" N_("Erosion:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"50\">1</param>\n" + "<param name=\"inverted\" gui-text=\"" N_("Inverted") "\" type=\"bool\" >false</param>\n" + "</page>\n" + "<page name=\"co11tab\" gui-text=\"" N_("Noise color") "\">\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">354957823</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Overlays") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Basic noise fill and transparency texture") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new NoiseFill()); + }; + +}; + +gchar const * +NoiseFill::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream type; + std::ostringstream hfreq; + std::ostringstream vfreq; + std::ostringstream complexity; + std::ostringstream variation; + std::ostringstream dilat; + std::ostringstream erosion; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream a; + std::ostringstream inverted; + + type << ext->get_param_optiongroup("type"); + hfreq << (ext->get_param_float("hfreq") / 1000); + vfreq << (ext->get_param_float("vfreq") / 1000); + complexity << ext->get_param_int("complexity"); + variation << ext->get_param_int("variation"); + dilat << ext->get_param_float("dilat"); + erosion << (- ext->get_param_float("erosion")); + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + if (ext->get_param_bool("inverted")) + inverted << "out"; + else + inverted << "in"; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Noise Fill\">\n" + "<feTurbulence type=\"%s\" baseFrequency=\"%s %s\" numOctaves=\"%s\" seed=\"%s\" result=\"turbulence\"/>\n" + "<feComposite in=\"SourceGraphic\" in2=\"turbulence\" operator=\"%s\" result=\"composite1\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color\" />\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feMerge result=\"merge\">\n" + "<feMergeNode in=\"flood\" />\n" + "<feMergeNode in=\"color\" />\n" + "</feMerge>\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n" + "</filter>\n", type.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), complexity.str().c_str(), variation.str().c_str(), inverted.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str()); + + return _filter; +}; /* NoiseFill filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'OVERLAYS' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_OVERLAYS_H__ */ diff --git a/src/extension/internal/filter/paint.h b/src/extension/internal/filter/paint.h new file mode 100644 index 0000000..162d150 --- /dev/null +++ b/src/extension/internal/filter/paint.h @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PAINT_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PAINT_H__ +/* Change the 'PAINT' above to be your file name */ + +/* + * Copyright (C) 2012 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Image paint and draw filters + * Chromolitho + * Cross engraving + * Drawing + * Electrize + * Neon draw + * Point engraving + * Posterize + * Posterize basic + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Chromolitho filter. + + Chromo effect with customizable edge drawing and graininess + + Filter's parameters: + * Drawing (boolean, default checked) -> Checked = blend1 (in="convolve1"), unchecked = blend1 (in="composite1") + * Transparent (boolean, default unchecked) -> Checked = colormatrix5 (in="colormatrix4"), Unchecked = colormatrix5 (in="component1") + * Invert (boolean, default false) -> component1 (tableValues) [adds a trailing 0] + * Dented (boolean, default false) -> component1 (tableValues) [adds intermediate 0s] + * Lightness (0.->10., default 0.) -> composite1 (k1) + * Saturation (0.->1., default 1.) -> colormatrix3 (values) + * Noise reduction (1->1000, default 20) -> convolve (kernelMatrix, central value -1001->-2000, default -1020) + * Drawing blend (enum, default Normal) -> blend1 (mode) + * Smoothness (0.01->10, default 1) -> blur1 (stdDeviation) + * Grain (boolean, default unchecked) -> Checked = blend2 (in="colormatrix2"), Unchecked = blend2 (in="blur1") + * Grain x frequency (0.->1000, default 1000) -> turbulence1 (baseFrequency, first value) + * Grain y frequency (0.->1000, default 1000) -> turbulence1 (baseFrequency, second value) + * Grain complexity (1->5, default 1) -> turbulence1 (numOctaves) + * Grain variation (0->1000, default 0) -> turbulence1 (seed) + * Grain expansion (1.->50., default 1.) -> colormatrix1 (n-1 value) + * Grain erosion (0.->40., default 0.) -> colormatrix1 (nth value) [inverted] + * Grain color (boolean, default true) -> colormatrix2 (values) + * Grain blend (enum, default Normal) -> blend2 (mode) +*/ +class Chromolitho : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Chromolitho ( ) : Filter() { }; + ~Chromolitho ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Chromolitho") "</name>\n" + "<id>org.inkscape.effect.filter.Chromolitho</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<param name=\"drawing\" gui-text=\"" N_("Drawing mode") "\" type=\"bool\" >true</param>\n" + "<param name=\"dblend\" gui-text=\"" N_("Drawing blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"darken\">Darken</option>\n" + "<option value=\"normal\">Normal</option>\n" + "<option value=\"multiply\">Multiply</option>\n" + "<option value=\"screen\">Screen</option>\n" + "<option value=\"lighten\">Lighten</option>\n" + "</param>\n" + "<param name=\"transparent\" gui-text=\"" N_("Transparent") "\" type=\"bool\" >false</param>\n" + "<param name=\"dented\" gui-text=\"" N_("Dented") "\" type=\"bool\" >false</param>\n" + "<param name=\"inverted\" gui-text=\"" N_("Inverted") "\" type=\"bool\" >false</param>\n" + "<param name=\"light\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10\">0</param>\n" + "<param name=\"saturation\" gui-text=\"" N_("Saturation") "\" type=\"float\" precision=\"2\" appearance=\"full\" min=\"0\" max=\"1\">1</param>\n" + "<param name=\"noise\" gui-text=\"" N_("Noise reduction") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"1000\">10</param>\n" + "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">1</param>\n" + "</page>\n" + "<page name=\"graintab\" gui-text=\"" N_("Grain") "\">\n" + "<param name=\"grain\" gui-text=\"" N_("Grain mode") "\" type=\"bool\" >true</param>\n" + "<param name=\"grainxf\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1000\">1000</param>\n" + "<param name=\"grainyf\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1000\">1000</param>\n" + "<param name=\"grainc\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">1</param>\n" + "<param name=\"grainv\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"1000\">0</param>\n" + "<param name=\"grainexp\" gui-text=\"" N_("Expansion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"50\">1</param>\n" + "<param name=\"grainero\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"40\">0</param>\n" + "<param name=\"graincol\" gui-text=\"" N_("Color") "\" type=\"bool\" >true</param>\n" + "<param name=\"gblend\" gui-text=\"" N_("Grain blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">Normal</option>\n" + "<option value=\"multiply\">Multiply</option>\n" + "<option value=\"screen\">Screen</option>\n" + "<option value=\"lighten\">Lighten</option>\n" + "<option value=\"darken\">Darken</option>\n" + "</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Chromo effect with customizable edge drawing and graininess") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Chromolitho()); + }; +}; + +gchar const * +Chromolitho::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream b1in; + std::ostringstream b2in; + std::ostringstream col3in; + std::ostringstream transf; + std::ostringstream light; + std::ostringstream saturation; + std::ostringstream noise; + std::ostringstream dblend; + std::ostringstream smooth; + std::ostringstream grainxf; + std::ostringstream grainyf; + std::ostringstream grainc; + std::ostringstream grainv; + std::ostringstream gblend; + std::ostringstream grainexp; + std::ostringstream grainero; + std::ostringstream graincol; + + if (ext->get_param_bool("drawing")) + b1in << "convolve1"; + else + b1in << "composite1"; + + if (ext->get_param_bool("transparent")) + col3in << "colormatrix4"; + else + col3in << "component1"; + light << ext->get_param_float("light"); + saturation << ext->get_param_float("saturation"); + noise << (-1000 - ext->get_param_int("noise")); + dblend << ext->get_param_optiongroup("dblend"); + smooth << ext->get_param_float("smooth"); + + if (ext->get_param_bool("dented")) { + transf << "0 1 0 1"; + } else { + transf << "0 1 1"; + } + if (ext->get_param_bool("inverted")) + transf << " 0"; + + if (ext->get_param_bool("grain")) + b2in << "colormatrix2"; + else + b2in << "blur1"; + grainxf << (ext->get_param_float("grainxf") / 1000); + grainyf << (ext->get_param_float("grainyf") / 1000); + grainc << ext->get_param_int("grainc"); + grainv << ext->get_param_int("grainv"); + gblend << ext->get_param_optiongroup("gblend"); + grainexp << ext->get_param_float("grainexp"); + grainero << (-ext->get_param_float("grainero")); + if (ext->get_param_bool("graincol")) + graincol << "1"; + else + graincol << "0"; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Chromolitho\">\n" + "<feComposite in=\"SourceGraphic\" in2=\"SourceGraphic\" operator=\"arithmetic\" k1=\"%s\" k2=\"1\" result=\"composite1\" />\n" + "<feConvolveMatrix in=\"composite1\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0 \" order=\"3 3\" result=\"convolve1\" />\n" + "<feBlend in=\"%s\" in2=\"composite1\" mode=\"%s\" result=\"blend1\" />\n" + "<feGaussianBlur in=\"blend1\" stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feTurbulence baseFrequency=\"%s %s\" numOctaves=\"%s\" seed=\"%s\" type=\"fractalNoise\" result=\"turbulence1\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix1\" />\n" + "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"colormatrix2\" />\n" + "<feBlend in=\"%s\" in2=\"blur1\" mode=\"%s\" result=\"blend2\" />\n" + "<feColorMatrix in=\"blend2\" type=\"saturate\" values=\"%s\" result=\"colormatrix3\" />\n" + "<feComponentTransfer in=\"colormatrix3\" result=\"component1\">\n" + "<feFuncR type=\"discrete\" tableValues=\"%s\" />\n" + "<feFuncG type=\"discrete\" tableValues=\"%s\" />\n" + "<feFuncB type=\"discrete\" tableValues=\"%s\" />\n" + "</feComponentTransfer>\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"colormatrix4\" />\n" + "<feColorMatrix in=\"%s\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 15 0 \" result=\"colormatrix5\" />\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n" + "</filter>\n", light.str().c_str(), noise.str().c_str(), b1in.str().c_str(), dblend.str().c_str(), smooth.str().c_str(), grainxf.str().c_str(), grainyf.str().c_str(), grainc.str().c_str(), grainv.str().c_str(), grainexp.str().c_str(), grainero.str().c_str(), graincol.str().c_str(), b2in.str().c_str(), gblend.str().c_str(), saturation.str().c_str(), transf.str().c_str(), transf.str().c_str(), transf.str().c_str(), col3in.str().c_str()); + + return _filter; +}; /* Chromolitho filter */ + +/** + \brief Custom predefined Cross engraving filter. + + Convert image to an engraving made of vertical and horizontal lines + + Filter's parameters: + * Clean-up (1->500, default 30) -> convolve1 (kernelMatrix, central value -1001->-1500, default -1030) + * Dilatation (1.->50., default 1) -> color2 (n-1th value) + * Erosion (0.->50., default 0) -> color2 (nth value 0->-50) + * Strength (0.->10., default 0.5) -> composite2 (k2) + * Length (0.5->20, default 4) -> blur1 (stdDeviation x), blur2 (stdDeviation y) + * Transparent (boolean, default false) -> composite 4 (in, true->composite3, false->blend) +*/ +class CrossEngraving : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + CrossEngraving ( ) : Filter() { }; + ~CrossEngraving ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Cross Engraving") "</name>\n" + "<id>org.inkscape.effect.filter.CrossEngraving</id>\n" + "<param name=\"clean\" gui-text=\"" N_("Clean-up") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"500\">30</param>\n" + "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" min=\"1\" max=\"50\">1</param>\n" + "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"50\">0</param>\n" + "<param name=\"strength\" gui-text=\"" N_("Strength") "\" type=\"float\" appearance=\"full\" min=\"0.1\" max=\"10\">0.5</param>\n" + "<param name=\"length\" gui-text=\"" N_("Length") "\" type=\"float\" appearance=\"full\" min=\"0.5\" max=\"20\">4</param>\n" + "<param name=\"trans\" gui-text=\"" N_("Transparent") "\" type=\"bool\" >false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Convert image to an engraving made of vertical and horizontal lines") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new CrossEngraving()); + }; +}; + +gchar const * +CrossEngraving::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream clean; + std::ostringstream dilat; + std::ostringstream erosion; + std::ostringstream strength; + std::ostringstream length; + std::ostringstream trans; + + clean << (-1000 - ext->get_param_int("clean")); + dilat << ext->get_param_float("dilat"); + erosion << (- ext->get_param_float("erosion")); + strength << ext->get_param_float("strength"); + length << ext->get_param_float("length"); + if (ext->get_param_bool("trans")) + trans << "composite3"; + else + trans << "blend"; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Cross Engraving\">\n" + "<feConvolveMatrix in=\"SourceGraphic\" targetY=\"1\" targetX=\"1\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0 \" order=\"3 3\" result=\"convolve\" />\n" + "<feComposite in=\"convolve\" in2=\"convolve\" k1=\"1\" k2=\"1\" operator=\"arithmetic\" result=\"composite1\" />\n" + "<feColorMatrix in=\"composite1\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"color1\" />\n" + "<feColorMatrix in=\"color1\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color2\" />\n" + "<feComposite in=\"color2\" in2=\"color2\" operator=\"arithmetic\" k2=\"%s\" result=\"composite2\" />\n" + "<feGaussianBlur in=\"composite2\" stdDeviation=\"%s 0.01\" result=\"blur1\" />\n" + "<feGaussianBlur in=\"composite2\" stdDeviation=\"0.01 %s\" result=\"blur2\" />\n" + "<feComposite in=\"blur2\" in2=\"blur1\" k3=\"1\" k2=\"1\" operator=\"arithmetic\" result=\"composite3\" />\n" + "<feFlood flood-color=\"rgb(255,255,255)\" flood-opacity=\"1\" result=\"flood\" />\n" + "<feBlend in=\"flood\" in2=\"composite3\" mode=\"multiply\" result=\"blend\" />\n" + "<feComposite in=\"%s\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite4\" />\n" + "</filter>\n", clean.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), strength.str().c_str(), length.str().c_str(), length.str().c_str(), trans.str().c_str()); + + return _filter; +}; /* CrossEngraving filter */ + +/** + \brief Custom predefined Drawing filter. + + Convert images to duochrome drawings. + + Filter's parameters: + * Simplification strength (0.01->20, default 0.6) -> blur1 (stdDeviation) + * Clean-up (1->500, default 10) -> convolve1 (kernelMatrix, central value -1001->-1500, default -1010) + * Erase (0.->6., default 0) -> composite1 (k4) + * Smoothness strength (0.01->20, default 0.6) -> blur2 (stdDeviation) + * Dilatation (1.->50., default 6) -> color2 (n-1th value) + * Erosion (0.->50., default 2) -> color2 (nth value 0->-50) + * translucent (boolean, default false) -> composite 8 (in, true->merge1, false->color5) + + * Blur strength (0.01->20., default 1.) -> blur3 (stdDeviation) + * Blur dilatation (1.->50., default 6) -> color4 (n-1th value) + * Blur erosion (0.->50., default 2) -> color4 (nth value 0->-50) + + * Stroke color (guint, default 64,64,64,255) -> flood2 (flood-color), composite3 (k2) + * Image on stroke (boolean, default false) -> composite2 (in="flood2" true-> in="SourceGraphic") + * Offset (-100->100, default 0) -> offset (val) + + * Fill color (guint, default 200,200,200,255) -> flood3 (flood-opacity), composite5 (k2) + * Image on fill (boolean, default false) -> composite4 (in="flood3" true-> in="SourceGraphic") + +*/ + +class Drawing : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Drawing ( ) : Filter() { }; + ~Drawing ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Drawing") "</name>\n" + "<id>org.inkscape.effect.filter.Drawing</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"Options\">\n" + "<label appearance=\"header\">" N_("Simplify") "</label>\n" + "<param name=\"simply\" gui-text=\"" N_("Strength") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">0.6</param>\n" + "<param name=\"clean\" gui-text=\"" N_("Clean-up") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"500\">10</param>\n" + "<param name=\"erase\" gui-text=\"" N_("Erase") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"60\">0</param>\n" + "<param name=\"translucent\" gui-text=\"" N_("Translucent") "\" indent=\"1\" type=\"bool\" >false</param>\n" + "<label appearance=\"header\">" N_("Smoothness") "</label>\n" + "<param name=\"smooth\" gui-text=\"" N_("Strength") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">0.6</param>\n" + "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"50\">6</param>\n" + "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"50\">2</param>\n" + "<label appearance=\"header\">" N_("Melt") "</label>\n" + "<param name=\"blur\" gui-text=\"" N_("Level") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">1</param>\n" + "<param name=\"bdilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"50\">6</param>\n" + "<param name=\"berosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"50\">2</param>\n" + "</page>\n" + "<page name=\"co11tab\" gui-text=\"Fill color\">\n" + "<param name=\"fcolor\" gui-text=\"" N_("Fill color") "\" type=\"color\">-1515870721</param>\n" + "<param name=\"iof\" gui-text=\"" N_("Image on fill") "\" type=\"bool\" >false</param>\n" + "</page>\n" + "<page name=\"co12tab\" gui-text=\"Stroke color\">\n" + "<param name=\"scolor\" gui-text=\"" N_("Stroke color") "\" type=\"color\">589505535</param>\n" + "<param name=\"ios\" gui-text=\"" N_("Image on stroke") "\" type=\"bool\" >false</param>\n" + "<param name=\"offset\" gui-text=\"" N_("Offset") "\" type=\"int\" appearance=\"full\" min=\"-100\" max=\"100\">0</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Convert images to duochrome drawings") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Drawing()); + }; +}; + +gchar const * +Drawing::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream simply; + std::ostringstream clean; + std::ostringstream erase; + std::ostringstream smooth; + std::ostringstream dilat; + std::ostringstream erosion; + std::ostringstream translucent; + std::ostringstream offset; + std::ostringstream blur; + std::ostringstream bdilat; + std::ostringstream berosion; + std::ostringstream strokea; + std::ostringstream stroker; + std::ostringstream strokeg; + std::ostringstream strokeb; + std::ostringstream ios; + std::ostringstream filla; + std::ostringstream fillr; + std::ostringstream fillg; + std::ostringstream fillb; + std::ostringstream iof; + + simply << ext->get_param_float("simply"); + clean << (-1000 - ext->get_param_int("clean")); + erase << (ext->get_param_float("erase") / 10); + smooth << ext->get_param_float("smooth"); + dilat << ext->get_param_float("dilat"); + erosion << (- ext->get_param_float("erosion")); + if (ext->get_param_bool("translucent")) + translucent << "merge1"; + else + translucent << "color5"; + offset << ext->get_param_int("offset"); + + blur << ext->get_param_float("blur"); + bdilat << ext->get_param_float("bdilat"); + berosion << (- ext->get_param_float("berosion")); + + guint32 fcolor = ext->get_param_color("fcolor"); + fillr << ((fcolor >> 24) & 0xff); + fillg << ((fcolor >> 16) & 0xff); + fillb << ((fcolor >> 8) & 0xff); + filla << (fcolor & 0xff) / 255.0F; + if (ext->get_param_bool("iof")) + iof << "SourceGraphic"; + else + iof << "flood3"; + + guint32 scolor = ext->get_param_color("scolor"); + stroker << ((scolor >> 24) & 0xff); + strokeg << ((scolor >> 16) & 0xff); + strokeb << ((scolor >> 8) & 0xff); + strokea << (scolor & 0xff) / 255.0F; + if (ext->get_param_bool("ios")) + ios << "SourceGraphic"; + else + ios << "flood2"; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Drawing\">\n" + "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feConvolveMatrix in=\"blur1\" targetX=\"1\" targetY=\"1\" order=\"3 3\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0 \" result=\"convolve1\" />\n" + "<feComposite in=\"convolve1\" in2=\"convolve1\" k1=\"1\" k2=\"1\" k4=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n" + "<feColorMatrix in=\"composite1\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"color1\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur2\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color2\" />\n" + "<feFlood flood-color=\"rgb(255,255,255)\" result=\"flood1\" />\n" + "<feBlend in2=\"color2\" mode=\"multiply\" result=\"blend1\" />\n" + "<feComponentTransfer in=\"blend1\" result=\"component1\">\n" + "<feFuncR type=\"discrete\" tableValues=\"0 1 1 1\" />\n" + "<feFuncG type=\"discrete\" tableValues=\"0 1 1 1\" />\n" + "<feFuncB type=\"discrete\" tableValues=\"0 1 1 1\" />\n" + "</feComponentTransfer>\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur3\" />\n" + "<feColorMatrix in=\"blur3\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"color3\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color4\" />\n" + "<feFlood flood-color=\"rgb(%s,%s,%s)\" result=\"flood2\" />\n" + "<feComposite in=\"%s\" in2=\"color4\" operator=\"in\" result=\"composite2\" />\n" + "<feComposite in=\"composite2\" in2=\"composite2\" operator=\"arithmetic\" k2=\"%s\" result=\"composite3\" />\n" + "<feOffset dx=\"%s\" dy=\"%s\" result=\"offset1\" />\n" + "<feFlood in=\"color4\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood3\" />\n" + "<feComposite in=\"%s\" in2=\"color4\" operator=\"out\" result=\"composite4\" />\n" + "<feComposite in=\"composite4\" in2=\"composite4\" operator=\"arithmetic\" k2=\"%s\" result=\"composite5\" />\n" + "<feMerge result=\"merge1\">\n" + "<feMergeNode in=\"composite5\" />\n" + "<feMergeNode in=\"offset1\" />\n" + "</feMerge>\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1.3 0 \" result=\"color5\" flood-opacity=\"0.56\" />\n" + "<feComposite in=\"%s\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite8\" />\n" + "</filter>\n", simply.str().c_str(), clean.str().c_str(), erase.str().c_str(), smooth.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), blur.str().c_str(), bdilat.str().c_str(), berosion.str().c_str(), stroker.str().c_str(), strokeg.str().c_str(), strokeb.str().c_str(), ios.str().c_str(), strokea.str().c_str(), offset.str().c_str(), offset.str().c_str(), fillr.str().c_str(), fillg.str().c_str(), fillb.str().c_str(), iof.str().c_str(), filla.str().c_str(), translucent.str().c_str()); + + return _filter; +}; /* Drawing filter */ + + +/** + \brief Custom predefined Electrize filter. + + Electro solarization effects. + + Filter's parameters: + * Simplify (0.01->10., default 2.) -> blur (stdDeviation) + * Effect type (enum: table or discrete, default "table") -> component (type) + * Level (0->10, default 3) -> component (tableValues) + * Inverted (boolean, default false) -> component (tableValues) +*/ +class Electrize : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Electrize ( ) : Filter() { }; + ~Electrize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Electrize") "</name>\n" + "<id>org.inkscape.effect.filter.Electrize</id>\n" + "<param name=\"blur\" gui-text=\"" N_("Simplify") "\" type=\"float\" appearance=\"full\" min=\"0.01\" max=\"10.0\">2.0</param>\n" + "<param name=\"type\" gui-text=\"" N_("Effect type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"table\">" N_("Table") "</option>\n" + "<option value=\"discrete\">" N_("Discrete") "</option>\n" + "</param>\n" + "<param name=\"levels\" gui-text=\"" N_("Levels") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"10\">3</param>\n" + "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Electro solarization effects") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Electrize()); + }; +}; + +gchar const * +Electrize::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream blur; + std::ostringstream type; + std::ostringstream values; + + blur << ext->get_param_float("blur"); + type << ext->get_param_optiongroup("type"); + + // TransfertComponent table values are calculated based on the effect level and inverted parameters. + int val = 0; + int levels = ext->get_param_int("levels") + 1; + if (ext->get_param_bool("invert")) { + val = 1; + } + values << val; + for ( int step = 1 ; step <= levels ; step++ ) { + if (val == 1) { + val = 0; + } + else { + val = 1; + } + values << " " << val; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Electrize\">\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n" + "<feComponentTransfer in=\"blur\" result=\"component\" >\n" + "<feFuncR type=\"%s\" tableValues=\"%s\" />\n" + "<feFuncG type=\"%s\" tableValues=\"%s\" />\n" + "<feFuncB type=\"%s\" tableValues=\"%s\" />\n" + "</feComponentTransfer>\n" + "</filter>\n", blur.str().c_str(), type.str().c_str(), values.str().c_str(), type.str().c_str(), values.str().c_str(), type.str().c_str(), values.str().c_str()); + + return _filter; +}; /* Electrize filter */ + +/** + \brief Custom predefined Neon draw filter. + + Posterize and draw smooth lines around color shapes + + Filter's parameters: + * Lines type (enum, default smooth) -> + smooth = component2 (type="table"), composite1 (in2="blur2") + hard = component2 (type="discrete"), composite1 (in2="component1") + * Simplify (0.01->20., default 3) -> blur1 (stdDeviation) + * Line width (0.01->20., default 3) -> blur2 (stdDeviation) + * Lightness (0.->10., default 1) -> composite1 (k2) + * Blend (enum [normal, multiply, screen], default normal) -> blend (mode) + * Dark mode (boolean, default false) -> composite2 (true: in2="component2") +*/ +class NeonDraw : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + NeonDraw ( ) : Filter() { }; + ~NeonDraw ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Neon Draw") "</name>\n" + "<id>org.inkscape.effect.filter.NeonDraw</id>\n" + "<param name=\"type\" gui-text=\"" N_("Line type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"table\">" N_("Smoothed") "</option>\n" + "<option value=\"discrete\">" N_("Contrasted") "</option>\n" + "</param>\n" + "<param name=\"simply\" gui-text=\"" N_("Simplify") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">3</param>\n" + "<param name=\"width\" gui-text=\"" N_("Line width") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">3</param>\n" + "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"10.00\">1</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">Normal</option>\n" + "<option value=\"multiply\">Multiply</option>\n" + "<option value=\"screen\">Screen</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Posterize and draw smooth lines around color shapes") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new NeonDraw()); + }; +}; + +gchar const * +NeonDraw::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream blend; + std::ostringstream simply; + std::ostringstream width; + std::ostringstream lightness; + std::ostringstream type; + + type << ext->get_param_optiongroup("type"); + blend << ext->get_param_optiongroup("blend"); + simply << ext->get_param_float("simply"); + width << ext->get_param_float("width"); + lightness << ext->get_param_float("lightness"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Neon Draw\">\n" + "<feBlend mode=\"%s\" result=\"blend\" />\n" + "<feGaussianBlur in=\"blend\" stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 50 0\" result=\"color1\" />\n" + "<feComponentTransfer result=\"component1\">\n" + "<feFuncR type=\"discrete\" tableValues=\"0 0.3 0.3 0.3 0.3 0.6 0.6 0.6 0.6 1 1\" />\n" + "<feFuncG type=\"discrete\" tableValues=\"0 0.3 0.3 0.3 0.3 0.6 0.6 0.6 0.6 1 1\" />\n" + "<feFuncB type=\"discrete\" tableValues=\"0 0.3 0.3 0.3 0.3 0.6 0.6 0.6 0.6 1 1\" />\n" + "</feComponentTransfer>\n" + "<feGaussianBlur in=\"component1\" stdDeviation=\"%s\" result=\"blur2\" />\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 50 0\" result=\"color2\" />\n" + "<feComponentTransfer in=\"color2\" result=\"component2\">\n" + "<feFuncR type=\"%s\" tableValues=\"0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1\" />\n" + "<feFuncG type=\"%s\" tableValues=\"0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1\" />\n" + "<feFuncB type=\"%s\" tableValues=\"0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1\" />\n" + "</feComponentTransfer>\n" + "<feComposite in=\"component2\" in2=\"blur2\" k3=\"%s\" operator=\"arithmetic\" k2=\"1\" result=\"composite1\" />\n" + "<feComposite in=\"composite1\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n" + "</filter>\n", blend.str().c_str(), simply.str().c_str(), width.str().c_str(), type.str().c_str(), type.str().c_str(), type.str().c_str(), lightness.str().c_str()); + + return _filter; +}; /* NeonDraw filter */ + +/** + \brief Custom predefined Point engraving filter. + + Convert image to a transparent point engraving + + Filter's parameters: + + * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type) + * Horizontal frequency (0.001->1., default 1) -> turbulence (baseFrequency [/100]) + * Vertical frequency (0.001->1., default 1) -> turbulence (baseFrequency [/100]) + * Complexity (1->5, default 3) -> turbulence (numOctaves) + * Variation (0->1000, default 0) -> turbulence (seed) + * Noise reduction (-1000->-1500, default -1045) -> convolve (kernelMatrix, central value) + * Noise blend (enum, all blend options, default normal) -> blend (mode) + * Lightness (0.->10., default 2.5) -> composite1 (k1) + * Grain lightness (0.->10., default 1.3) -> composite1 (k2) + * Erase (0.00->1., default 0) -> composite1 (k4) + * Blur (0.01->2., default 0.5) -> blur (stdDeviation) + + * Drawing color (guint32, default rgb(255,255,255)) -> flood1 (flood-color, flood-opacity) + + * Background color (guint32, default rgb(99,89,46)) -> flood2 (flood-color, flood-opacity) +*/ + +class PointEngraving : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + PointEngraving ( ) : Filter() { }; + ~PointEngraving ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Point Engraving") "</name>\n" + "<id>org.inkscape.effect.filter.PointEngraving</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"" N_("Options") "\">\n" + "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n" + "<option value=\"turbulence\">" N_("Turbulence") "</option>\n" + "</param>\n" + "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"100.00\">100</param>\n" + "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"100.00\">100</param>\n" + "<param name=\"complexity\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">1</param>\n" + "<param name=\"variation\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"100\">0</param>\n" + "<param name=\"reduction\" gui-text=\"" N_("Noise reduction") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"500\">45</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Noise blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "</param>\n" + "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10\">2.5</param>\n" + "<param name=\"grain\" gui-text=\"" N_("Grain lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10\">1.3</param>\n" + "<param name=\"erase\" gui-text=\"" N_("Erase") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">0</param>\n" + "<param name=\"blur\" gui-text=\"" N_("Blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"2\">0.5</param>\n" + "</page>\n" + "<page name=\"fcolortab\" gui-text=\"" N_("Fill color") "\">\n" + "<param name=\"fcolor\" gui-text=\"" N_("Color") "\" type=\"color\">-1</param>\n" + "<param name=\"iof\" gui-text=\"" N_("Image on fill") "\" type=\"bool\" >false</param>\n" + "</page>\n" + "<page name=\"pcolortab\" gui-text=\"" N_("Points color") "\">\n" + "<param name=\"pcolor\" gui-text=\"" N_("Color") "\" type=\"color\">1666789119</param>\n" + "<param name=\"iop\" gui-text=\"" N_("Image on points") "\" type=\"bool\" >false</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Convert image to a transparent point engraving") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new PointEngraving()); + }; + +}; + +gchar const * +PointEngraving::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream type; + std::ostringstream hfreq; + std::ostringstream vfreq; + std::ostringstream complexity; + std::ostringstream variation; + std::ostringstream reduction; + std::ostringstream blend; + std::ostringstream lightness; + std::ostringstream grain; + std::ostringstream erase; + std::ostringstream blur; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream a; + std::ostringstream br; + std::ostringstream bg; + std::ostringstream bb; + std::ostringstream ba; + std::ostringstream iof; + std::ostringstream iop; + + type << ext->get_param_optiongroup("type"); + hfreq << ext->get_param_float("hfreq") / 100; + vfreq << ext->get_param_float("vfreq") / 100; + complexity << ext->get_param_int("complexity"); + variation << ext->get_param_int("variation"); + reduction << (-1000 - ext->get_param_int("reduction")); + blend << ext->get_param_optiongroup("blend"); + lightness << ext->get_param_float("lightness"); + grain << ext->get_param_float("grain"); + erase << ext->get_param_float("erase"); + blur << ext->get_param_float("blur"); + + guint32 fcolor = ext->get_param_color("fcolor"); + r << ((fcolor >> 24) & 0xff); + g << ((fcolor >> 16) & 0xff); + b << ((fcolor >> 8) & 0xff); + a << (fcolor & 0xff) / 255.0F; + + guint32 pcolor = ext->get_param_color("pcolor"); + br << ((pcolor >> 24) & 0xff); + bg << ((pcolor >> 16) & 0xff); + bb << ((pcolor >> 8) & 0xff); + ba << (pcolor & 0xff) / 255.0F; + + if (ext->get_param_bool("iof")) + iof << "SourceGraphic"; + else + iof << "flood2"; + + if (ext->get_param_bool("iop")) + iop << "SourceGraphic"; + else + iop << "flood1"; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Point Engraving\" style=\"color-interpolation-filters:sRGB;\">\n" + "<feConvolveMatrix in=\"SourceGraphic\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0\" order=\"3 3\" result=\"convolve\" />\n" + "<feBlend in=\"convolve\" in2=\"SourceGraphic\" mode=\"%s\" result=\"blend\" />\n" + "<feTurbulence type=\"%s\" baseFrequency=\"%s %s\" numOctaves=\"%s\" seed=\"%s\" result=\"turbulence\" />\n" + "<feColorMatrix in=\"blend\" type=\"luminanceToAlpha\" result=\"colormatrix1\" />\n" + "<feComposite in=\"turbulence\" in2=\"colormatrix1\" k1=\"%s\" k2=\"%s\" k4=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n" + "<feColorMatrix in=\"composite1\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 10 -9 \" result=\"colormatrix2\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n" + "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood1\" />\n" + "<feComposite in=\"%s\" in2=\"blur\" operator=\"out\" result=\"composite2\" />\n" + "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood2\" />\n" + "<feComposite in=\"%s\" in2=\"blur\" operator=\"in\" result=\"composite3\" />\n" + "<feComposite in=\"composite3\" in2=\"composite2\" k2=\"%s\" k3=\"%s\" operator=\"arithmetic\" result=\"composite4\" />\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite5\" />\n" + "</filter>\n", reduction.str().c_str(), blend.str().c_str(), + type.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), complexity.str().c_str(), variation.str().c_str(), + lightness.str().c_str(), grain.str().c_str(), erase.str().c_str(), blur.str().c_str(), + br.str().c_str(), bg.str().c_str(), bb.str().c_str(), ba.str().c_str(), iop.str().c_str(), + r.str().c_str(), g.str().c_str(), b.str().c_str(), a.str().c_str(), iof.str().c_str(), + a.str().c_str(), ba.str().c_str() ); + + return _filter; +}; /* Point engraving filter */ + +/** + \brief Custom predefined Poster paint filter. + + Poster and painting effects. + + Filter's parameters: + * Effect type (enum, default "Normal") -> + Normal = feComponentTransfer + Dented = Normal + intermediate values + * Transfer type (enum, default "discrete") -> component (type) + * Levels (0->15, default 5) -> component (tableValues) + * Blend mode (enum, default "Lighten") -> blend (mode) + * Primary simplify (0.01->100., default 4.) -> blur1 (stdDeviation) + * Secondary simplify (0.01->100., default 0.5) -> blur2 (stdDeviation) + * Pre-saturation (0.->1., default 1.) -> color1 (values) + * Post-saturation (0.->1., default 1.) -> color2 (values) + * Simulate antialiasing (boolean, default false) -> blur3 (true->stdDeviation=0.5, false->stdDeviation=0.01) +*/ +class Posterize : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Posterize ( ) : Filter() { }; + ~Posterize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Poster Paint") "</name>\n" + "<id>org.inkscape.effect.filter.Posterize</id>\n" + "<param name=\"type\" gui-text=\"" N_("Effect type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"normal\">Normal</option>\n" + "<option value=\"dented\">Dented</option>\n" + "</param>\n" + "<param name=\"table\" gui-text=\"" N_("Transfer type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"discrete\">" N_("Poster") "</option>\n" + "<option value=\"table\">" N_("Painting") "</option>\n" + "</param>\n" + "<param name=\"levels\" gui-text=\"" N_("Levels") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"15\">5</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"lighten\">Lighten</option>\n" + "<option value=\"normal\">Normal</option>\n" + "<option value=\"darken\">Darken</option>\n" + "<option value=\"multiply\">Multiply</option>\n" + "<option value=\"screen\">Screen</option>\n" + "</param>\n" + "<param name=\"blur1\" gui-text=\"" N_("Simplify (primary)") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">4.0</param>\n" + "<param name=\"blur2\" gui-text=\"" N_("Simplify (secondary)") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">0.5</param>\n" + "<param name=\"presaturation\" gui-text=\"" N_("Pre-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"1.00\">1.00</param>\n" + "<param name=\"postsaturation\" gui-text=\"" N_("Post-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"1.00\">1.00</param>\n" + "<param name=\"antialiasing\" gui-text=\"" N_("Simulate antialiasing") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Poster and painting effects") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Posterize()); + }; +}; + +gchar const * +Posterize::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream table; + std::ostringstream blendmode; + std::ostringstream blur1; + std::ostringstream blur2; + std::ostringstream presat; + std::ostringstream postsat; + std::ostringstream transf; + std::ostringstream antialias; + + table << ext->get_param_optiongroup("table"); + blendmode << ext->get_param_optiongroup("blend"); + blur1 << ext->get_param_float("blur1"); + blur2 << ext->get_param_float("blur2"); + presat << ext->get_param_float("presaturation"); + postsat << ext->get_param_float("postsaturation"); + + // TransfertComponent table values are calculated based on the poster type. + transf << "0"; + int levels = ext->get_param_int("levels") + 1; + const gchar *effecttype = ext->get_param_optiongroup("type"); + if (levels == 1) { + if ((g_ascii_strcasecmp("dented", effecttype) == 0)) { + transf << " 1 0 1"; + } else { + transf << " 1"; + } + } else { + for ( int step = 1 ; step <= levels ; step++ ) { + float val = (float) step / levels; + transf << " " << val; + if ((g_ascii_strcasecmp("dented", effecttype) == 0)) { + transf << " " << (val - ((float) 1 / (3 * levels))) << " " << (val + ((float) 1 / (2 * levels))); + } + } + } + transf << " 1"; + + if (ext->get_param_bool("antialiasing")) + antialias << "0.5"; + else + antialias << "0.01"; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Poster Paint\">\n" + "<feComposite operator=\"arithmetic\" k2=\"1\" result=\"composite1\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur2\" />\n" + "<feBlend in2=\"blur1\" mode=\"%s\" result=\"blend\"/>\n" + "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"color1\" />\n" + "<feComponentTransfer result=\"component\">\n" + "<feFuncR type=\"%s\" tableValues=\"%s\" />\n" + "<feFuncG type=\"%s\" tableValues=\"%s\" />\n" + "<feFuncB type=\"%s\" tableValues=\"%s\" />\n" + "</feComponentTransfer>\n" + "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"color2\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur3\" />\n" + "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite3\" />\n" + "</filter>\n", blur1.str().c_str(), blur2.str().c_str(), blendmode.str().c_str(), presat.str().c_str(), table.str().c_str(), transf.str().c_str(), table.str().c_str(), transf.str().c_str(), table.str().c_str(), transf.str().c_str(), postsat.str().c_str(), antialias.str().c_str()); + + return _filter; +}; /* Posterize filter */ + +/** + \brief Custom predefined Posterize basic filter. + + Simple posterizing effect + + Filter's parameters: + * Levels (0->20, default 5) -> component1 (tableValues) + * Blur (0.01->20., default 4.) -> blur1 (stdDeviation) +*/ +class PosterizeBasic : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + PosterizeBasic ( ) : Filter() { }; + ~PosterizeBasic ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Posterize Basic") "</name>\n" + "<id>org.inkscape.effect.filter.PosterizeBasic</id>\n" + "<param name=\"levels\" gui-text=\"" N_("Levels") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"20\">5</param>\n" + "<param name=\"blur\" gui-text=\"" N_("Simplify") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">4.0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Simple posterizing effect") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new PosterizeBasic()); + }; +}; + +gchar const * +PosterizeBasic::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream blur; + std::ostringstream transf; + + blur << ext->get_param_float("blur"); + + transf << "0"; + int levels = ext->get_param_int("levels") + 1; + for ( int step = 1 ; step <= levels ; step++ ) { + const float val = (float) step / levels; + transf << " " << val; + } + transf << " 1"; + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Posterize Basic\">\n" + "<feGaussianBlur stdDeviation=\"%s\" result=\"blur1\" />\n" + "<feComponentTransfer in=\"blur1\" result=\"component1\">\n" + "<feFuncR type=\"discrete\" tableValues=\"%s\" />\n" + "<feFuncG type=\"discrete\" tableValues=\"%s\" />\n" + "<feFuncB type=\"discrete\" tableValues=\"%s\" />\n" + "</feComponentTransfer>\n" + "<feComposite in=\"component1\" in2=\"SourceGraphic\" operator=\"in\" />\n" + "</filter>\n", blur.str().c_str(), transf.str().c_str(), transf.str().c_str(), transf.str().c_str()); + + return _filter; +}; /* PosterizeBasic filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'PAINT' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PAINT_H__ */ diff --git a/src/extension/internal/filter/protrusions.h b/src/extension/internal/filter/protrusions.h new file mode 100644 index 0000000..590608d --- /dev/null +++ b/src/extension/internal/filter/protrusions.h @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PROTRUSIONS_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PROTRUSIONS_H__ +/* Change the 'PROTRUSIONS' above to be your file name */ + +/* + * Copyright (C) 2008 Authors: + * Ted Gould <ted@gould.cx> + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Protrusion filters + * Snow + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + + +/** + \brief Custom predefined Snow filter. + + Snow has fallen on object. +*/ +class Snow : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Snow ( ) : Filter() { }; + ~Snow ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + +public: + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Snow Crest") "</name>\n" + "<id>org.inkscape.effect.filter.snow</id>\n" + "<param name=\"drift\" gui-text=\"" N_("Drift Size") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"20.0\">3.5</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"Protrusions\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Snow has fallen on object") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Snow()); + }; + +}; + +gchar const * +Snow::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream drift; + drift << ext->get_param_float("drift"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Snow\">\n" + "<feConvolveMatrix order=\"3 3\" kernelMatrix=\"1 1 1 0 0 0 -1 -1 -1\" preserveAlpha=\"false\" divisor=\"3\"/>\n" + "<feMorphology operator=\"dilate\" radius=\"1 %s\"/>\n" + "<feGaussianBlur stdDeviation=\"1.6270889487870621\" result=\"result0\"/>\n" + "<feColorMatrix values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 0\" result=\"result1\"/>\n" + "<feOffset dx=\"0\" dy=\"1\" result=\"result5\"/>\n" + "<feDiffuseLighting in=\"result0\" diffuseConstant=\"2.2613065326633168\" surfaceScale=\"1\">\n" + "<feDistantLight azimuth=\"225\" elevation=\"32\"/>\n" + "</feDiffuseLighting>\n" + "<feComposite in2=\"result1\" operator=\"in\" result=\"result2\"/>\n" + "<feColorMatrix values=\"0.4 0 0 0 0.6 0 0.4 0 0 0.6 0 0 0 0 1 0 0 0 1 0\" result=\"result4\"/>\n" + "<feComposite in2=\"result5\" in=\"result4\"/>\n" + "<feComposite in2=\"SourceGraphic\"/>\n" + "</filter>\n", drift.str().c_str()); + + return _filter; +}; /* Snow filter */ + + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'PROTRUSIONS' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PROTRUSIONS_H__ */ diff --git a/src/extension/internal/filter/shadows.h b/src/extension/internal/filter/shadows.h new file mode 100644 index 0000000..d8aa69e --- /dev/null +++ b/src/extension/internal/filter/shadows.h @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_SHADOWS_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_SHADOWS_H__ +/* Change the 'SHADOWS' above to be your file name */ + +/* + * Copyright (C) 2013 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Shadow filters + * Drop shadow + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Drop shadow filter. + + Colorizable Drop shadow. + + Filter's parameters: + * Blur radius (0.->200., default 3) -> blur (stdDeviation) + * Horizontal offset (-50.->50., default 6.0) -> offset (dx) + * Vertical offset (-50.->50., default 6.0) -> offset (dy) + * Blur type (enum, default outer) -> + outer = composite1 (operator="in"), composite2 (operator="over", in1="SourceGraphic", in2="offset") + inner = composite1 (operator="out"), composite2 (operator="atop", in1="offset", in2="SourceGraphic") + innercut = composite1 (operator="in"), composite2 (operator="out", in1="offset", in2="SourceGraphic") + outercut = composite1 (operator="out"), composite2 (operator="in", in1="SourceGraphic", in2="offset") + shadow = composite1 (operator="out"), composite2 (operator="atop", in1="offset", in2="offset") + * Color (guint, default 0,0,0,127) -> flood (flood-opacity, flood-color) + * Use object's color (boolean, default false) -> composite1 (in1, in2) +*/ +class ColorizableDropShadow : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ColorizableDropShadow ( ) : Filter() { }; + ~ColorizableDropShadow ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Drop Shadow") "</name>\n" + "<id>org.inkscape.effect.filter.ColorDropShadow</id>\n" + "<param name=\"tab\" type=\"notebook\">\n" + "<page name=\"optionstab\" gui-text=\"" N_("Options") "\">\n" + "<param name=\"blur\" gui-text=\"" N_("Blur radius (px)") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"200.0\">3.0</param>\n" + "<param name=\"xoffset\" gui-text=\"" N_("Horizontal offset (px)") "\" type=\"float\" appearance=\"full\" min=\"-50.0\" max=\"50.0\">6.0</param>\n" + "<param name=\"yoffset\" gui-text=\"" N_("Vertical offset (px)") "\" type=\"float\" appearance=\"full\" min=\"-50.0\" max=\"50.0\">6.0</param>\n" + "<param name=\"type\" gui-text=\"" N_("Shadow type:") "\" type=\"optiongroup\" appearance=\"combo\" >\n" + "<option value=\"outer\">" N_("Outer") "</option>\n" + "<option value=\"inner\">" N_("Inner") "</option>\n" + "<option value=\"outercut\">" N_("Outer cutout") "</option>\n" + "<option value=\"innercut\">" N_("Inner cutout") "</option>\n" + "<option value=\"shadow\">" N_("Shadow only") "</option>\n" + "</param>\n" + "</page>\n" + "<page name=\"coltab\" gui-text=\"" N_("Blur color") "\">\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">127</param>\n" + "<param name=\"objcolor\" gui-text=\"" N_("Use object's color") "\" type=\"bool\" >false</param>\n" + "</page>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Shadows and Glows") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Colorizable Drop shadow") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ColorizableDropShadow()); + }; + +}; + +gchar const * +ColorizableDropShadow::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream blur; + std::ostringstream a; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream x; + std::ostringstream y; + std::ostringstream comp1in1; + std::ostringstream comp1in2; + std::ostringstream comp1op; + std::ostringstream comp2in1; + std::ostringstream comp2in2; + std::ostringstream comp2op; + + const gchar *type = ext->get_param_optiongroup("type"); + guint32 color = ext->get_param_color("color"); + + blur << ext->get_param_float("blur"); + x << ext->get_param_float("xoffset"); + y << ext->get_param_float("yoffset"); + a << (color & 0xff) / 255.0F; + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + + // Select object or user-defined color + if ((g_ascii_strcasecmp("innercut", type) == 0)) { + if (ext->get_param_bool("objcolor")) { + comp2in1 << "SourceGraphic"; + comp2in2 << "offset"; + } else { + comp2in1 << "offset"; + comp2in2 << "SourceGraphic"; + } + } else { + if (ext->get_param_bool("objcolor")) { + comp1in1 << "SourceGraphic"; + comp1in2 << "flood"; + } else { + comp1in1 << "flood"; + comp1in2 << "SourceGraphic"; + } + } + + // Shadow mode + if ((g_ascii_strcasecmp("outer", type) == 0)) { + comp1op << "in"; + comp2op << "over"; + comp2in1 << "SourceGraphic"; + comp2in2 << "offset"; + } else if ((g_ascii_strcasecmp("inner", type) == 0)) { + comp1op << "out"; + comp2op << "atop"; + comp2in1 << "offset"; + comp2in2 << "SourceGraphic"; + } else if ((g_ascii_strcasecmp("outercut", type) == 0)) { + comp1op << "in"; + comp2op << "out"; + comp2in1 << "offset"; + comp2in2 << "SourceGraphic"; + } else if ((g_ascii_strcasecmp("innercut", type) == 0)){ + comp1op << "out"; + comp1in1 << "flood"; + comp1in2 << "SourceGraphic"; + comp2op << "in"; + } else { //shadow + comp1op << "in"; + comp2op << "atop"; + comp2in1 << "offset"; + comp2in2 << "offset"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Drop Shadow\">\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feComposite in=\"%s\" in2=\"%s\" operator=\"%s\" result=\"composite1\" />\n" + "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur\" />\n" + "<feOffset dx=\"%s\" dy=\"%s\" result=\"offset\" />\n" + "<feComposite in=\"%s\" in2=\"%s\" operator=\"%s\" result=\"composite2\" />\n" + "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), + comp1in1.str().c_str(), comp1in2.str().c_str(), comp1op.str().c_str(), + blur.str().c_str(), x.str().c_str(), y.str().c_str(), + comp2in1.str().c_str(), comp2in2.str().c_str(), comp2op.str().c_str()); + + return _filter; +}; /* Drop shadow filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'SHADOWS' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_SHADOWS_H__ */ diff --git a/src/extension/internal/filter/textures.h b/src/extension/internal/filter/textures.h new file mode 100644 index 0000000..45a78a3 --- /dev/null +++ b/src/extension/internal/filter/textures.h @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TEXTURES_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TEXTURES_H__ +/* Change the 'TEXTURES' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Protrusion filters + * Ink blot + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + + +/** + \brief Custom predefined Ink Blot filter. + + Inkblot on tissue or rough paper. + + Filter's parameters: + + * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type) + * Frequency (0.001->1., default 0.04) -> turbulence (baseFrequency [/100]) + * Complexity (1->5, default 3) -> turbulence (numOctaves) + * Variation (0->100, default 0) -> turbulence (seed) + * Horizontal inlay (0.01->30., default 10) -> blur1 (stdDeviation x) + * Vertical inlay (0.01->30., default 10) -> blur1 (stdDeviation y) + * Displacement (0.->100., default 50) -> map (scale) + * Blend (0.01->30., default 5) -> blur2 (stdDeviation) + * Stroke (enum, default over) -> composite (operator) + * Arithmetic stroke options + * k1 (-10.->10., default 1.5) + * k2 (-10.->10., default -0.25) + * k3 (-10.->10., default 0.5) +*/ +class InkBlot : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + InkBlot ( ) : Filter() { }; + ~InkBlot ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + +public: + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Ink Blot") "</name>\n" + "<id>org.inkscape.effect.filter.InkBlot</id>\n" + "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"fractalNoise\">Fractal noise</option>\n" + "<option value=\"turbulence\">Turbulence</option>\n" + "</param>\n" + "<param name=\"freq\" gui-text=\"" N_("Frequency:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">4</param>\n" + "<param name=\"complexity\" gui-text=\"" N_("Complexity:") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">3</param>\n" + "<param name=\"variation\" gui-text=\"" N_("Variation:") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"100\">0</param>\n" + "<param name=\"hblur\" gui-text=\"" N_("Horizontal inlay:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">10</param>\n" + "<param name=\"vblur\" gui-text=\"" N_("Vertical inlay:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">10</param>\n" + "<param name=\"displacement\" gui-text=\"" N_("Displacement:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"100.00\">50</param>\n" + "<param name=\"blend\" gui-text=\"" N_("Blend:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">5</param>\n" + "<param name=\"stroke\" gui-text=\"" N_("Stroke:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"over\">" N_("Wide") "</option>\n" + "<option value=\"atop\">" N_("Normal") "</option>\n" + "<option value=\"in\">" N_("Narrow") "</option>\n" + "<option value=\"xor\">" N_("Overlapping") "</option>\n" + "<option value=\"out\">" N_("External") "</option>\n" + "<option value=\"arithmetic\">" N_("Custom") "</option>\n" + "</param>\n" + "<label appearance=\"header\">" N_("Custom stroke options") "</label>\n" + "<param name=\"k1\" gui-text=\"" N_("k1:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1.5</param>\n" + "<param name=\"k2\" gui-text=\"" N_("k2:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">-0.25</param>\n" + "<param name=\"k3\" gui-text=\"" N_("k3:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"Textures\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Inkblot on tissue or rough paper") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new InkBlot()); + }; + +}; + +gchar const * +InkBlot::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream type; + std::ostringstream freq; + std::ostringstream complexity; + std::ostringstream variation; + std::ostringstream hblur; + std::ostringstream vblur; + std::ostringstream displacement; + std::ostringstream blend; + std::ostringstream stroke; + std::ostringstream custom; + + type << ext->get_param_optiongroup("type"); + freq << ext->get_param_float("freq") / 100; + complexity << ext->get_param_int("complexity"); + variation << ext->get_param_int("variation"); + hblur << ext->get_param_float("hblur"); + vblur << ext->get_param_float("vblur"); + displacement << ext->get_param_float("displacement"); + blend << ext->get_param_float("blend"); + + const gchar *ope = ext->get_param_optiongroup("stroke"); + if (g_ascii_strcasecmp("arithmetic", ope) == 0) { + custom << "k1=\"" << ext->get_param_float("k1") << "\" k2=\"" << ext->get_param_float("k2") << "\" k3=\"" << ext->get_param_float("k3") << "\""; + } else { + custom << ""; + } + + stroke << ext->get_param_optiongroup("stroke"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" x=\"-0.15\" width=\"1.3\" y=\"-0.15\" height=\"1.3\" inkscape:label=\"Ink Blot\" >\n" + "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s %s\" result=\"blur1\" />\n" + "<feTurbulence type=\"%s\" baseFrequency=\"%s\" numOctaves=\"%s\" seed=\"%s\" result=\"turbulence\" />\n" + "<feDisplacementMap in=\"blur1\" in2=\"turbulence\" xChannelSelector=\"R\" yChannelSelector=\"G\" scale=\"%s\" result=\"map\" />\n" + "<feGaussianBlur in=\"map\" stdDeviation=\"%s\" result=\"blur2\" />\n" + "<feComposite in=\"blur2\" in2=\"map\" %s operator=\"%s\" result=\"composite\" />\n" + "</filter>\n", hblur.str().c_str(), vblur.str().c_str(), type.str().c_str(), + freq.str().c_str(), complexity.str().c_str(), variation.str().c_str(), + displacement.str().c_str(), blend.str().c_str(), + custom.str().c_str(), stroke.str().c_str() ); + + return _filter; + +}; /* Ink Blot filter */ + + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'TEXTURES' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TEXTURES_H__ */ diff --git a/src/extension/internal/filter/transparency.h b/src/extension/internal/filter/transparency.h new file mode 100644 index 0000000..87a4c88 --- /dev/null +++ b/src/extension/internal/filter/transparency.h @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TRANSPARENCY_H__ +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TRANSPARENCY_H__ +/* Change the 'TRANSPARENCY' above to be your file name */ + +/* + * Copyright (C) 2011 Authors: + * Ivan Louette (filters) + * Nicolas Dufour (UI) <nicoduf@yahoo.fr> + * + * Fill and transparency filters + * Blend + * Channel transparency + * Light eraser + * Opacity + * Silhouette + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* ^^^ Change the copyright to be you and your e-mail address ^^^ */ + +#include "filter.h" + +#include "extension/internal/clear-n_.h" +#include "extension/system.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { +namespace Filter { + +/** + \brief Custom predefined Blend filter. + + Blend objects with background images or with themselves + + Filter's parameters: + * Source (enum [SourceGraphic,BackgroundImage], default BackgroundImage) -> blend (in2) + * Mode (enum, all blend modes, default Multiply) -> blend (mode) +*/ + +class Blend : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Blend ( ) : Filter() { }; + ~Blend ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Blend") "</name>\n" + "<id>org.inkscape.effect.filter.Blend</id>\n" + "<param name=\"source\" gui-text=\"" N_("Source:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"BackgroundImage\">" N_("Background") "</option>\n" + "<option value=\"SourceGraphic\">" N_("Image") "</option>\n" + "</param>\n" + "<param name=\"mode\" gui-text=\"" N_("Mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n" + "<option value=\"multiply\">" N_("Multiply") "</option>\n" + "<option value=\"normal\">" N_("Normal") "</option>\n" + "<option value=\"screen\">" N_("Screen") "</option>\n" + "<option value=\"darken\">" N_("Darken") "</option>\n" + "<option value=\"lighten\">" N_("Lighten") "</option>\n" + "</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Fill and Transparency") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Blend objects with background images or with themselves") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Blend()); + }; + +}; + +gchar const * +Blend::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream source; + std::ostringstream mode; + + source << ext->get_param_optiongroup("source"); + mode << ext->get_param_optiongroup("mode"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Blend\">\n" + "<feBlend in2=\"%s\" mode=\"%s\" result=\"blend\" />\n" + "</filter>\n", source.str().c_str(), mode.str().c_str() ); + + return _filter; +}; /* Blend filter */ + +/** + \brief Custom predefined Channel transparency filter. + + Channel transparency filter. + + Filter's parameters: + * Saturation (0.->1., default 1.) -> colormatrix1 (values) + * Red (-10.->10., default -1.) -> colormatrix2 (values) + * Green (-10.->10., default 0.5) -> colormatrix2 (values) + * Blue (-10.->10., default 0.5) -> colormatrix2 (values) + * Alpha (-10.->10., default 1.) -> colormatrix2 (values) + * Flood colors (guint, default 16777215) -> flood (flood-opacity, flood-color) + * Inverted (boolean, default false) -> composite1 (operator, true='in', false='out') + + Matrix: + 1 0 0 0 0 + 0 1 0 0 0 + 0 0 1 0 0 + R G B A 0 +*/ +class ChannelTransparency : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + ChannelTransparency ( ) : Filter() { }; + ~ChannelTransparency ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Channel Transparency") "</name>\n" + "<id>org.inkscape.effect.filter.ChannelTransparency</id>\n" + "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">-1</param>\n" + "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n" + "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n" + "<param name=\"alpha\" gui-text=\"" N_("Alpha") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1</param>\n" + "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Fill and Transparency") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Replace RGB with transparency") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new ChannelTransparency()); + }; +}; + +gchar const * +ChannelTransparency::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream red; + std::ostringstream green; + std::ostringstream blue; + std::ostringstream alpha; + std::ostringstream invert; + + red << ext->get_param_float("red"); + green << ext->get_param_float("green"); + blue << ext->get_param_float("blue"); + alpha << ext->get_param_float("alpha"); + + if (!ext->get_param_bool("invert")) { + invert << "in"; + } else { + invert << "xor"; + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Channel Transparency\" style=\"color-interpolation-filters:sRGB;\" >\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s %s %s 0 \" in=\"SourceGraphic\" result=\"colormatrix\" />\n" + "<feComposite in=\"colormatrix\" in2=\"SourceGraphic\" operator=\"%s\" result=\"composite1\" />\n" + "</filter>\n", red.str().c_str(), green.str().c_str(), blue.str().c_str(), alpha.str().c_str(), + invert.str().c_str()); + + return _filter; +}; /* Channel transparency filter */ + +/** + \brief Custom predefined LightEraser filter. + + Make the lightest parts of the object progressively transparent. + + Filter's parameters: + * Expansion (0.->1000., default 50) -> colormatrix (4th value, multiplicator) + * Erosion (1.->1000., default 100) -> colormatrix (first 3 values, multiplicator) + * Global opacity (0.->1., default 1.) -> composite (k2) + * Inverted (boolean, default false) -> colormatrix (values, true: first 3 values positive, 4th negative) + +*/ +class LightEraser : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + LightEraser ( ) : Filter() { }; + ~LightEraser ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Light Eraser") "</name>\n" + "<id>org.inkscape.effect.filter.LightEraser</id>\n" + "<param name=\"expand\" gui-text=\"" N_("Expansion") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"1000\">50</param>\n" + "<param name=\"erode\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" min=\"1\" max=\"1000\">100</param>\n" + "<param name=\"opacity\" gui-text=\"" N_("Global opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">1</param>\n" + "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Fill and Transparency") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Make the lightest parts of the object progressively transparent") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new LightEraser()); + }; +}; + +gchar const * +LightEraser::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream expand; + std::ostringstream erode; + std::ostringstream opacity; + + opacity << ext->get_param_float("opacity"); + + if (ext->get_param_bool("invert")) { + expand << (ext->get_param_float("erode") * 0.2125) << " " + << (ext->get_param_float("erode") * 0.7154) << " " + << (ext->get_param_float("erode") * 0.0721); + erode << (-ext->get_param_float("expand")); + } else { + expand << (-ext->get_param_float("erode") * 0.2125) << " " + << (-ext->get_param_float("erode") * 0.7154) << " " + << (-ext->get_param_float("erode") * 0.0721); + erode << ext->get_param_float("expand"); + } + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Light Eraser\" style=\"color-interpolation-filters:sRGB;\" >\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s 0 \" result=\"colormatrix\" />\n" + "<feComposite in2=\"colormatrix\" operator=\"arithmetic\" k2=\"%s\" result=\"composite\" />\n" + "</filter>\n", expand.str().c_str(), erode.str().c_str(), opacity.str().c_str()); + + return _filter; +}; /* Light Eraser filter */ + + +/** + \brief Custom predefined Opacity filter. + + Set opacity and strength of opacity boundaries. + + Filter's parameters: + * Expansion (0.->1000., default 5) -> colormatrix (last-1th value) + * Erosion (0.->1000., default 1) -> colormatrix (last value) + * Global opacity (0.->1., default 1.) -> composite (k2) + +*/ +class Opacity : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Opacity ( ) : Filter() { }; + ~Opacity ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Opacity") "</name>\n" + "<id>org.inkscape.effect.filter.Opacity</id>\n" + "<param name=\"expand\" gui-text=\"" N_("Expansion") "\" type=\"float\" appearance=\"full\" min=\"1\" max=\"1000\">5</param>\n" + "<param name=\"erode\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"1000\">1</param>\n" + "<param name=\"opacity\" gui-text=\"" N_("Global opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">1</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Fill and Transparency") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Set opacity and strength of opacity boundaries") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Opacity()); + }; +}; + +gchar const * +Opacity::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream matrix; + std::ostringstream opacity; + + opacity << ext->get_param_float("opacity"); + + matrix << (ext->get_param_float("expand")) << " " + << (-ext->get_param_float("erode")); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Opacity\" style=\"color-interpolation-filters:sRGB;\" >\n" + "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s \" result=\"colormatrix\" />\n" + "<feComposite in2=\"colormatrix\" operator=\"arithmetic\" k2=\"%s\" result=\"composite\" />\n" + "</filter>\n", matrix.str().c_str(), opacity.str().c_str()); + + return _filter; +}; /* Opacity filter */ + +/** + \brief Custom predefined Silhouette filter. + + Repaint anything visible monochrome + + Filter's parameters: + * Blur (0.01->50., default 0.01) -> blur (stdDeviation) + * Cutout (boolean, default False) -> composite (false=in, true=out) + * Color (guint, default 0,0,0,255) -> flood (flood-color, flood-opacity) +*/ + +class Silhouette : public Inkscape::Extension::Internal::Filter::Filter { +protected: + gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override; + +public: + Silhouette ( ) : Filter() { }; + ~Silhouette ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; } + + static void init () { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Silhouette") "</name>\n" + "<id>org.inkscape.effect.filter.Silhouette</id>\n" + "<param name=\"blur\" gui-text=\"" N_("Blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"50.00\">0.01</param>\n" + "<param name=\"cutout\" gui-text=\"" N_("Cutout") "\" type=\"bool\">false</param>\n" + "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">255</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Filters") "\">\n" + "<submenu name=\"" N_("Fill and Transparency") "\"/>\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Repaint anything visible monochrome") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Silhouette()); + }; + +}; + +gchar const * +Silhouette::get_filter_text (Inkscape::Extension::Extension * ext) +{ + if (_filter != nullptr) g_free((void *)_filter); + + std::ostringstream a; + std::ostringstream r; + std::ostringstream g; + std::ostringstream b; + std::ostringstream cutout; + std::ostringstream blur; + + guint32 color = ext->get_param_color("color"); + r << ((color >> 24) & 0xff); + g << ((color >> 16) & 0xff); + b << ((color >> 8) & 0xff); + a << (color & 0xff) / 255.0F; + if (ext->get_param_bool("cutout")) + cutout << "out"; + else + cutout << "in"; + blur << ext->get_param_float("blur"); + + _filter = g_strdup_printf( + "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Silhouette\">\n" + "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n" + "<feComposite in=\"flood\" in2=\"SourceGraphic\" operator=\"%s\" result=\"composite\" />\n" + "<feGaussianBlur stdDeviation=\"%s\" />\n" + "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), cutout.str().c_str(), blur.str().c_str()); + + return _filter; +}; /* Silhouette filter */ + +}; /* namespace Filter */ +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +/* Change the 'TRANSPARENCY' below to be your file name */ +#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TRANSPARENCY_H__ */ diff --git a/src/extension/internal/gdkpixbuf-input.cpp b/src/extension/internal/gdkpixbuf-input.cpp new file mode 100644 index 0000000..08555fb --- /dev/null +++ b/src/extension/internal/gdkpixbuf-input.cpp @@ -0,0 +1,254 @@ +// 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. + */ + +#include <set> + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdkmm/pixbuf.h> +#include <gdkmm/pixbufformat.h> +#include <glib/gprintf.h> +#include <glibmm/i18n.h> + +#include "document.h" +#include "document-undo.h" +#include "gdkpixbuf-input.h" +#include "image-resolution.h" +#include "preferences.h" +#include "selection-chemistry.h" + +#include "display/cairo-utils.h" + +#include "extension/input.h" +#include "extension/system.h" + +#include "io/dir-util.h" + +#include "object/sp-image.h" +#include "object/sp-root.h" + +#include "util/units.h" + +namespace Inkscape { + +namespace Extension { +namespace Internal { + +SPDocument * +GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) +{ + // Determine whether the image should be embedded + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool ask = prefs->getBool( "/dialogs/import/ask"); + bool forcexdpi = prefs->getBool( "/dialogs/import/forcexdpi"); + Glib::ustring link = prefs->getString("/dialogs/import/link"); + Glib::ustring scale = prefs->getString("/dialogs/import/scale"); + + // If we asked about import preferences, get values and update preferences. + if (ask) { + ask = !mod->get_param_bool("do_not_ask"); + forcexdpi = (strcmp(mod->get_param_optiongroup("dpi"), "from_default") == 0); + link = mod->get_param_optiongroup("link"); + scale = mod->get_param_optiongroup("scale"); + + prefs->setBool( "/dialogs/import/ask", ask ); + prefs->setBool( "/dialogs/import/forcexdpi", forcexdpi); + prefs->setString("/dialogs/import/link", link ); + prefs->setString("/dialogs/import/scale", scale ); + } + + bool embed = (link == "embed"); + + SPDocument *doc = nullptr; + std::unique_ptr<Inkscape::Pixbuf> pb(Inkscape::Pixbuf::create_from_file(uri)); + + // TODO: the pixbuf is created again from the base64-encoded attribute in SPImage. + // Find a way to create the pixbuf only once. + + if (pb) { + doc = SPDocument::createNewDoc(nullptr, TRUE, TRUE); + bool saved = DocumentUndo::getUndoSensitive(doc); + DocumentUndo::setUndoSensitive(doc, false); // no need to undo in this temporary document + + double width = pb->width(); + double height = pb->height(); + double defaultxdpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", Inkscape::Util::Quantity::convert(1, "in", "px")); + + ImageResolution *ir = nullptr; + double xscale = 1; + double yscale = 1; + + + if (!ir && !forcexdpi) { + ir = new ImageResolution(uri); + } + if (ir && ir->ok()) { + xscale = 960.0 / round(10.*ir->x()); // round-off to 0.1 dpi + yscale = 960.0 / round(10.*ir->y()); + // prevent crash on image with too small dpi (bug 1479193) + if (ir->x() <= .05) + xscale = 960.0; + if (ir->y() <= .05) + yscale = 960.0; + } else { + xscale = 96.0 / defaultxdpi; + yscale = 96.0 / defaultxdpi; + } + + width *= xscale; + height *= yscale; + + delete ir; // deleting NULL is safe + + // Create image node + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *image_node = xml_doc->createElement("svg:image"); + sp_repr_set_svg_double(image_node, "width", width); + sp_repr_set_svg_double(image_node, "height", height); + + // Set default value as we honor "preserveAspectRatio". + image_node->setAttribute("preserveAspectRatio", "none"); + + // This is actually 'image-rendering'. + if( scale.compare( "auto" ) != 0 ) { + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "image-rendering", scale.c_str()); + sp_repr_css_set(image_node, css, "style"); + sp_repr_css_attr_unref( css ); + } + + if (embed) { + sp_embed_image(image_node, pb.get()); + } else { + // convert filename to uri + gchar* _uri = g_filename_to_uri(uri, nullptr, nullptr); + if(_uri) { + image_node->setAttribute("xlink:href", _uri); + g_free(_uri); + } else { + image_node->setAttribute("xlink:href", uri); + } + } + + // Add it to the current layer + Inkscape::XML::Node *layer_node = xml_doc->createElement("svg:g"); + layer_node->setAttribute("inkscape:groupmode", "layer"); + layer_node->setAttribute("inkscape:label", "Image"); + doc->getRoot()->appendChildRepr(layer_node); + layer_node->appendChild(image_node); + Inkscape::GC::release(image_node); + Inkscape::GC::release(layer_node); + fit_canvas_to_drawing(doc); + + // Set viewBox if it doesn't exist + if (!doc->getRoot()->viewBox_set) { + // std::cerr << "Viewbox not set, setting" << std::endl; + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit()))); + } + + // restore undo, as now this document may be shown to the user if a bitmap was opened + DocumentUndo::setUndoSensitive(doc, saved); + } else { + printf("GdkPixbuf loader failed\n"); + } + + return doc; +} + +#include "clear-n_.h" + +void +GdkpixbufInput::init() +{ + static std::vector< Gdk::PixbufFormat > formatlist = Gdk::Pixbuf::get_formats(); + for (auto i: formatlist) { + GdkPixbufFormat *pixformat = i.gobj(); + + gchar *name = gdk_pixbuf_format_get_name(pixformat); + gchar *description = gdk_pixbuf_format_get_description(pixformat); + gchar **extensions = gdk_pixbuf_format_get_extensions(pixformat); + gchar **mimetypes = gdk_pixbuf_format_get_mime_types(pixformat); + + for (int i = 0; extensions[i] != nullptr; i++) { + for (int j = 0; mimetypes[j] != nullptr; j++) { + + /* thanks but no thanks, we'll handle SVG extensions... */ + if (strcmp(extensions[i], "svg") == 0) { + continue; + } + if (strcmp(extensions[i], "svgz") == 0) { + continue; + } + if (strcmp(extensions[i], "svg.gz") == 0) { + continue; + } + gchar *caption = g_strdup_printf(_("%s bitmap image import"), name); + + gchar *xmlString = g_strdup_printf( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>%s</name>\n" + "<id>org.inkscape.input.gdkpixbuf.%s</id>\n" + + "<param name='link' type='optiongroup' gui-text='" N_("Image Import Type:") "' gui-description='" N_("Embed results in stand-alone, larger SVG files. Link references a file outside this SVG document and all files must be moved together.") "' >\n" + "<option value='embed' >" N_("Embed") "</option>\n" + "<option value='link' >" N_("Link") "</option>\n" + "</param>\n" + + "<param name='dpi' type='optiongroup' gui-text='" N_("Image DPI:") "' gui-description='" N_("Take information from file or use default bitmap import resolution as defined in the preferences.") "' >\n" + "<option value='from_file' >" N_("From file") "</option>\n" + "<option value='from_default' >" N_("Default import resolution") "</option>\n" + "</param>\n" + + "<param name='scale' type='optiongroup' gui-text='" N_("Image Rendering Mode:") "' gui-description='" N_("When an image is upscaled, apply smoothing or keep blocky (pixelated). (Will not work in all browsers.)") "' >\n" + "<option value='auto' >" N_("None (auto)") "</option>\n" + "<option value='optimizeQuality' >" N_("Smooth (optimizeQuality)") "</option>\n" + "<option value='optimizeSpeed' >" N_("Blocky (optimizeSpeed)") "</option>\n" + "</param>\n" + + "<param name=\"do_not_ask\" gui-description='" N_("Hide the dialog next time and always apply the same actions.") "' gui-text=\"" N_("Don't ask again") "\" type=\"bool\" >false</param>\n" + "<input>\n" + "<extension>.%s</extension>\n" + "<mimetype>%s</mimetype>\n" + "<filetypename>%s (*.%s)</filetypename>\n" + "<filetypetooltip>%s</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", + caption, + extensions[i], + extensions[i], + mimetypes[j], + name, + extensions[i], + description + ); + + Inkscape::Extension::build_from_mem(xmlString, new GdkpixbufInput()); + g_free(xmlString); + g_free(caption); + }} + + g_free(name); + g_free(description); + g_strfreev(mimetypes); + g_strfreev(extensions); + } +} + +} } } /* namespace Inkscape, Extension, Implementation */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/gdkpixbuf-input.h b/src/extension/internal/gdkpixbuf-input.h new file mode 100644 index 0000000..1980700 --- /dev/null +++ b/src/extension/internal/gdkpixbuf-input.h @@ -0,0 +1,40 @@ +// 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_EXTENSION_INTERNAL_GDKPIXBUF_INPUT_H +#define INKSCAPE_EXTENSION_INTERNAL_GDKPIXBUF_INPUT_H + +#include "extension/implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class GdkpixbufInput : Inkscape::Extension::Implementation::Implementation { +public: + SPDocument *open(Inkscape::Extension::Input *mod, + char const *uri) override; + static void init(); +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + + +#endif /* INKSCAPE_EXTENSION_INTERNAL_GDKPIXBUF_INPUT_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/extension/internal/gimpgrad.cpp b/src/extension/internal/gimpgrad.cpp new file mode 100644 index 0000000..0ecef60 --- /dev/null +++ b/src/extension/internal/gimpgrad.cpp @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Inkscape::Extension::Internal::GimpGrad implementation + */ + +/* + * Authors: + * Ted Gould <ted@gould.cx> + * Abhishek Sharma + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <color-rgba.h> +#include "io/sys.h" +#include "extension/system.h" +#include "svg/css-ostringstream.h" +#include "svg/svg-color.h" + +#include "gimpgrad.h" +#include "streq.h" +#include "strneq.h" +#include "document.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +/** + \brief A function to allocate anything -- just an example here + \param module Unused + \return Whether the load was successful +*/ +bool GimpGrad::load (Inkscape::Extension::Extension */*module*/) +{ + // std::cout << "Hey, I'm loading!\n" << std::endl; + return TRUE; +} + +/** + \brief A function to remove what was allocated + \param module Unused + \return None +*/ +void GimpGrad::unload (Inkscape::Extension::Extension */*module*/) +{ + // std::cout << "Nooo! I'm being unloaded!" << std::endl; + return; +} + +static void append_css_num(Glib::ustring &str, double const num) +{ + CSSOStringStream stream; + stream << num; + str += stream.str(); +} + +/** + \brief A function to turn a color into a gradient stop + \param in_color The color for the stop + \param location Where the stop is placed in the gradient + \return The text that is the stop. Full SVG containing the element. + + This function encapsulates all of the translation of the ColorRGBA + and the location into the gradient. It is really pretty simple except + that the ColorRGBA is in floats that are 0 to 1 and the SVG wants + hex values from 0 to 255 for color. Otherwise mostly this is just + turning the values into strings and returning it. +*/ +static Glib::ustring stop_svg(ColorRGBA const in_color, double const location) +{ + Glib::ustring ret("<stop stop-color=\""); + + char stop_color_css[16]; + sp_svg_write_color(stop_color_css, sizeof(stop_color_css), in_color.getIntValue()); + ret += stop_color_css; + ret += '"'; + + if (in_color[3] != 1) { + ret += " stop-opacity=\""; + append_css_num(ret, in_color[3]); + ret += '"'; + } + ret += " offset=\""; + append_css_num(ret, location); + ret += "\"/>\n"; + return ret; +} + +/** + \brief Actually open the gradient and turn it into an SPDocument + \param module The input module being used + \param filename The filename of the gradient to be opened + \return A Document with the gradient in it. + + GIMP gradients are pretty simple (atleast the newer format, this + function does not handle the old one yet). They start out with + the like "GIMP Gradient", then name it, and tell how many entries + there are. This function currently ignores the name and the number + of entries just reading until it fails. + + The other small piece of trickery here is that GIMP gradients define + a left position, right position and middle position. SVG gradients + have no middle position in them. In order to handle this case the + left and right colors are averaged in a linear manner and the middle + position is used for that color. + + That is another point, the GIMP gradients support many different types + of gradients -- linear being the most simple. This plugin assumes + that they are all linear. Most GIMP gradients are done this way, + but it is possible to encounter more complex ones -- which won't be + handled correctly. + + The one optimization that this plugin makes that if the right side + of the previous segment is the same color as the left side of the + current segment, then the second one is dropped. This is often + done in GIMP gradients and they are not necissary in SVG. + + What this function does is build up an SVG document with a single + linear gradient in it with all the stops of the colors in the GIMP + gradient that is passed in. This document is then turned into a + document using the \c sp_document_from_mem. That is then returned + to Inkscape. +*/ +SPDocument * +GimpGrad::open (Inkscape::Extension::Input */*module*/, gchar const *filename) +{ + Inkscape::IO::dump_fopen_call(filename, "I"); + FILE *gradient = Inkscape::IO::fopen_utf8name(filename, "r"); + if (gradient == nullptr) { + return nullptr; + } + + { + char tempstr[1024]; + if (fgets(tempstr, 1024, gradient) == nullptr) { + // std::cout << "Seems that the read failed" << std::endl; + goto error; + } + if (!streq(tempstr, "GIMP Gradient\n")) { + // std::cout << "This doesn't appear to be a GIMP gradient" << std::endl; + goto error; + } + + /* Name field. */ + if (fgets(tempstr, 1024, gradient) == nullptr) { + // std::cout << "Seems that the second read failed" << std::endl; + goto error; + } + if (!strneq(tempstr, "Name: ", 6)) { + goto error; + } + /* Handle very long names. (And also handle nul bytes gracefully: don't use strlen.) */ + while (memchr(tempstr, '\n', sizeof(tempstr) - 1) == nullptr) { + if (fgets(tempstr, sizeof(tempstr), gradient) == nullptr) { + goto error; + } + } + + /* n. segments */ + if (fgets(tempstr, 1024, gradient) == nullptr) { + // std::cout << "Seems that the third read failed" << std::endl; + goto error; + } + char *endptr = nullptr; + long const n_segs = strtol(tempstr, &endptr, 10); + if ((*endptr != '\n') + || n_segs < 1) { + /* SVG gradients are allowed to have zero stops (treated as `none'), but gimp 2.2 + * requires at least one segment (i.e. at least two stops) (see gimp_gradient_load in + * gimpgradient-load.c). We try to use the same error handling as gimp, so that + * .ggr files that work in one program work in both programs. */ + goto error; + } + + ColorRGBA prev_color(-1.0, -1.0, -1.0, -1.0); + Glib::ustring outsvg("<svg><defs><linearGradient>\n"); + long n_segs_found = 0; + double prev_right = 0.0; + while (fgets(tempstr, 1024, gradient) != nullptr) { + double dbls[3 + 4 + 4]; + gchar *p = tempstr; + for (double & dbl : dbls) { + gchar *end = nullptr; + double const xi = g_ascii_strtod(p, &end); + if (!end || end == p || !g_ascii_isspace(*end)) { + goto error; + } + if (xi < 0 || 1 < xi) { + goto error; + } + dbl = xi; + p = end + 1; + } + + double const left = dbls[0]; + if (left != prev_right) { + goto error; + } + double const middle = dbls[1]; + if (!(left <= middle)) { + goto error; + } + double const right = dbls[2]; + if (!(middle <= right)) { + goto error; + } + + ColorRGBA const leftcolor(dbls[3], dbls[4], dbls[5], dbls[6]); + ColorRGBA const rightcolor(dbls[7], dbls[8], dbls[9], dbls[10]); + g_assert(11 == G_N_ELEMENTS(dbls)); + + /* Interpolation enums: curve shape and colour space. */ + { + /* TODO: Currently we silently ignore type & color, assuming linear interpolation in + * sRGB space (or whatever the default in SVG is). See gimp/app/core/gimpgradient.c + * for how gimp uses these. We could use gimp functions to sample at a few points, and + * add some intermediate stops to convert to the linear/sRGB interpolation */ + int type; /* enum: linear, curved, sine, sphere increasing, sphere decreasing. */ + int color_interpolation; /* enum: rgb, hsv anticlockwise, hsv clockwise. */ + if (sscanf(p, "%8d %8d", &type, &color_interpolation) != 2) { + continue; + } + } + + if (prev_color != leftcolor) { + outsvg += stop_svg(leftcolor, left); + } + if (fabs(middle - .5 * (left + right)) > 1e-4) { + outsvg += stop_svg(leftcolor.average(rightcolor), middle); + } + outsvg += stop_svg(rightcolor, right); + + prev_color = rightcolor; + prev_right = right; + ++n_segs_found; + } + if (prev_right != 1.0) { + goto error; + } + + if (n_segs_found != n_segs) { + goto error; + } + + outsvg += "</linearGradient></defs></svg>"; + + // std::cout << "SVG Output: " << outsvg << std::endl; + + fclose(gradient); + + return SPDocument::createNewDocFromMem(outsvg.c_str(), outsvg.length(), TRUE); + } + +error: + fclose(gradient); + return nullptr; +} + +#include "clear-n_.h" + +void GimpGrad::init () +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("GIMP Gradients") "</name>\n" + "<id>org.inkscape.input.gimpgrad</id>\n" + "<input>\n" + "<extension>.ggr</extension>\n" + "<mimetype>application/x-gimp-gradient</mimetype>\n" + "<filetypename>" N_("GIMP Gradient (*.ggr)") "</filetypename>\n" + "<filetypetooltip>" N_("Gradients used in GIMP") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>\n", new GimpGrad()); + return; +} + +} } } /* namespace Internal; Extension; 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/extension/internal/gimpgrad.h b/src/extension/internal/gimpgrad.h new file mode 100644 index 0000000..8daadef --- /dev/null +++ b/src/extension/internal/gimpgrad.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +// TODO add include guard +#include <glibmm/ustring.h> + +#include "extension/implementation/implementation.h" + +namespace Inkscape { +namespace Extension { + +class Extension; + +namespace Internal { + +/** + * Implementation class of the GIMP gradient plugin. + * This mostly just creates a namespace for the GIMP gradient plugin today. + */ +class GimpGrad : public Inkscape::Extension::Implementation::Implementation +{ +public: + bool load(Inkscape::Extension::Extension *module) override; + void unload(Inkscape::Extension::Extension *module) override; + SPDocument *open(Inkscape::Extension::Input *module, gchar const *filename) override; + + static void init(); +}; + + +} // namespace Internal +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/grid.cpp b/src/extension/internal/grid.cpp new file mode 100644 index 0000000..a4fb576 --- /dev/null +++ b/src/extension/internal/grid.cpp @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + \file grid.cpp + + A plug-in to add a grid creation effect into Inkscape. +*/ +/* + * Copyright (C) 2004-2005 Ted Gould <ted@gould.cx> + * Copyright (C) 2007 MenTaLguY <mental@rydia.net> + * Abhishek Sharma + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/box.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/spinbutton.h> + +#include "desktop.h" + +#include "document.h" +#include "selection.h" +#include "2geom/geom.h" + +#include "svg/path-string.h" + +#include "extension/effect.h" +#include "extension/system.h" + +#include "util/units.h" + +#include "grid.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +/** + \brief A function to allocated anything -- just an example here + \param module Unused + \return Whether the load was successful +*/ +bool +Grid::load (Inkscape::Extension::Extension */*module*/) +{ + // std::cout << "Hey, I'm Grid, I'm loading!" << std::endl; + return TRUE; +} + +namespace { + +Glib::ustring build_lines(Geom::Rect bounding_area, + Geom::Point const &offset, Geom::Point const &spacing) +{ + Geom::Point point_offset(0.0, 0.0); + + SVG::PathString path_data; + + for ( int axis = Geom::X ; axis <= Geom::Y ; ++axis ) { + point_offset[axis] = offset[axis]; + + for (Geom::Point start_point = bounding_area.min(); + start_point[axis] + offset[axis] <= (bounding_area.max())[axis]; + start_point[axis] += spacing[axis]) { + Geom::Point end_point = start_point; + end_point[1-axis] = (bounding_area.max())[1-axis]; + + path_data.moveTo(start_point + point_offset) + .lineTo(end_point + point_offset); + } + } + // std::cout << "Path data:" << path_data.c_str() << std::endl; + return path_data; +} + +} // namespace + +/** + \brief This actually draws the grid. + \param module The effect that was called (unused) + \param document What should be edited. +*/ +void +Grid::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + Inkscape::Selection * selection = ((SPDesktop *)document)->selection; + + Geom::Rect bounding_area = Geom::Rect(Geom::Point(0,0), Geom::Point(100,100)); + if (selection->isEmpty()) { + /* get page size */ + SPDocument * doc = document->doc(); + bounding_area = Geom::Rect( Geom::Point(0,0), + Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px")) ); + } else { + Geom::OptRect bounds = selection->visualBounds(); + if (bounds) { + bounding_area = *bounds; + } + + Geom::Rect temprec = bounding_area * static_cast<SPDesktop *>(document)->doc2dt(); + + bounding_area = temprec; + } + + double scale = document->doc()->getDocumentScale().inverse()[Geom::X]; + + bounding_area *= Geom::Scale(scale); + Geom::Point spacings( scale * module->get_param_float("xspacing"), + scale * module->get_param_float("yspacing") ); + gdouble line_width = scale * module->get_param_float("lineWidth"); + Geom::Point offsets( scale * module->get_param_float("xoffset"), + scale * module->get_param_float("yoffset") ); + + Glib::ustring path_data(""); + + path_data = build_lines(bounding_area, offsets, spacings); + Inkscape::XML::Document * xml_doc = document->doc()->getReprDoc(); + + //XML Tree being used directly here while it shouldn't be. + Inkscape::XML::Node * current_layer = static_cast<SPDesktop *>(document)->currentLayer()->getRepr(); + Inkscape::XML::Node * path = xml_doc->createElement("svg:path"); + + path->setAttribute("d", path_data); + + std::ostringstream stringstream; + stringstream << "fill:none;stroke:#000000;stroke-width:" << line_width << "px"; + path->setAttribute("style", stringstream.str()); + + current_layer->appendChild(path); + Inkscape::GC::release(path); +} + +/** \brief A class to make an adjustment that uses Extension params */ +class PrefAdjustment : public Gtk::Adjustment { + /** Extension that this relates to */ + Inkscape::Extension::Extension * _ext; + /** The string which represents the parameter */ + char * _pref; +public: + /** \brief Make the adjustment using an extension and the string + describing the parameter. */ + PrefAdjustment(Inkscape::Extension::Extension * ext, char * pref) : + Gtk::Adjustment(0.0, 0.0, 10.0, 0.1), _ext(ext), _pref(pref) { + this->set_value(_ext->get_param_float(_pref)); + this->signal_value_changed().connect(sigc::mem_fun(this, &PrefAdjustment::val_changed)); + return; + }; + + void val_changed (); +}; /* class PrefAdjustment */ + +/** \brief A function to respond to the value_changed signal from the + adjustment. + + This function just grabs the value from the adjustment and writes + it to the parameter. Very simple, but yet beautiful. +*/ +void +PrefAdjustment::val_changed () +{ + // std::cout << "Value Changed to: " << this->get_value() << std::endl; + _ext->set_param_float(_pref, this->get_value()); + return; +} + +/** \brief A function to get the prefences for the grid + \param moudule Module which holds the params + \param view Unused today - may get style information in the future. + + Uses AutoGUI for creating the GUI. +*/ +Gtk::Widget * +Grid::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + SPDocument * current_document = view->doc(); + + auto selected = ((SPDesktop *) view)->getSelection()->items(); + Inkscape::XML::Node * first_select = nullptr; + if (!selected.empty()) { + first_select = selected.front()->getRepr(); + } + + return module->autogui(current_document, first_select, changeSignal); +} + +#include "clear-n_.h" + +void +Grid::init () +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("Grid") "</name>\n" + "<id>org.inkscape.effect.grid</id>\n" + "<param name=\"lineWidth\" gui-text=\"" N_("Line Width:") "\" type=\"float\">1.0</param>\n" + "<param name=\"xspacing\" gui-text=\"" N_("Horizontal Spacing:") "\" type=\"float\" min=\"0.1\" max=\"1000\">10.0</param>\n" + "<param name=\"yspacing\" gui-text=\"" N_("Vertical Spacing:") "\" type=\"float\" min=\"0.1\" max=\"1000\">10.0</param>\n" + "<param name=\"xoffset\" gui-text=\"" N_("Horizontal Offset:") "\" type=\"float\" min=\"0.0\" max=\"1000\">0.0</param>\n" + "<param name=\"yoffset\" gui-text=\"" N_("Vertical Offset:") "\" type=\"float\" min=\"0.0\" max=\"1000\">0.0</param>\n" + "<effect>\n" + "<object-type>all</object-type>\n" + "<effects-menu>\n" + "<submenu name=\"" N_("Render") "\">\n" + "<submenu name=\"" N_("Grids") "\" />\n" + "</submenu>\n" + "</effects-menu>\n" + "<menu-tip>" N_("Draw a path which is a grid") "</menu-tip>\n" + "</effect>\n" + "</inkscape-extension>\n", new Grid()); + return; +} + +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* 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/extension/internal/grid.h b/src/extension/internal/grid.h new file mode 100644 index 0000000..16f4cd7 --- /dev/null +++ b/src/extension/internal/grid.h @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/implementation/implementation.h" + +namespace Inkscape { +namespace Extension { + +class Effect; +class Extension; + +namespace Internal { + +/** \brief Implementation class of the GIMP gradient plugin. This mostly + just creates a namespace for the GIMP gradient plugin today. +*/ +class Grid : public Inkscape::Extension::Implementation::Implementation { + +public: + bool load(Inkscape::Extension::Extension *module) override; + void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + Gtk::Widget * prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + + static void init (); +}; + +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* 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/extension/internal/image-resolution.cpp b/src/extension/internal/image-resolution.cpp new file mode 100644 index 0000000..3ca596c --- /dev/null +++ b/src/extension/internal/image-resolution.cpp @@ -0,0 +1,447 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Daniel Wagenaar <daw@caltech.edu> + * + * Copyright (C) 2012 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 + +#include "util/units.h" +#include "image-resolution.h" + +#define IR_TRY_PNG 1 +#include <png.h> + +#ifdef HAVE_EXIF +#include <math.h> +#include <libexif/exif-data.h> +#endif + +#define IR_TRY_EXIV 0 + +#ifdef HAVE_JPEG +#define IR_TRY_JFIF 1 +#include <jpeglib.h> +#include <csetjmp> +#endif + +#ifdef WITH_MAGICK +#include <Magick++.h> +#endif + +#define noIMAGE_RESOLUTION_DEBUG + +#ifdef IMAGE_RESOLUTION_DEBUG +# define debug(f, a...) { g_print("%s(%d) %s:", \ + __FILE__,__LINE__,__FUNCTION__); \ + g_print(f, ## a); \ + g_print("\n"); \ + } +#else +# define debug(f, a...) /* */ +#endif + +namespace Inkscape { +namespace Extension { +namespace Internal { + +ImageResolution::ImageResolution(char const *fn) { + ok_ = false; + + readpng(fn); + if (!ok_) { + readexiv(fn); + } + if (!ok_) { + readjfif(fn); + } + if (!ok_) { + readexif(fn); + } + if (!ok_) { + readmagick(fn); + } +} + +bool ImageResolution::ok() const { + return ok_; +} + +double ImageResolution::x() const { + return x_; +} + +double ImageResolution::y() const { + return y_; +} + + + +#if IR_TRY_PNG + +static bool haspngheader(FILE *fp) { + unsigned char header[8]; + if ( fread(header, 1, 8, fp) != 8 ) { + return false; + } + + fseek(fp, 0, SEEK_SET); + + if (png_sig_cmp(header, 0, 8)) { + return false; + } + + return true; +} + +// Implementation using libpng +void ImageResolution::readpng(char const *fn) { + FILE *fp = fopen(fn, "rb"); + if (!fp) + return; + + if (!haspngheader(fp)) { + fclose(fp); + return; + } + + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!png_ptr) + return; + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, nullptr, nullptr); + return; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + fclose(fp); + return; + } + + png_init_io(png_ptr, fp); + png_read_info(png_ptr, info_ptr); + + png_uint_32 res_x, res_y; +#ifdef PNG_INCH_CONVERSIONS_SUPPORTED + debug("PNG_INCH_CONVERSIONS_SUPPORTED"); + res_x = png_get_x_pixels_per_inch(png_ptr, info_ptr); + res_y = png_get_y_pixels_per_inch(png_ptr, info_ptr); + if (res_x != 0 && res_y != 0) { + ok_ = true; + x_ = res_x * 1.0; // FIXME: implicit conversion of png_uint_32 to double ok? + y_ = res_y * 1.0; // FIXME: implicit conversion of png_uint_32 to double ok? + } +#else + debug("PNG_RESOLUTION_METER"); + int unit_type; + // FIXME: png_get_pHYs() fails to return expected values + // with clang (based on LLVM 3.2svn) from Xcode 4.6.3 (OS X 10.7.5) + png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type); + + if (unit_type == PNG_RESOLUTION_METER) { + ok_ = true; + x_ = res_x * 2.54 / 100; + y_ = res_y * 2.54 / 100; + } +#endif + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + fclose(fp); + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} +#else + +// Dummy implementation +void ImageResolution::readpng(char const *) { +} + +#endif + +#if IR_TRY_EXIF + +static double exifDouble(ExifEntry *entry, ExifByteOrder byte_order) { + switch (entry->format) { + case EXIF_FORMAT_BYTE: { + return double(entry->data[0]); + } + case EXIF_FORMAT_SHORT: { + return double(exif_get_short(entry->data, byte_order)); + } + case EXIF_FORMAT_LONG: { + return double(exif_get_long(entry->data, byte_order)); + } + case EXIF_FORMAT_RATIONAL: { + ExifRational r = exif_get_rational(entry->data, byte_order); + return double(r.numerator) / double(r.denominator); + } + case EXIF_FORMAT_SBYTE: { + return double(*(signed char *)entry->data); + } + case EXIF_FORMAT_SSHORT: { + return double(exif_get_sshort(entry->data, byte_order)); + } + case EXIF_FORMAT_SLONG: { + return double(exif_get_slong(entry->data, byte_order)); + } + case EXIF_FORMAT_SRATIONAL: { + ExifSRational r = exif_get_srational(entry->data, byte_order); + return double(r.numerator) / double(r.denominator); + } + case EXIF_FORMAT_FLOAT: { + return double((reinterpret_cast<float *>(entry->data))[0]); + } + case EXIF_FORMAT_DOUBLE: { + return (reinterpret_cast<double *>(entry->data))[0]; + } + default: { + return nan(0); + } + } +} + +// Implementation using libexif +void ImageResolution::readexif(char const *fn) { + ExifData *ed; + ed = exif_data_new_from_file(fn); + if (!ed) + return; + + ExifByteOrder byte_order = exif_data_get_byte_order(ed); + + ExifEntry *xres = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_X_RESOLUTION); + ExifEntry *yres = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_Y_RESOLUTION); + ExifEntry *unit = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_RESOLUTION_UNIT); + + if ( xres && yres ) { + x_ = exifDouble(xres, byte_order); + y_ = exifDouble(yres, byte_order); + if (unit) { + double u = exifDouble(unit, byte_order); + if ( u == 3 ) { + x_ *= 2.54; + y_ *= 2.54; + } + } + ok_ = true; + } + exif_data_free(ed); + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readexif(char const *) { +} + +#endif + +#if IR_TRY_EXIV + +void ImageResolution::readexiv(char const *fn) { + Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(fn); + if (!image.get()) + return; + + image->readMetadata(); + Exiv2::ExifData &exifData = image->exifData(); + if (exifData.empty()) + return; + + Exiv2::ExifData::const_iterator end = exifData.end(); + bool havex = false; + bool havey = false; + bool haveunit = false; + int unit; + for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) { + if (ok_) + break; + if ( i->tag() == 0x011a ) { + // X Resolution + x_ = i->toFloat(); + havex = true; + } else if ( i->tag() == 0x011b ) { + // Y Resolution + y_ = i->toFloat(); + havey = true; + } else if ( i->tag() == 0x0128 ) { + unit = i->toLong(); + } + ok_ = havex && havey && haveunit; + } + if (haveunit) { + if ( unit == 3 ) { + x_ *= 2.54; + y_ *= 2.54; + } + } + ok_ = havex && havey; + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readexiv(char const *) { +} + +#endif + +#if IR_TRY_JFIF + +static void irjfif_error_exit(j_common_ptr cinfo) { + longjmp(*(jmp_buf*)cinfo->client_data, 1); +} + +static void irjfif_emit_message(j_common_ptr, int) { +} + +static void irjfif_output_message(j_common_ptr) { +} + +static void irjfif_format_message(j_common_ptr, char *) { +} + +static void irjfif_reset(j_common_ptr) { +} + +void ImageResolution::readjfif(char const *fn) { + FILE *ifd = fopen(fn, "rb"); + if (!ifd) { + return; + } + + struct jpeg_decompress_struct cinfo; + jmp_buf jbuf; + struct jpeg_error_mgr jerr; + + if (setjmp(jbuf)) { + fclose(ifd); + jpeg_destroy_decompress(&cinfo); + return; + } + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + jerr.error_exit = &irjfif_error_exit; + jerr.emit_message = &irjfif_emit_message; + jerr.output_message = &irjfif_output_message; + jerr.format_message = &irjfif_format_message; + jerr.reset_error_mgr = &irjfif_reset; + cinfo.client_data = (void*)&jbuf; + + jpeg_stdio_src(&cinfo, ifd); + jpeg_read_header(&cinfo, TRUE); + + debug("cinfo.[XY]_density"); + if (cinfo.saw_JFIF_marker) { // JFIF APP0 marker was seen + if ( cinfo.density_unit == 1 ) { // dots/inch + x_ = cinfo.X_density; + y_ = cinfo.Y_density; + ok_ = true; + } else if ( cinfo.density_unit == 2 ) { // dots/cm + x_ = cinfo.X_density * 2.54; + y_ = cinfo.Y_density * 2.54; + ok_ = true; + } + /* According to http://www.jpeg.org/public/jfif.pdf (page 7): + * "Xdensity and Ydensity should always be non-zero". + * but in some cases, they are (see LP bug #1275443) */ + if (x_ == 0 or y_ == 0) { + ok_ = false; + } + } + jpeg_destroy_decompress(&cinfo); + fclose(ifd); + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readjfif(char const *) { +} + +#endif + +#ifdef WITH_MAGICK +void ImageResolution::readmagick(char const *fn) { + Magick::Image image; + debug("Trying image.read"); + try { + image.read(fn); + } catch (Magick::Error & err) { + debug("ImageMagick error: %s", err.what()); + return; + } catch (std::exception & err) { + debug("ImageResolution::readmagick: %s", err.what()); + return; + } + debug("image.[xy]Resolution"); + std::string const type = image.magick(); + x_ = image.xResolution(); + y_ = image.yResolution(); + +// TODO: find out why the hell the following conversion is necessary + if (type == "BMP") { + x_ = Inkscape::Util::Quantity::convert(x_, "in", "cm"); + y_ = Inkscape::Util::Quantity::convert(y_, "in", "cm"); + } + + if (x_ != 0 && y_ != 0) { + ok_ = true; + } + + if (ok_) { + debug("xdpi: %f", x_); + debug("ydpi: %f", y_); + } else { + debug("FAILED"); + debug("Using default Inkscape import resolution"); + } +} + +#else + +// Dummy implementation +void ImageResolution::readmagick(char const *) { +} + +#endif /* WITH_MAGICK */ + +} +} +} diff --git a/src/extension/internal/image-resolution.h b/src/extension/internal/image-resolution.h new file mode 100644 index 0000000..ad69df0 --- /dev/null +++ b/src/extension/internal/image-resolution.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Daniel Wagenaar <daw@caltech.edu> + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +#ifndef IMAGE_RESOLUTION_H + +#define IMAGE_RESOLUTION_H + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class ImageResolution { +public: + ImageResolution(char const *fn); + bool ok() const; + double x() const; + double y() const; +private: + bool ok_; + double x_; + double y_; +private: + void readpng(char const *fn); + void readexif(char const *fn); + void readexiv(char const *fn); + void readjfif(char const *fn); + void readmagick(char const *fn); +}; + +} +} +} + +#endif diff --git a/src/extension/internal/latex-pstricks-out.cpp b/src/extension/internal/latex-pstricks-out.cpp new file mode 100644 index 0000000..5bc4e8d --- /dev/null +++ b/src/extension/internal/latex-pstricks-out.cpp @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Michael Forbes <miforbes@mbhs.edu> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "latex-pstricks-out.h" +#include <print.h> +#include "extension/system.h" +#include "extension/print.h" +#include "extension/db.h" +#include "display/drawing.h" +#include "object/sp-root.h" + + +#include "document.h" + + +namespace Inkscape { +namespace Extension { +namespace Internal { + +LatexOutput::LatexOutput () // The null constructor +{ + return; +} + +LatexOutput::~LatexOutput () //The destructor +{ + return; +} + +bool LatexOutput::check(Inkscape::Extension::Extension * /*module*/) +{ + bool result = Inkscape::Extension::db.get("org.inkscape.print.latex") != nullptr; + return result; +} + + +void LatexOutput::save(Inkscape::Extension::Output * /*mod2*/, SPDocument *doc, gchar const *filename) +{ + SPPrintContext context; + doc->ensureUpToDate(); + + Inkscape::Extension::Print *mod = Inkscape::Extension::get_print(SP_MODULE_KEY_PRINT_LATEX); + const gchar * oldconst = mod->get_param_string("destination"); + gchar * oldoutput = g_strdup(oldconst); + mod->set_param_string("destination", filename); + + // Start + context.module = mod; + // fixme: This has to go into module constructor somehow + mod->base = doc->getRoot(); + Inkscape::Drawing drawing; + mod->dkey = SPItem::display_key_new(1); + mod->root = (mod->base)->invoke_show(drawing, mod->dkey, SP_ITEM_SHOW_DISPLAY); + drawing.setRoot(mod->root); + // Print document + mod->begin(doc); + (mod->base)->invoke_print(&context); + mod->finish(); + // Release things + (mod->base)->invoke_hide(mod->dkey); + mod->base = nullptr; + mod->root = nullptr; // should have been deleted by invoke_hide + // end + + mod->set_param_string("destination", oldoutput); + g_free(oldoutput); +} + +#include "clear-n_.h" + +/** + \brief A function allocate a copy of this function. + + This is the definition of postscript out. This function just + calls the extension system with the memory allocated XML that + describes the data. +*/ +void +LatexOutput::init () +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("LaTeX Output") "</name>\n" + "<id>org.inkscape.output.latex</id>\n" + "<output>\n" + "<extension>.tex</extension>\n" + "<mimetype>text/x-tex</mimetype>\n" + "<filetypename>" N_("LaTeX With PSTricks macros (*.tex)") "</filetypename>\n" + "<filetypetooltip>" N_("LaTeX PSTricks File") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new LatexOutput()); + + return; +} + +} } } /* namespace Inkscape, Extension, Implementation */ + +/* + Local Variables: + mode:cpp + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/latex-pstricks-out.h b/src/extension/internal/latex-pstricks-out.h new file mode 100644 index 0000000..670904b --- /dev/null +++ b/src/extension/internal/latex-pstricks-out.h @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * + * Authors: + * Michael Forbes <miforbes-inkscape@mbhs.edu> + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef EXTENSION_INTERNAL_LATEX_OUT_H +#define EXTENSION_INTERNAL_LATEX_OUT_H + +#include "extension/implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class LatexOutput : Inkscape::Extension::Implementation::Implementation { //This is a derived class + +public: + LatexOutput(); // Empty constructor + + ~LatexOutput() override;//Destructor + + bool check(Inkscape::Extension::Extension *module) override; //Can this module load (always yes for now) + + void save(Inkscape::Extension::Output *mod, // Save the given document to the given filename + SPDocument *doc, + gchar const *filename) override; + + static void init();//Initialize the class +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + +#endif /* EXTENSION_INTERNAL_LATEX_OUT_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/extension/internal/latex-pstricks.cpp b/src/extension/internal/latex-pstricks.cpp new file mode 100644 index 0000000..bb680e7 --- /dev/null +++ b/src/extension/internal/latex-pstricks.cpp @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * LaTeX Printing + * + * Author: + * Michael Forbes <miforbes@mbhs.edu> + * Abhishek Sharma + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <2geom/pathvector.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/curves.h> +#include <cerrno> +#include <csignal> +#include "util/units.h" +#include "helper/geom-curves.h" + +#include "extension/print.h" +#include "extension/system.h" +#include "inkscape-version.h" +#include "io/sys.h" +#include "latex-pstricks.h" +#include "style.h" +#include "document.h" +#include <cstring> + +namespace Inkscape { +namespace Extension { +namespace Internal { + +PrintLatex::PrintLatex (): + _width(0), + _height(0), + _stream(nullptr) +{ +} + +PrintLatex::~PrintLatex () +{ + if (_stream) fclose(_stream); + + /* restore default signal handling for SIGPIPE */ +#if !defined(_WIN32) && !defined(__WIN32__) + (void) signal(SIGPIPE, SIG_DFL); +#endif + return; +} + +unsigned int PrintLatex::setup(Inkscape::Extension::Print * /*mod*/) +{ + return TRUE; +} + +unsigned int PrintLatex::begin (Inkscape::Extension::Print *mod, SPDocument *doc) +{ + Inkscape::SVGOStringStream os; + int res; + FILE *osf = nullptr; + const gchar * fn = nullptr; + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError* error = nullptr; + + os.setf(std::ios::fixed); + fn = mod->get_param_string("destination"); + gchar* local_fn = g_filename_from_utf8( fn, + -1, &bytesRead, &bytesWritten, &error); + fn = local_fn; + + /* TODO: Replace the below fprintf's with something that does the right thing whether in + * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of + * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the + * return code. + */ + if (fn != nullptr) { + while (isspace(*fn)) fn += 1; + Inkscape::IO::dump_fopen_call(fn, "K"); + osf = Inkscape::IO::fopen_utf8name(fn, "w+"); + if (!osf) { + fprintf(stderr, "inkscape: fopen(%s): %s\n", fn, strerror(errno)); + g_free(local_fn); + return 0; + } + _stream = osf; + } + + g_free(local_fn); + + /* fixme: this is kinda icky */ +#if !defined(_WIN32) && !defined(__WIN32__) + (void) signal(SIGPIPE, SIG_IGN); +#endif + + res = fprintf(_stream, "%%LaTeX with PSTricks extensions\n"); + /* flush this to test output stream as early as possible */ + if (fflush(_stream)) { + /*g_print("caught error in sp_module_print_plain_begin\n");*/ + if (ferror(_stream)) { + g_print("Error %d on output stream: %s\n", errno, + g_strerror(errno)); + } + g_print("Printing failed\n"); + /* fixme: should use pclose() for pipes */ + fclose(_stream); + _stream = nullptr; + fflush(stdout); + return 0; + } + + // width and height in pt + _width = doc->getWidth().value("pt"); + _height = doc->getHeight().value("pt"); + + if (res >= 0) { + + os << "%%Creator: Inkscape " << Inkscape::version_string << "\n"; + os << "%%Please note this file requires PSTricks extensions\n"; + + os << "\\psset{xunit=.5pt,yunit=.5pt,runit=.5pt}\n"; + // from now on we can output px, but they will be treated as pt + + os << "\\begin{pspicture}(" << doc->getWidth().value("px") << "," << doc->getHeight().value("px") << ")\n"; + } + + m_tr_stack.push( Geom::Scale(1, -1) * Geom::Translate(0, doc->getHeight().value("px"))); /// @fixme hardcoded doc2dt transform + + return fprintf(_stream, "%s", os.str().c_str()); +} + +unsigned int PrintLatex::finish(Inkscape::Extension::Print * /*mod*/) +{ + if (_stream) { + fprintf(_stream, "\\end{pspicture}\n"); + + // Flush stream to be sure. + fflush(_stream); + + fclose(_stream); + _stream = nullptr; + } + return 0; +} + +unsigned int PrintLatex::bind(Inkscape::Extension::Print * /*mod*/, Geom::Affine const &transform, float /*opacity*/) +{ + if (!m_tr_stack.empty()) { + Geom::Affine tr_top = m_tr_stack.top(); + m_tr_stack.push(transform * tr_top); + } else { + m_tr_stack.push(transform); + } + + return 1; +} + +unsigned int PrintLatex::release(Inkscape::Extension::Print * /*mod*/) +{ + m_tr_stack.pop(); + return 1; +} + +unsigned int PrintLatex::comment(Inkscape::Extension::Print * /*mod*/, + const char * comment) +{ + if (!_stream) { + return 0; // XXX: fixme, returning -1 as unsigned. + } + + return fprintf(_stream, "%%! %s\n",comment); +} + +unsigned int PrintLatex::fill(Inkscape::Extension::Print * /*mod*/, + Geom::PathVector const &pathv, Geom::Affine const &transform, SPStyle const *style, + Geom::OptRect const & /*pbox*/, Geom::OptRect const & /*dbox*/, Geom::OptRect const & /*bbox*/) +{ + if (!_stream) { + return 0; // XXX: fixme, returning -1 as unsigned. + } + + if (style->fill.isColor()) { + Inkscape::SVGOStringStream os; + float rgb[3]; + float fill_opacity; + + os.setf(std::ios::fixed); + + fill_opacity=SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + style->fill.value.color.get_rgb_floatv(rgb); + os << "{\n\\newrgbcolor{curcolor}{" << rgb[0] << " " << rgb[1] << " " << rgb[2] << "}\n"; + os << "\\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor"; + if (fill_opacity!=1.0) { + os << ",opacity="<<fill_opacity; + } + + os << "]\n{\n"; + + print_pathvector(os, pathv, transform); + + os << "}\n}\n"; + + fprintf(_stream, "%s", os.str().c_str()); + } + + return 0; +} + +unsigned int PrintLatex::stroke(Inkscape::Extension::Print * /*mod*/, + Geom::PathVector const &pathv, Geom::Affine const &transform, SPStyle const *style, + Geom::OptRect const & /*pbox*/, Geom::OptRect const & /*dbox*/, Geom::OptRect const & /*bbox*/) +{ + if (!_stream) { + return 0; // XXX: fixme, returning -1 as unsigned. + } + + if (style->stroke.isColor()) { + Inkscape::SVGOStringStream os; + float rgb[3]; + float stroke_opacity; + Geom::Affine tr_stack = m_tr_stack.top(); + double const scale = tr_stack.descrim(); + os.setf(std::ios::fixed); + + stroke_opacity=SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); + style->stroke.value.color.get_rgb_floatv(rgb); + os << "{\n\\newrgbcolor{curcolor}{" << rgb[0] << " " << rgb[1] << " " << rgb[2] << "}\n"; + + os << "\\pscustom[linewidth=" << style->stroke_width.computed*scale<< ",linecolor=curcolor"; + + if (stroke_opacity!=1.0) { + os<<",strokeopacity="<<stroke_opacity; + } + + if (style->stroke_dasharray.set && !style->stroke_dasharray.values.empty()) { + os << ",linestyle=dashed,dash="; + for (unsigned i = 0; i < style->stroke_dasharray.values.size(); i++) { + if ((i)) { + os << " "; + } + os << style->stroke_dasharray.values[i].value; + } + } + + os <<"]\n{\n"; + + print_pathvector(os, pathv, transform); + + os << "}\n}\n"; + + fprintf(_stream, "%s", os.str().c_str()); + } + + return 0; +} + +// FIXME: why is 'transform' argument not used? +void +PrintLatex::print_pathvector(SVGOStringStream &os, Geom::PathVector const &pathv_in, const Geom::Affine & /*transform*/) +{ + if (pathv_in.empty()) + return; + +// Geom::Affine tf=transform; // why was this here? + Geom::Affine tf_stack=m_tr_stack.top(); // and why is transform argument not used? + Geom::PathVector pathv = pathv_in * tf_stack; // generates new path, which is a bit slow, but this doesn't have to be performance optimized + + os << "\\newpath\n"; + + for(const auto & it : pathv) { + + os << "\\moveto(" << it.initialPoint()[Geom::X] << "," << it.initialPoint()[Geom::Y] << ")\n"; + + for(Geom::Path::const_iterator cit = it.begin(); cit != it.end_open(); ++cit) { + print_2geomcurve(os, *cit); + } + + if (it.closed()) { + os << "\\closepath\n"; + } + + } +} + +void +PrintLatex::print_2geomcurve(SVGOStringStream &os, Geom::Curve const &c) +{ + using Geom::X; + using Geom::Y; + + if( is_straight_curve(c) ) + { + os << "\\lineto(" << c.finalPoint()[X] << "," << c.finalPoint()[Y] << ")\n"; + } + else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) { + std::vector<Geom::Point> points = cubic_bezier->controlPoints(); + os << "\\curveto(" << points[1][X] << "," << points[1][Y] << ")(" + << points[2][X] << "," << points[2][Y] << ")(" + << points[3][X] << "," << points[3][Y] << ")\n"; + } + else { + //this case handles sbasis as well as all other curve types + Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1); + + for(const auto & iter : sbasis_path) { + print_2geomcurve(os, iter); + } + } +} + +bool +PrintLatex::textToPath(Inkscape::Extension::Print * ext) +{ + return ext->get_param_bool("textToPath"); +} + +#include "clear-n_.h" + +void PrintLatex::init() +{ + /* SVG in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("LaTeX Print") "</name>\n" + "<id>" SP_MODULE_KEY_PRINT_LATEX "</id>\n" + "<param gui-hidden=\"true\" name=\"destination\" type=\"string\"></param>\n" + "<param gui-hidden=\"true\" name=\"textToPath\" type=\"bool\">true</param>\n" + "<print/>\n" + "</inkscape-extension>", new PrintLatex()); +} + +} /* namespace Internal */ +} /* namespace Extension */ +} /* 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:fileencoding=utf-8:textwidth=99 : + diff --git a/src/extension/internal/latex-pstricks.h b/src/extension/internal/latex-pstricks.h new file mode 100644 index 0000000..37d08b8 --- /dev/null +++ b/src/extension/internal/latex-pstricks.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef __INKSCAPE_EXTENSION_INTERNAL_PRINT_LATEX_H__ +#define __INKSCAPE_EXTENSION_INTERNAL_PRINT_LATEX_H__ + +/* + * LaTeX Printing + * + * Author: + * Michael Forbes <miforbes@mbhs.edu> + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <stack> + +#include "extension/implementation/implementation.h" +#include "extension/extension.h" + +#include "svg/stringstream.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class PrintLatex : public Inkscape::Extension::Implementation::Implementation { + + float _width; + float _height; + FILE * _stream; + + std::stack<Geom::Affine> m_tr_stack; + + void print_pathvector(SVGOStringStream &os, Geom::PathVector const &pathv_in, const Geom::Affine & /*transform*/); + void print_2geomcurve(SVGOStringStream &os, Geom::Curve const & c ); + +public: + PrintLatex (); + ~PrintLatex () override; + + /* Print functions */ + unsigned int setup (Inkscape::Extension::Print * module) override; + + unsigned int begin (Inkscape::Extension::Print * module, SPDocument *doc) override; + unsigned int finish (Inkscape::Extension::Print * module) override; + + /* Rendering methods */ + unsigned int bind(Inkscape::Extension::Print *module, Geom::Affine const &transform, float opacity) override; + unsigned int release(Inkscape::Extension::Print *module) override; + + unsigned int fill (Inkscape::Extension::Print *module, Geom::PathVector const &pathv, + Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, + Geom::OptRect const &bbox) override; + unsigned int stroke (Inkscape::Extension::Print *module, Geom::PathVector const &pathv, + Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, + Geom::OptRect const &bbox) override; + unsigned int comment(Inkscape::Extension::Print *module, const char * comment) override; + bool textToPath (Inkscape::Extension::Print * ext) override; + + static void init (); +}; + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_LATEX */ + +/* + Local Variables: + mode:cpp + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/latex-text-renderer.cpp b/src/extension/internal/latex-text-renderer.cpp new file mode 100644 index 0000000..732f9c8 --- /dev/null +++ b/src/extension/internal/latex-text-renderer.cpp @@ -0,0 +1,754 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Rendering LaTeX file (pdf/eps/ps+latex output) + * + * The idea stems from GNUPlot's epslatex terminal output :-) + */ +/* + * Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * Miklos Erdelyi <erdelyim@gmail.com> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2006-2011 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "latex-text-renderer.h" + +#include <csignal> +#include <cerrno> + +#include <glibmm/i18n.h> +#include <glibmm/regex.h> + +#include "libnrtype/Layout-TNG.h" +#include <2geom/transforms.h> +#include <2geom/rect.h> + +#include "object/sp-item.h" +#include "object/sp-item-group.h" +#include "object/sp-root.h" +#include "object/sp-use.h" +#include "object/sp-text.h" +#include "object/sp-flowtext.h" +#include "object/sp-rect.h" +#include "style.h" + +#include "text-editing.h" + +#include "util/units.h" + +#include "extension/output.h" +#include "extension/system.h" + +#include "inkscape-version.h" +#include "io/sys.h" +#include "document.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +/** + * This method is called by the PDF, EPS and PS output extensions. + * @param filename This should be the filename without '_tex' extension to which the tex code should be written. Output goes to <filename>_tex, note the underscore instead of period. + */ +bool +latex_render_document_text_to_file( SPDocument *doc, gchar const *filename, + const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px, + bool pdflatex) +{ + doc->ensureUpToDate(); + + SPRoot *root = doc->getRoot(); + SPItem *base = nullptr; + + bool pageBoundingBox = true; + if (exportId && strcmp(exportId, "")) { + // we want to export the given item only + base = dynamic_cast<SPItem *>(doc->getObjectById(exportId)); + if (!base) { + throw Inkscape::Extension::Output::export_id_not_found(exportId); + } + root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only) + pageBoundingBox = exportCanvas; + } + else { + // we want to export the entire document from root + base = root; + pageBoundingBox = !exportDrawing; + } + + if (!base) + return false; + + /* Create renderer */ + LaTeXTextRenderer *renderer = new LaTeXTextRenderer(pdflatex); + + bool ret = renderer->setTargetFile(filename); + if (ret) { + /* Render document */ + bool ret = renderer->setupDocument(doc, pageBoundingBox, bleedmargin_px, base); + if (ret) { + renderer->renderItem(root); + } + } + + delete renderer; + + return ret; +} + +LaTeXTextRenderer::LaTeXTextRenderer(bool pdflatex) + : _stream(nullptr), + _filename(nullptr), + _pdflatex(pdflatex), + _omittext_state(EMPTY), + _omittext_page(1) +{ + push_transform(Geom::identity()); +} + +LaTeXTextRenderer::~LaTeXTextRenderer() +{ + if (_stream) { + writePostamble(); + + fclose(_stream); + } + + /* restore default signal handling for SIGPIPE */ +#if !defined(_WIN32) && !defined(__WIN32__) + (void) signal(SIGPIPE, SIG_DFL); +#endif + + if (_filename) { + g_free(_filename); + } + + return; +} + +/** This should create the output LaTeX file, and assign it to _stream. + * @return Returns true when successful + */ +bool +LaTeXTextRenderer::setTargetFile(gchar const *filename) { + if (filename != nullptr) { + while (isspace(*filename)) filename += 1; + + _filename = g_path_get_basename(filename); + + gchar *filename_ext = g_strdup_printf("%s_tex", filename); + Inkscape::IO::dump_fopen_call(filename_ext, "K"); + FILE *osf = Inkscape::IO::fopen_utf8name(filename_ext, "w+"); + if (!osf) { + fprintf(stderr, "inkscape: fopen(%s): %s\n", filename_ext, strerror(errno)); + g_free(filename_ext); + return false; + } + _stream = osf; + g_free(filename_ext); + } + + /* fixme: this is kinda icky */ +#if !defined(_WIN32) && !defined(__WIN32__) + (void) signal(SIGPIPE, SIG_IGN); +#endif + + fprintf(_stream, "%%%% Creator: Inkscape %s, www.inkscape.org\n", Inkscape::version_string); + fprintf(_stream, "%%%% PDF/EPS/PS + LaTeX output extension by Johan Engelen, 2010\n"); + fprintf(_stream, "%%%% Accompanies image file '%s' (pdf, eps, ps)\n", _filename); + fprintf(_stream, "%%%%\n"); + /* flush this to test output stream as early as possible */ + if (fflush(_stream)) { + if (ferror(_stream)) { + g_print("Error %d on LaTeX file output stream: %s\n", errno, + g_strerror(errno)); + } + g_print("Output to LaTeX file failed\n"); + /* fixme: should use pclose() for pipes */ + fclose(_stream); + _stream = nullptr; + fflush(stdout); + return false; + } + + writePreamble(); + + return true; +} + +static char const preamble[] = +"%% To include the image in your LaTeX document, write\n" +"%% \\input{<filename>.pdf_tex}\n" +"%% instead of\n" +"%% \\includegraphics{<filename>.pdf}\n" +"%% To scale the image, write\n" +"%% \\def\\svgwidth{<desired width>}\n" +"%% \\input{<filename>.pdf_tex}\n" +"%% instead of\n" +"%% \\includegraphics[width=<desired width>]{<filename>.pdf}\n" +"%%\n" +"%% Images with a different path to the parent latex file can\n" +"%% be accessed with the `import' package (which may need to be\n" +"%% installed) using\n" +"%% \\usepackage{import}\n" +"%% in the preamble, and then including the image with\n" +"%% \\import{<path to file>}{<filename>.pdf_tex}\n" +"%% Alternatively, one can specify\n" +"%% \\graphicspath{{<path to file>/}}\n" +"%% \n" +"%% For more information, please see info/svg-inkscape on CTAN:\n" +"%% http://tug.ctan.org/tex-archive/info/svg-inkscape\n" +"%%\n" +"\\begingroup%\n" +" \\makeatletter%\n" +" \\providecommand\\color[2][]{%\n" +" \\errmessage{(Inkscape) Color is used for the text in Inkscape, but the package \'color.sty\' is not loaded}%\n" +" \\renewcommand\\color[2][]{}%\n" +" }%\n" +" \\providecommand\\transparent[1]{%\n" +" \\errmessage{(Inkscape) Transparency is used (non-zero) for the text in Inkscape, but the package \'transparent.sty\' is not loaded}%\n" +" \\renewcommand\\transparent[1]{}%\n" +" }%\n" +" \\providecommand\\rotatebox[2]{#2}%\n" +" \\newcommand*\\fsize{\\dimexpr\\f@size pt\\relax}%\n" +" \\newcommand*\\lineheight[1]{\\fontsize{\\fsize}{#1\\fsize}\\selectfont}%\n"; + +static char const postamble[] = +" \\end{picture}%\n" +"\\endgroup%\n"; + +void +LaTeXTextRenderer::writePreamble() +{ + fprintf(_stream, "%s", preamble); +} +void +LaTeXTextRenderer::writePostamble() +{ + fprintf(_stream, "%s", postamble); +} + +void LaTeXTextRenderer::sp_group_render(SPGroup *group) +{ + std::vector<SPObject*> l = (group->childList(false)); + for(auto x : l){ + SPItem *item = dynamic_cast<SPItem*>(x); + if (item) { + renderItem(item); + } + } +} + +void LaTeXTextRenderer::sp_use_render(SPUse *use) +{ + bool translated = false; + + if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) { + Geom::Affine tp(Geom::Translate(use->x.computed, use->y.computed)); + push_transform(tp); + translated = true; + } + + SPItem *childItem = dynamic_cast<SPItem *>(use->child); + if (childItem) { + renderItem(childItem); + } + + if (translated) { + pop_transform(); + } +} + +void LaTeXTextRenderer::sp_text_render(SPText *textobj) +{ + // Nothing to do here... (so don't emit an empty box) + // Also avoids falling out of sync with the CairoRenderer (which won't render anything in this case either) + if (textobj->layout.getActualLength() == 0) + return; + + // Only PDFLaTeX supports importing a single page of a graphics file, + // so only PDF backend gets interleaved text/graphics + if (_pdflatex && _omittext_state == GRAPHIC_ON_TOP) + _omittext_state = NEW_PAGE_ON_GRAPHIC; + + SPStyle *style = textobj->style; + + // get position and alignment + // Align vertically on the baseline of the font (retrieved from the anchor point) + // Align horizontally on anchorpoint + gchar const *alignment = nullptr; + gchar const *aligntabular = nullptr; + switch (style->text_anchor.computed) { + case SP_CSS_TEXT_ANCHOR_START: + alignment = "[lt]"; + aligntabular = "{l}"; + break; + case SP_CSS_TEXT_ANCHOR_END: + alignment = "[rt]"; + aligntabular = "{r}"; + break; + case SP_CSS_TEXT_ANCHOR_MIDDLE: + default: + alignment = "[t]"; + aligntabular = "{c}"; + break; + } + Geom::Point anchor = textobj->attributes.firstXY() * transform(); + Geom::Point pos(anchor); + + // determine color and transparency (for now, use rgb color model as it is most native to Inkscape) + bool has_color = false; // if the item has no color set, don't force black color + bool has_transparency = false; + // TODO: how to handle ICC colors? + // give priority to fill color + guint32 rgba = 0; + float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); + if (style->fill.set && style->fill.isColor()) { + has_color = true; + rgba = style->fill.value.color.toRGBA32(1.); + opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + } else if (style->stroke.set && style->stroke.isColor()) { + has_color = true; + rgba = style->stroke.value.color.toRGBA32(1.); + opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); + } + if (opacity < 1.0) { + has_transparency = true; + } + + // get rotation + Geom::Affine i2doc = textobj->i2doc_affine(); + Geom::Affine wotransl = i2doc.withoutTranslation(); + double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis()); + bool has_rotation = !Geom::are_near(degrees,0.); + + // get line-height + float line_height; + if (style->line_height.unit == SP_CSS_UNIT_NONE) { + // unitless 'line-height' (use as-is, computed value is relative value) + line_height = style->line_height.computed; + } else { + // 'line-height' with unit (make relative, computed value is absolute value) + line_height = style->line_height.computed / style->font_size.computed; + } + + // write to LaTeX + Inkscape::SVGOStringStream os; + os.setf(std::ios::fixed); // don't use scientific notation + + os << " \\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){"; + if (has_color) { + os << "\\color[rgb]{" << SP_RGBA32_R_F(rgba) << "," << SP_RGBA32_G_F(rgba) << "," << SP_RGBA32_B_F(rgba) << "}"; + } + if (_pdflatex && has_transparency) { + os << "\\transparent{" << opacity << "}"; + } + if (has_rotation) { + os << "\\rotatebox{" << degrees << "}{"; + } + os << "\\makebox(0,0)" << alignment << "{"; + if (line_height != 1) { + os << "\\lineheight{" << line_height << "}"; + } + os << "\\smash{"; + os << "\\begin{tabular}[t]" << aligntabular; + + // Walk through all spans in the text object. + // Write span strings to LaTeX, associated with font weight and style. + Inkscape::Text::Layout const &layout = *(te_get_layout (textobj)); + for (Inkscape::Text::Layout::iterator li = layout.begin(), le = layout.end(); + li != le; li.nextStartOfSpan()) + { + Inkscape::Text::Layout::iterator ln = li; + ln.nextStartOfSpan(); + Glib::ustring uspanstr = sp_te_get_string_multiline (textobj, li, ln); + + // escape ampersands + uspanstr = Glib::Regex::create("&")->replace_literal(uspanstr, 0, "\\&", (Glib::RegexMatchFlags)0); + // escape percent + uspanstr = Glib::Regex::create("%")->replace_literal(uspanstr, 0, "\\%", (Glib::RegexMatchFlags)0); + + const gchar *spanstr = uspanstr.c_str(); + if (!spanstr) { + continue; + } + + bool is_bold = false, is_italic = false, is_oblique = false; + + // newline character only -> don't attempt to add style (will break compilation in LaTeX) + if (g_strcmp0(spanstr, "\n")) { + SPStyle const &spanstyle = *(sp_te_style_at_position (textobj, li)); + if (spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_500 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_600 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_700 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_800 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_900 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLD || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLDER) + { + is_bold = true; + os << "\\textbf{"; + } + if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_ITALIC) + { + is_italic = true; + os << "\\textit{"; + } + if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_OBLIQUE) + { + is_oblique = true; + os << "\\textsl{"; // this is an accurate choice if the LaTeX chosen font matches the font in Inkscape. Gives bad results when it is not so... + } + } + + // replace carriage return with double slash + gchar ** splitstr = g_strsplit(spanstr, "\n", 2); + os << splitstr[0]; + if (g_strv_length(splitstr) > 1) + { + os << "\\\\"; + } + g_strfreev(splitstr); + + if (is_oblique) { os << "}"; } // oblique end + if (is_italic) { os << "}"; } // italic end + if (is_bold) { os << "}"; } // bold end + } + + os << "\\end{tabular}"; // tabular end + os << "}"; // smash end + if (has_rotation) { os << "}"; } // rotatebox end + os << "}"; //makebox end + os << "}%\n"; // put end + + fprintf(_stream, "%s", os.str().c_str()); +} + +void LaTeXTextRenderer::sp_flowtext_render(SPFlowtext *flowtext) +{ +/* +Flowtext is possible by using a minipage! :) +Flowing in rectangle is possible, not in arb shape. +*/ + + // Only PDFLaTeX supports importing a single page of a graphics file, + // so only PDF backend gets interleaved text/graphics + if (_pdflatex && _omittext_state == GRAPHIC_ON_TOP) + _omittext_state = NEW_PAGE_ON_GRAPHIC; + + SPStyle *style = flowtext->style; + + SPItem *frame_item = flowtext->get_frame(nullptr); + SPRect *frame = dynamic_cast<SPRect *>(frame_item); + if (!frame_item || !frame) { + g_warning("LaTeX export: non-rectangular flowed text shapes are not supported, skipping text."); + return; // don't know how to handle non-rect frames yet. is quite uncommon for latex users i think + } + + // We will transform the coordinates + Geom::Rect framebox = frame->getRect(); + + // get position and alignment + // Align on topleft corner. + gchar const *alignment = "[lt]"; + gchar const *justification = ""; + switch (flowtext->layout.paragraphAlignment(flowtext->layout.begin())) { + case Inkscape::Text::Layout::LEFT: + justification = "\\raggedright "; + break; + case Inkscape::Text::Layout::RIGHT: + justification = "\\raggedleft "; + break; + case Inkscape::Text::Layout::CENTER: + justification = "\\centering "; + case Inkscape::Text::Layout::FULL: + default: + // no need to add LaTeX code for standard justified output :) + break; + } + + // The topleft Corner was calculated after rotating the text which results in a wrong Coordinate. + // Now, the topleft Corner is rotated after calculating it + Geom::Point pos(framebox.corner(0) * transform()); //topleft corner + + // determine color and transparency (for now, use rgb color model as it is most native to Inkscape) + bool has_color = false; // if the item has no color set, don't force black color + bool has_transparency = false; + // TODO: how to handle ICC colors? + // give priority to fill color + guint32 rgba = 0; + float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value); + if (style->fill.set && style->fill.isColor()) { + has_color = true; + rgba = style->fill.value.color.toRGBA32(1.); + opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + } else if (style->stroke.set && style->stroke.isColor()) { + has_color = true; + rgba = style->stroke.value.color.toRGBA32(1.); + opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value); + } + if (opacity < 1.0) { + has_transparency = true; + } + + // get rotation + Geom::Affine i2doc = flowtext->i2doc_affine(); + Geom::Affine wotransl = i2doc.withoutTranslation(); + double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis()); + bool has_rotation = !Geom::are_near(degrees,0.); + + // write to LaTeX + Inkscape::SVGOStringStream os; + os.setf(std::ios::fixed); // don't use scientific notation + + os << " \\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){"; + if (has_color) { + os << "\\color[rgb]{" << SP_RGBA32_R_F(rgba) << "," << SP_RGBA32_G_F(rgba) << "," << SP_RGBA32_B_F(rgba) << "}"; + } + if (_pdflatex && has_transparency) { + os << "\\transparent{" << opacity << "}"; + } + if (has_rotation) { + os << "\\rotatebox{" << degrees << "}{"; + } + os << "\\makebox(0,0)" << alignment << "{"; + + // Scale the x width correctly + os << "\\begin{minipage}{" << framebox.width() * transform().expansionX() << "\\unitlength}"; + os << justification; + + // Walk through all spans in the text object. + // Write span strings to LaTeX, associated with font weight and style. + Inkscape::Text::Layout const &layout = *(te_get_layout(flowtext)); + for (Inkscape::Text::Layout::iterator li = layout.begin(), le = layout.end(); + li != le; li.nextStartOfSpan()) + { + SPStyle const &spanstyle = *(sp_te_style_at_position(flowtext, li)); + bool is_bold = false, is_italic = false, is_oblique = false; + + if (spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_500 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_600 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_700 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_800 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_900 || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLD || + spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLDER) + { + is_bold = true; + os << "\\textbf{"; + } + if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_ITALIC) + { + is_italic = true; + os << "\\textit{"; + } + if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_OBLIQUE) + { + is_oblique = true; + os << "\\textsl{"; // this is an accurate choice if the LaTeX chosen font matches the font in Inkscape. Gives bad results when it is not so... + } + + Inkscape::Text::Layout::iterator ln = li; + ln.nextStartOfSpan(); + Glib::ustring uspanstr = sp_te_get_string_multiline(flowtext, li, ln); + const gchar *spanstr = uspanstr.c_str(); + if (!spanstr) { + continue; + } + // replace carriage return with double slash + gchar ** splitstr = g_strsplit(spanstr, "\n", -1); + gchar *spanstr_new = g_strjoinv("\\\\ ", splitstr); + os << spanstr_new; + g_strfreev(splitstr); + g_free(spanstr_new); + + if (is_oblique) { os << "}"; } // oblique end + if (is_italic) { os << "}"; } // italic end + if (is_bold) { os << "}"; } // bold end + } + + os << "\\end{minipage}"; + if (has_rotation) { + os << "}"; // rotatebox end + } + os << "}"; //makebox end + os << "}%\n"; // put end + + fprintf(_stream, "%s", os.str().c_str()); +} + +void LaTeXTextRenderer::sp_root_render(SPRoot *root) +{ + push_transform(root->c2p); + sp_group_render(root); + pop_transform(); +} + +void +LaTeXTextRenderer::sp_item_invoke_render(SPItem *item) +{ + // Check item's visibility + if (item->isHidden()) { + return; + } + + SPRoot *root = dynamic_cast<SPRoot *>(item); + if (root) { + sp_root_render(root); + } else { + SPGroup *group = dynamic_cast<SPGroup *>(item); + if (group) { + sp_group_render(group); + } else { + SPUse *use = dynamic_cast<SPUse *>(item); + if (use) { + sp_use_render(use); + } else { + SPText *text = dynamic_cast<SPText *>(item); + if (text) { + sp_text_render(text); + } else { + SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(item); + if (flowtext) { + sp_flowtext_render(flowtext); + } else { + // Only PDFLaTeX supports importing a single page of a graphics file, + // so only PDF backend gets interleaved text/graphics + if (_pdflatex && (_omittext_state == EMPTY || _omittext_state == NEW_PAGE_ON_GRAPHIC)) { + writeGraphicPage(); + } + _omittext_state = GRAPHIC_ON_TOP; + } + } + } + } + } +} + +void +LaTeXTextRenderer::renderItem(SPItem *item) +{ + push_transform(item->transform); + sp_item_invoke_render(item); + pop_transform(); +} + +void +LaTeXTextRenderer::writeGraphicPage() { + Inkscape::SVGOStringStream os; + os.setf(std::ios::fixed); // no scientific notation + + // strip pathname, as it is probably desired. Having a specific path in the TeX file is not convenient. + if (_pdflatex) + os << " \\put(0,0){\\includegraphics[width=\\unitlength,page=" << _omittext_page++ << "]{" << _filename << "}}%\n"; + else + os << " \\put(0,0){\\includegraphics[width=\\unitlength]{" << _filename << "}}%\n"; + + fprintf(_stream, "%s", os.str().c_str()); +} + +bool +LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, float bleedmargin_px, SPItem *base) +{ +// The boundingbox calculation here should be exactly the same as the one by CairoRenderer::setupDocument ! + + if (!base) { + base = doc->getRoot(); + } + + Geom::Rect d; + if (pageBoundingBox) { + d = Geom::Rect::from_xywh(Geom::Point(0,0), doc->getDimensions()); + } else { + Geom::OptRect bbox = base->documentVisualBounds(); + if (!bbox) { + g_message("CairoRenderer: empty bounding box."); + return false; + } + d = *bbox; + } + d.expandBy(bleedmargin_px); + + // scale all coordinates, such that the width of the image is 1, this is convenient for scaling the image in LaTeX + double scale = 1/(d.width()); + double _width = d.width() * scale; + double _height = d.height() * scale; + push_transform(Geom::Translate(-d.corner(3)) * Geom::Scale(scale, -scale)); + + // write the info to LaTeX + Inkscape::SVGOStringStream os; + os.setf(std::ios::fixed); // no scientific notation + + // scaling of the image when including it in LaTeX + os << " \\ifx\\svgwidth\\undefined%\n"; + os << " \\setlength{\\unitlength}{" << Inkscape::Util::Quantity::convert(d.width(), "px", "pt") << "bp}%\n"; // note: 'bp' is the Postscript pt unit in LaTeX, see LP bug #792384 + os << " \\ifx\\svgscale\\undefined%\n"; + os << " \\relax%\n"; + os << " \\else%\n"; + os << " \\setlength{\\unitlength}{\\unitlength * \\real{\\svgscale}}%\n"; + os << " \\fi%\n"; + os << " \\else%\n"; + os << " \\setlength{\\unitlength}{\\svgwidth}%\n"; + os << " \\fi%\n"; + os << " \\global\\let\\svgwidth\\undefined%\n"; + os << " \\global\\let\\svgscale\\undefined%\n"; + os << " \\makeatother%\n"; + + os << " \\begin{picture}(" << _width << "," << _height << ")%\n"; + + // set \baselineskip equal to fontsize (the closest we can seem to get to CSS "line-height: 1;") + // and remove column spacing from tabular + os << " \\lineheight{1}%\n"; + os << " \\setlength\\tabcolsep{0pt}%\n"; + + fprintf(_stream, "%s", os.str().c_str()); + + if (!_pdflatex) + writeGraphicPage(); + + return true; +} + +Geom::Affine const & +LaTeXTextRenderer::transform() +{ + return _transform_stack.top(); +} + +void +LaTeXTextRenderer::push_transform(Geom::Affine const &tr) +{ + if(!_transform_stack.empty()){ + Geom::Affine tr_top = _transform_stack.top(); + _transform_stack.push(tr * tr_top); + } else { + _transform_stack.push(tr); + } +} + +void +LaTeXTextRenderer::pop_transform() +{ + _transform_stack.pop(); +} + +} /* namespace Internal */ +} /* namespace Extension */ +} /* 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/latex-text-renderer.h b/src/extension/internal/latex-text-renderer.h new file mode 100644 index 0000000..a96a263 --- /dev/null +++ b/src/extension/internal/latex-text-renderer.h @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef EXTENSION_INTERNAL_LATEX_TEXT_RENDERER_H_SEEN +#define EXTENSION_INTERNAL_LATEX_TEXT_RENDERER_H_SEEN + +/** \file + * Declaration of LaTeXTextRenderer, used for rendering the accompanying LaTeX file when exporting to PDF/EPS/PS + LaTeX + */ +/* + * Authors: + * Johan Engelen <goejendaagh@zonnet.nl> + * + * Copyright (C) 2010 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "extension/extension.h" +#include <2geom/affine.h> +#include <stack> + +class SPItem; +class SPRoot; +class SPGroup; +class SPUse; +class SPText; +class SPFlowtext; + +namespace Inkscape { +namespace Extension { +namespace Internal { + +bool latex_render_document_text_to_file(SPDocument *doc, gchar const *filename, + const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px, + bool pdflatex); + +class LaTeXTextRenderer { +public: + LaTeXTextRenderer(bool pdflatex); + virtual ~LaTeXTextRenderer(); + + bool setTargetFile(gchar const *filename); + + /** Initializes the LaTeXTextRenderer according to the specified + SPDocument. Important to set the boundingbox to the pdf boundingbox */ + bool setupDocument(SPDocument *doc, bool pageBoundingBox, float bleedmargin_px, SPItem *base); + + /** Traverses the object tree and invokes the render methods. */ + void renderItem(SPItem *item); + +protected: + enum LaTeXOmitTextPageState { + EMPTY, + GRAPHIC_ON_TOP, + NEW_PAGE_ON_GRAPHIC + }; + + FILE * _stream; + gchar * _filename; + + bool _pdflatex; /** true if outputting for pdfLaTeX*/ + + LaTeXOmitTextPageState _omittext_state; + gulong _omittext_page; + + void push_transform(Geom::Affine const &transform); + Geom::Affine const & transform(); + void pop_transform(); + std::stack<Geom::Affine> _transform_stack; + + void writePreamble(); + void writePostamble(); + + void writeGraphicPage(); + + void sp_item_invoke_render(SPItem *item); + void sp_root_render(SPRoot *item); + void sp_group_render(SPGroup *group); + void sp_use_render(SPUse *use); + void sp_text_render(SPText *text); + void sp_flowtext_render(SPFlowtext *flowtext); +}; + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* !EXTENSION_INTERNAL_LATEX_TEXT_RENDERER_H_SEEN */ + +/* + 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/extension/internal/metafile-inout.cpp b/src/extension/internal/metafile-inout.cpp new file mode 100644 index 0000000..e657845 --- /dev/null +++ b/src/extension/internal/metafile-inout.cpp @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Metafile input - common routines + *//* + * Authors: + * David Mathog + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstring> +#include <fstream> +#include <glib.h> +#include <glibmm/miscutils.h> + +#include "display/curve.h" +#include "extension/internal/metafile-inout.h" // picks up PNG +#include "extension/print.h" +#include "path-prefix.h" +#include "document.h" +#include "util/units.h" +#include "ui/shape-editor.h" +#include "document-undo.h" +#include "inkscape.h" +#include "preferences.h" + +#include "object/sp-root.h" +#include "object/sp-namedview.h" +#include "svg/stringstream.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +Metafile::~Metafile() +{ + return; +} + +/** Construct a PNG in memory from an RGB from the EMF file + +from: +http://www.lemoda.net/c/write-png/ + +which was based on: +http://stackoverflow.com/questions/1821806/how-to-encode-png-to-buffer-using-libpng + +gcc -Wall -o testpng testpng.c -lpng + +Originally here, but moved up + +#include <png.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +*/ + + +/* Given "bitmap", this returns the pixel of bitmap at the point + ("x", "y"). */ + +pixel_t * Metafile::pixel_at (bitmap_t * bitmap, int x, int y) +{ + return bitmap->pixels + bitmap->width * y + x; +} + + +/* Write "bitmap" to a PNG file specified by "path"; returns 0 on + success, non-zero on error. */ + +void +Metafile::my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) +{ + PMEMPNG p=(PMEMPNG)png_get_io_ptr(png_ptr); + + size_t nsize = p->size + length; + + /* allocate or grow buffer */ + if(p->buffer){ p->buffer = (char *) realloc(p->buffer, nsize); } + else{ p->buffer = (char *) malloc(nsize); } + + if(!p->buffer){ png_error(png_ptr, "Write Error"); } + + /* copy new bytes to end of buffer */ + memcpy(p->buffer + p->size, data, length); + p->size += length; +} + +void Metafile::toPNG(PMEMPNG accum, int width, int height, const char *px){ + bitmap_t bmStore; + bitmap_t *bitmap = &bmStore; + accum->buffer=nullptr; // PNG constructed in memory will end up here, caller must free(). + accum->size=0; + bitmap->pixels=(pixel_t *)px; + bitmap->width = width; + bitmap->height = height; + + png_structp png_ptr = nullptr; + png_infop info_ptr = nullptr; + size_t x, y; + png_byte ** row_pointers = nullptr; + /* The following number is set by trial and error only. I cannot + see where it it is documented in the libpng manual. + */ + int pixel_size = 3; + int depth = 8; + + png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (png_ptr == nullptr){ + accum->buffer=nullptr; + return; + } + + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == nullptr){ + png_destroy_write_struct (&png_ptr, &info_ptr); + accum->buffer=nullptr; + return; + } + + /* Set up error handling. */ + + if (setjmp (png_jmpbuf (png_ptr))) { + png_destroy_write_struct (&png_ptr, &info_ptr); + accum->buffer=nullptr; + return; + } + + /* Set image attributes. */ + + png_set_IHDR ( + png_ptr, + info_ptr, + bitmap->width, + bitmap->height, + depth, + PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT + ); + + /* Initialize rows of PNG. */ + + row_pointers = (png_byte **) png_malloc (png_ptr, bitmap->height * sizeof (png_byte *)); + for (y = 0; y < bitmap->height; ++y) { + png_byte *row = + (png_byte *) png_malloc (png_ptr, sizeof (uint8_t) * bitmap->width * pixel_size); + row_pointers[bitmap->height - y - 1] = row; // Row order in EMF is reversed. + for (x = 0; x < bitmap->width; ++x) { + pixel_t * pixel = pixel_at (bitmap, x, y); + *row++ = pixel->red; // R & B channels were set correctly by DIB_to_RGB + *row++ = pixel->green; + *row++ = pixel->blue; + } + } + + /* Write the image data to memory */ + + png_set_rows (png_ptr, info_ptr, row_pointers); + + png_set_write_fn(png_ptr, accum, my_png_write_data, nullptr); + + png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr); + + for (y = 0; y < bitmap->height; y++) { + png_free (png_ptr, row_pointers[y]); + } + png_free (png_ptr, row_pointers); + png_destroy_write_struct(&png_ptr, &info_ptr); + +} + +/* If the viewBox is missing, set one +*/ +void Metafile::setViewBoxIfMissing(SPDocument *doc) { + + if (doc && !doc->getRoot()->viewBox_set) { + bool saved = Inkscape::DocumentUndo::getUndoSensitive(doc); + Inkscape::DocumentUndo::setUndoSensitive(doc, false); + + doc->ensureUpToDate(); + + // Set document unit + Inkscape::XML::Node *repr = sp_document_namedview(doc, nullptr)->getRepr(); + Inkscape::SVGOStringStream os; + Inkscape::Util::Unit const* doc_unit = doc->getWidth().unit; + os << doc_unit->abbr; + repr->setAttribute("inkscape:document-units", os.str()); + + // Set viewBox + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc_unit), doc->getHeight().value(doc_unit))); + doc->ensureUpToDate(); + + // Scale and translate objects + double scale = Inkscape::Util::Quantity::convert(1, "px", doc_unit); + Inkscape::UI::ShapeEditor::blockSetItem(true); + double dh; + if(SP_ACTIVE_DOCUMENT){ // for file menu open or import, or paste from clipboard + dh = SP_ACTIVE_DOCUMENT->getHeight().value("px"); + } + else { // for open via --file on command line + dh = doc->getHeight().value("px"); + } + + // These should not affect input, but they do, so set them to a neutral state + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool transform_stroke = prefs->getBool("/options/transform/stroke", true); + bool transform_rectcorners = prefs->getBool("/options/transform/rectcorners", true); + bool transform_pattern = prefs->getBool("/options/transform/pattern", true); + bool transform_gradient = prefs->getBool("/options/transform/gradient", true); + prefs->setBool("/options/transform/stroke", true); + prefs->setBool("/options/transform/rectcorners", true); + prefs->setBool("/options/transform/pattern", true); + prefs->setBool("/options/transform/gradient", true); + + doc->getRoot()->scaleChildItemsRec(Geom::Scale(scale), Geom::Point(0, dh), true); + Inkscape::UI::ShapeEditor::blockSetItem(false); + + // restore options + prefs->setBool("/options/transform/stroke", transform_stroke); + prefs->setBool("/options/transform/rectcorners", transform_rectcorners); + prefs->setBool("/options/transform/pattern", transform_pattern); + prefs->setBool("/options/transform/gradient", transform_gradient); + + Inkscape::DocumentUndo::setUndoSensitive(doc, saved); + } +} + +/** + \fn Convert EMF/WMF region combining ops to livarot region combining ops + \return combination operators in livarot enumeration, or -1 on no match + \param ops (int) combination operator (Inkscape) +*/ +int Metafile::combine_ops_to_livarot(const int op) +{ + int ret = -1; + switch(op) { + case U_RGN_AND: + ret = bool_op_inters; + break; + case U_RGN_OR: + ret = bool_op_union; + break; + case U_RGN_XOR: + ret = bool_op_symdiff; + break; + case U_RGN_DIFF: + ret = bool_op_diff; + break; + } + return(ret); +} + + + +/* convert an EMF RGB(A) color to 0RGB +inverse of gethexcolor() in emf-print.cpp +*/ +uint32_t Metafile::sethexcolor(U_COLORREF color){ + + uint32_t out; + out = (U_RGBAGetR(color) << 16) + + (U_RGBAGetG(color) << 8 ) + + (U_RGBAGetB(color) ); + return(out); +} + +/* Return the base64 encoded png which is shown for all bad images. +Currently a random 3x4 blotch. +Caller must free. +*/ +gchar *Metafile::bad_image_png(){ + gchar *gstring = g_strdup("iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAA3NCSVQICAjb4U/gAAAALElEQVQImQXBQQ2AMAAAsUJQMSWI2H8qME1yMshojwrvGB8XcHKvR1XtOTc/8HENumHCsOMAAAAASUVORK5CYII="); + return(gstring); +} + + + +} // namespace Internal +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/metafile-inout.h b/src/extension/internal/metafile-inout.h new file mode 100644 index 0000000..c742a64 --- /dev/null +++ b/src/extension/internal/metafile-inout.h @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Metafile input - common functions + *//* + * Authors: + * David Mathog + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_H + +#define PNG_SKIP_SETJMP_CHECK // else any further png.h include blows up in the compiler +#include <png.h> +#include <cstdio> +#include <cstdlib> +#include <cstdint> +#include <map> +#include <stack> +#include <glibmm/ustring.h> +#include <3rdparty/libuemf/uemf.h> +#include <2geom/affine.h> +#include <2geom/pathvector.h> + +#include "extension/implementation/implementation.h" + +class SPObject; + +namespace Inkscape { +class Pixbuf; + +namespace Extension { +namespace Internal { + +/* A coloured pixel. */ +struct pixel_t { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t opacity; +}; + +/* A picture. */ +struct bitmap_t { + pixel_t *pixels; + size_t width; + size_t height; +}; + +/* structure to store PNG image bytes */ +struct MEMPNG { + char *buffer; + size_t size; +}; +using PMEMPNG = MEMPNG *; + +class Metafile + : public Inkscape::Extension::Implementation::Implementation +{ +public: + Metafile() = default; + ~Metafile() override; + +protected: + static uint32_t sethexcolor(U_COLORREF color); + static pixel_t *pixel_at (bitmap_t * bitmap, int x, int y); + static void my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length); + static void toPNG(PMEMPNG accum, int width, int height, const char *px); + static gchar *bad_image_png(); + static void setViewBoxIfMissing(SPDocument *doc); + static int combine_ops_to_livarot(const int op); + + +private: +}; + +} // namespace Internal +} // namespace Extension +} // namespace Inkscape + +#endif // SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_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: +*/ diff --git a/src/extension/internal/metafile-print.cpp b/src/extension/internal/metafile-print.cpp new file mode 100644 index 0000000..0935d3d --- /dev/null +++ b/src/extension/internal/metafile-print.cpp @@ -0,0 +1,464 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Metafile printing - common routines + *//* + * Authors: + * Krzysztof KosiÅ„ski <tweenk.pl@gmail.com> + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstring> +#include <fstream> +#include <glib.h> +#include <glibmm/miscutils.h> +#include <2geom/rect.h> +#include <2geom/curves.h> +#include <2geom/svg-path-parser.h> + +#include "extension/internal/metafile-print.h" +#include "extension/print.h" +#include "path-prefix.h" +#include "object/sp-gradient.h" +#include "object/sp-image.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-pattern.h" +#include "object/sp-radial-gradient.h" +#include "style.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +PrintMetafile::~PrintMetafile() +{ +#ifndef G_OS_WIN32 + // restore default signal handling for SIGPIPE + (void) signal(SIGPIPE, SIG_DFL); +#endif + return; +} + +static std::map<Glib::ustring, FontfixParams> _ppt_fixable_fonts = { + {{"Arial"}, { 0.05, -0.055, -0.065}}, + {{"Times New Roman"}, { 0.05, -0.055, -0.065}}, + {{"Lucida Sans"}, {-0.025, -0.055, -0.065}}, + {{"Sans"}, { 0.05, -0.055, -0.065}}, + {{"Microsoft Sans Serif"}, {-0.05, -0.055, -0.065}}, + {{"Serif"}, { 0.05, -0.055, -0.065}}, + {{"Garamond"}, { 0.05, -0.055, -0.065}}, + {{"Century Schoolbook"}, { 0.25, 0.025, 0.025}}, + {{"Verdana"}, { 0.025, 0.0, 0.0}}, + {{"Tahoma"}, { 0.045, 0.025, 0.025}}, + {{"Symbol"}, { 0.025, 0.0, 0.0}}, + {{"Wingdings"}, { 0.05, 0.0, 0.0}}, + {{"Zapf Dingbats"}, { 0.025, 0.0, 0.0}}, + {{"Convert To Symbol"}, { 0.025, 0.0, 0.0}}, + {{"Convert To Wingdings"}, { 0.05, 0.0, 0.0}}, + {{"Convert To Zapf Dingbats"}, { 0.025, 0.0, 0.0}}, + {{"Sylfaen"}, { 0.1, 0.0, 0.0}}, + {{"Palatino Linotype"}, { 0.175, 0.125, 0.125}}, + {{"Segoe UI"}, { 0.1, 0.0, 0.0}}, +}; + +bool PrintMetafile::textToPath(Inkscape::Extension::Print *ext) +{ + return ext->get_param_bool("textToPath"); +} + +unsigned int PrintMetafile::bind(Inkscape::Extension::Print * /*mod*/, Geom::Affine const &transform, float /*opacity*/) +{ + if (!m_tr_stack.empty()) { + Geom::Affine tr_top = m_tr_stack.top(); + m_tr_stack.push(transform * tr_top); + } else { + m_tr_stack.push(transform); + } + + return 1; +} + +unsigned int PrintMetafile::release(Inkscape::Extension::Print * /*mod*/) +{ + m_tr_stack.pop(); + return 1; +} + +// Finds font fix parameters for the given fontname. +void PrintMetafile::_lookup_ppt_fontfix(Glib::ustring const &fontname, FontfixParams ¶ms) +{ + auto it = _ppt_fixable_fonts.find(fontname); + if (it!=_ppt_fixable_fonts.end()) { + params = it->second; + } +} + +U_COLORREF PrintMetafile::_gethexcolor(uint32_t color) +{ + U_COLORREF out; + out = U_RGB( + (color >> 16) & 0xFF, + (color >> 8) & 0xFF, + (color >> 0) & 0xFF + ); + return out; +} + +// Translate Inkscape weights to EMF weights. +uint32_t PrintMetafile::_translate_weight(unsigned inkweight) +{ + switch (inkweight) { + // 400 is tested first, as it is the most common case + case SP_CSS_FONT_WEIGHT_400: return U_FW_NORMAL; + case SP_CSS_FONT_WEIGHT_100: return U_FW_THIN; + case SP_CSS_FONT_WEIGHT_200: return U_FW_EXTRALIGHT; + case SP_CSS_FONT_WEIGHT_300: return U_FW_LIGHT; + case SP_CSS_FONT_WEIGHT_500: return U_FW_MEDIUM; + case SP_CSS_FONT_WEIGHT_600: return U_FW_SEMIBOLD; + case SP_CSS_FONT_WEIGHT_700: return U_FW_BOLD; + case SP_CSS_FONT_WEIGHT_800: return U_FW_EXTRABOLD; + case SP_CSS_FONT_WEIGHT_900: return U_FW_HEAVY; + default: return U_FW_NORMAL; + } +} + +/* opacity weighting of two colors as float. v1 is the color, op is its opacity, v2 is the background color */ +inline float opweight(float v1, float v2, float op) +{ + return v1 * op + v2 * (1.0 - op); +} + +U_COLORREF PrintMetafile::avg_stop_color(SPGradient *gr) +{ + U_COLORREF cr; + int last = gr->vector.stops.size() - 1; + if (last >= 1) { + float rgbs[3]; + float rgbe[3]; + float ops, ope; + + ops = gr->vector.stops[0 ].opacity; + ope = gr->vector.stops[last].opacity; + gr->vector.stops[0 ].color.get_rgb_floatv(rgbs); + gr->vector.stops[last].color.get_rgb_floatv(rgbe); + + /* Replace opacity at start & stop with that fraction background color, then average those two for final color. */ + cr = U_RGB( + 255 * ((opweight(rgbs[0], gv.rgb[0], ops) + opweight(rgbe[0], gv.rgb[0], ope)) / 2.0), + 255 * ((opweight(rgbs[1], gv.rgb[1], ops) + opweight(rgbe[1], gv.rgb[1], ope)) / 2.0), + 255 * ((opweight(rgbs[2], gv.rgb[2], ops) + opweight(rgbe[2], gv.rgb[2], ope)) / 2.0) + ); + } else { + cr = U_RGB(0, 0, 0); // The default fill + } + return cr; +} + +U_COLORREF PrintMetafile::weight_opacity(U_COLORREF c1) +{ + float opa = c1.Reserved / 255.0; + U_COLORREF result = U_RGB( + 255 * opweight((float)c1.Red / 255.0, gv.rgb[0], opa), + 255 * opweight((float)c1.Green / 255.0, gv.rgb[1], opa), + 255 * opweight((float)c1.Blue / 255.0, gv.rgb[2], opa) + ); + return result; +} + +/* t between 0 and 1, values outside that range use the nearest limit */ +U_COLORREF PrintMetafile::weight_colors(U_COLORREF c1, U_COLORREF c2, double t) +{ +#define clrweight(a,b,t) ((1-t)*((double) a) + (t)*((double) b)) + U_COLORREF result; + t = ( t > 1.0 ? 1.0 : ( t < 0.0 ? 0.0 : t)); + result.Red = clrweight(c1.Red, c2.Red, t); + result.Green = clrweight(c1.Green, c2.Green, t); + result.Blue = clrweight(c1.Blue, c2.Blue, t); + result.Reserved = clrweight(c1.Reserved, c2.Reserved, t); + + // now handle the opacity, mix the RGB with background at the weighted opacity + + if (result.Reserved != 255) { + result = weight_opacity(result); + } + + return result; +} + +// Extract hatchType, hatchColor from a name like +// EMFhatch<hatchType>_<hatchColor> +// Where the first one is a number and the second a color in hex. +// hatchType and hatchColor have been set with defaults before this is called. +// +void PrintMetafile::hatch_classify(char *name, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor) +{ + int val; + uint32_t hcolor = 0; + uint32_t bcolor = 0; + + // name should be EMFhatch or WMFhatch but *MFhatch will be accepted + if (0 != strncmp(&name[1], "MFhatch", 7)) { + return; // not anything we can parse + } + name += 8; // EMFhatch already detected + val = 0; + while (*name && isdigit(*name)) { + val = 10 * val + *name - '0'; + name++; + } + *hatchType = val; + if (*name != '_' || val > U_HS_DITHEREDBKCLR) { // wrong syntax, cannot classify + *hatchType = -1; + } else { + name++; + if (2 != sscanf(name, "%X_%X", &hcolor, &bcolor)) { // not a pattern with background + if (1 != sscanf(name, "%X", &hcolor)) { + *hatchType = -1; // not a pattern, cannot classify + } + *hatchColor = _gethexcolor(hcolor); + } else { + *hatchColor = _gethexcolor(hcolor); + *bkColor = _gethexcolor(bcolor); + usebk = true; + } + } + /* Everything > U_HS_SOLIDCLR is solid, just specify the color in the brush rather than messing around with background or textcolor */ + if (*hatchType > U_HS_SOLIDCLR) { + *hatchType = U_HS_SOLIDCLR; + } +} + +// +// Recurse down from a brush pattern, try to figure out what it is. +// If an image is found set a pointer to the epixbuf, else set that to NULL +// If a pattern is found with a name like [EW]MFhatch3_3F7FFF return hatchType=3, hatchColor=3F7FFF (as a uint32_t), +// otherwise hatchType is set to -1 and hatchColor is not defined. +// + +void PrintMetafile::brush_classify(SPObject *parent, int depth, Inkscape::Pixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor) +{ + if (depth == 0) { + *epixbuf = nullptr; + *hatchType = -1; + *hatchColor = U_RGB(0, 0, 0); + *bkColor = U_RGB(255, 255, 255); + } + depth++; + // first look along the pattern chain, if there is one + if (SP_IS_PATTERN(parent)) { + for (SPPattern *pat_i = SP_PATTERN(parent); pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) { + if (SP_IS_IMAGE(pat_i)) { + *epixbuf = ((SPImage *)pat_i)->pixbuf; + return; + } + char temp[32]; // large enough + strncpy(temp, pat_i->getAttribute("id"), sizeof(temp)-1); // Some names may be longer than [EW]MFhatch#_###### + temp[sizeof(temp)-1] = '\0'; + hatch_classify(temp, hatchType, hatchColor, bkColor); + if (*hatchType != -1) { + return; + } + + // still looking? Look at this pattern's children, if there are any + for (auto& child: pat_i->children) { + if (*epixbuf || *hatchType != -1) { + break; + } + brush_classify(&child, depth, epixbuf, hatchType, hatchColor, bkColor); + } + } + } else if (SP_IS_IMAGE(parent)) { + *epixbuf = ((SPImage *)parent)->pixbuf; + return; + } else { // some inkscape rearrangements pass through nodes between pattern and image which are not classified as either. + for (auto& child: parent->children) { + if (*epixbuf || *hatchType != -1) { + break; + } + brush_classify(&child, depth, epixbuf, hatchType, hatchColor, bkColor); + } + } +} + +//swap R/B in 4 byte pixel +void PrintMetafile::swapRBinRGBA(char *px, int pixels) +{ + char tmp; + for (int i = 0; i < pixels * 4; px += 4, i += 4) { + tmp = px[2]; + px[2] = px[0]; + px[0] = tmp; + } +} + +int PrintMetafile::hold_gradient(void *gr, int mode) +{ + gv.mode = mode; + gv.grad = gr; + if (mode == DRAW_RADIAL_GRADIENT) { + SPRadialGradient *rg = (SPRadialGradient *) gr; + gv.r = rg->r.computed; // radius, but of what??? + gv.p1 = Geom::Point(rg->cx.computed, rg->cy.computed); // center + gv.p2 = Geom::Point(gv.r, 0) + gv.p1; // xhandle + gv.p3 = Geom::Point(0, -gv.r) + gv.p1; // yhandle + if (rg->gradientTransform_set) { + gv.p1 = gv.p1 * rg->gradientTransform; + gv.p2 = gv.p2 * rg->gradientTransform; + gv.p3 = gv.p3 * rg->gradientTransform; + } + } else if (mode == DRAW_LINEAR_GRADIENT) { + SPLinearGradient *lg = (SPLinearGradient *) gr; + gv.r = 0; // unused + gv.p1 = Geom::Point(lg->x1.computed, lg->y1.computed); // start + gv.p2 = Geom::Point(lg->x2.computed, lg->y2.computed); // end + gv.p3 = Geom::Point(0, 0); // unused + if (lg->gradientTransform_set) { + gv.p1 = gv.p1 * lg->gradientTransform; + gv.p2 = gv.p2 * lg->gradientTransform; + } + } else { + g_error("Fatal programming error, hold_gradient() in metafile-print.cpp called with invalid draw mode"); + } + return 1; +} + +/* convert from center ellipse to SVGEllipticalArc ellipse + + From: + http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter + A point (x,y) on the arc can be found by: + + {x,y} = {cx,cy} + {cosF,-sinF,sinF,cosF} x {rxcosT,rysinT} + + where + {cx,cy} is the center of the ellipse + F is the rotation angle of the X axis of the ellipse from the true X axis + T is the rotation angle around the ellipse + {,,,} is the rotation matrix + rx,ry are the radii of the ellipse's axes + + For SVG parameterization need two points. + Arbitrarily we can use T=0 and T=pi + Since the sweep is 180 the flags are always 0: + + F is in RADIANS, but the SVGEllipticalArc needs degrees! + +*/ +Geom::PathVector PrintMetafile::center_ellipse_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F) +{ + using Geom::X; + using Geom::Y; + double x1, y1, x2, y2; + Geom::Path SVGep; + + x1 = ctr[X] + cos(F) * rx * cos(0) + sin(-F) * ry * sin(0); + y1 = ctr[Y] + sin(F) * rx * cos(0) + cos(F) * ry * sin(0); + x2 = ctr[X] + cos(F) * rx * cos(M_PI) + sin(-F) * ry * sin(M_PI); + y2 = ctr[Y] + sin(F) * rx * cos(M_PI) + cos(F) * ry * sin(M_PI); + + char text[256]; + snprintf(text, 256, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z", + x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1); + Geom::PathVector outres = Geom::parse_svg_path(text); + return outres; +} + + +/* rx2,ry2 must be larger than rx1,ry1! + angle is in RADIANS +*/ +Geom::PathVector PrintMetafile::center_elliptical_ring_as_SVG_PathV(Geom::Point ctr, double rx1, double ry1, double rx2, double ry2, double F) +{ + using Geom::X; + using Geom::Y; + double x11, y11, x12, y12; + double x21, y21, x22, y22; + double degrot = F * 360. / (2.*M_PI); + + x11 = ctr[X] + cos(F) * rx1 * cos(0) + sin(-F) * ry1 * sin(0); + y11 = ctr[Y] + sin(F) * rx1 * cos(0) + cos(F) * ry1 * sin(0); + x12 = ctr[X] + cos(F) * rx1 * cos(M_PI) + sin(-F) * ry1 * sin(M_PI); + y12 = ctr[Y] + sin(F) * rx1 * cos(M_PI) + cos(F) * ry1 * sin(M_PI); + + x21 = ctr[X] + cos(F) * rx2 * cos(0) + sin(-F) * ry2 * sin(0); + y21 = ctr[Y] + sin(F) * rx2 * cos(0) + cos(F) * ry2 * sin(0); + x22 = ctr[X] + cos(F) * rx2 * cos(M_PI) + sin(-F) * ry2 * sin(M_PI); + y22 = ctr[Y] + sin(F) * rx2 * cos(M_PI) + cos(F) * ry2 * sin(M_PI); + + char text[512]; + snprintf(text, 512, " M %f,%f A %f %f %f 0 1 %f %f A %f %f %f 0 1 %f %f z M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z", + x11, y11, rx1, ry1, degrot, x12, y12, rx1, ry1, degrot, x11, y11, + x21, y21, rx2, ry2, degrot, x22, y22, rx2, ry2, degrot, x21, y21); + Geom::PathVector outres = Geom::parse_svg_path(text); + + return outres; +} + +/* Elliptical hole in a large square extending from -50k to +50k */ +Geom::PathVector PrintMetafile::center_elliptical_hole_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F) +{ + using Geom::X; + using Geom::Y; + double x1, y1, x2, y2; + Geom::Path SVGep; + + x1 = ctr[X] + cos(F) * rx * cos(0) + sin(-F) * ry * sin(0); + y1 = ctr[Y] + sin(F) * rx * cos(0) + cos(F) * ry * sin(0); + x2 = ctr[X] + cos(F) * rx * cos(M_PI) + sin(-F) * ry * sin(M_PI); + y2 = ctr[Y] + sin(F) * rx * cos(M_PI) + cos(F) * ry * sin(M_PI); + + char text[256]; + snprintf(text, 256, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z M 50000,50000 50000,-50000 -50000,-50000 -50000,50000 z", + x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1); + Geom::PathVector outres = Geom::parse_svg_path(text); + return outres; +} + +/* rectangular cutter. +ctr "center" of rectangle (might not actually be in the center with respect to leading/trailing edges +pos vector from center to leading edge +neg vector from center to trailing edge +width vector to side edge +*/ +Geom::PathVector PrintMetafile::rect_cutter(Geom::Point ctr, Geom::Point pos, Geom::Point neg, Geom::Point width) +{ + Geom::PathVector outres; + Geom::Path cutter; + cutter.start(ctr + pos - width); + cutter.appendNew<Geom::LineSegment>(ctr + pos + width); + cutter.appendNew<Geom::LineSegment>(ctr + neg + width); + cutter.appendNew<Geom::LineSegment>(ctr + neg - width); + cutter.close(); + outres.push_back(cutter); + return outres; +} + +/* Convert from SPWindRule to livarot's FillRule + This is similar to what sp_selected_path_boolop() does +*/ +FillRule PrintMetafile::SPWR_to_LVFR(SPWindRule wr) +{ + FillRule fr; + if (wr == SP_WIND_RULE_EVENODD) { + fr = fill_oddEven; + } else { + fr = fill_nonZero; + } + return fr; +} + +} // namespace Internal +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/metafile-print.h b/src/extension/internal/metafile-print.h new file mode 100644 index 0000000..3aeb0a0 --- /dev/null +++ b/src/extension/internal/metafile-print.h @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Metafile printing - common functions + *//* + * Authors: + * Krzysztof KosiÅ„ski <tweenk.pl@gmail.com> + * + * Copyright (C) 2013 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_PRINT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_PRINT_H + +#include <map> +#include <stack> +#include <glibmm/ustring.h> +#include <3rdparty/libuemf/uemf.h> +#include <2geom/affine.h> +#include <2geom/pathvector.h> + +#include "extension/implementation/implementation.h" +#include "splivarot.h" +#include "display/canvas-bpath.h" + +class SPGradient; +class SPObject; + +namespace Inkscape { +class Pixbuf; + +namespace Extension { +namespace Internal { + +enum MFDrawMode {DRAW_PAINT, DRAW_PATTERN, DRAW_IMAGE, DRAW_LINEAR_GRADIENT, DRAW_RADIAL_GRADIENT}; + +struct FontfixParams { + double f1; //Vertical (rotating) offset factor (* font height) + double f2; //Vertical (nonrotating) offset factor (* font height) + double f3; //Horizontal (nonrotating) offset factor (* font height) +}; + +class PrintMetafile + : public Inkscape::Extension::Implementation::Implementation +{ +public: + PrintMetafile() = default; + ~PrintMetafile() override; + + bool textToPath (Inkscape::Extension::Print * ext) override; + unsigned int bind(Inkscape::Extension::Print *module, Geom::Affine const &transform, float opacity) override; + unsigned int release(Inkscape::Extension::Print *module) override; + +protected: + struct GRADVALUES { + Geom::Point p1; // center or start + Geom::Point p2; // xhandle or end + Geom::Point p3; // yhandle or unused + double r; // radius or unused + void *grad; // to access the stops information + int mode; // DRAW_LINEAR_GRADIENT or DRAW_RADIAL_GRADIENT, if GRADVALUES is valid, else any value + U_COLORREF bgc; // document background color, this is as good a place as any to keep it + float rgb[3]; // also background color, but as 0-1 float. + }; + + double _width; + double _height; + double _doc_unit_scale; // to pixels, regardless of the document units + + U_RECTL rc; + + uint32_t htextalignment; + uint32_t hpolyfillmode; // used to minimize redundant records that set this + float htextcolor_rgb[3]; // used to minimize redundant records that set this + + std::stack<Geom::Affine> m_tr_stack; + Geom::PathVector fill_pathv; + Geom::Affine fill_transform; + bool use_stroke; + bool use_fill; + bool simple_shape; + bool usebk; + + GRADVALUES gv; + + static void _lookup_ppt_fontfix(Glib::ustring const &fontname, FontfixParams &); + static U_COLORREF _gethexcolor(uint32_t color); + static uint32_t _translate_weight(unsigned inkweight); + + U_COLORREF avg_stop_color(SPGradient *gr); + U_COLORREF weight_opacity(U_COLORREF c1); + U_COLORREF weight_colors(U_COLORREF c1, U_COLORREF c2, double t); + + void hatch_classify(char *name, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor); + void brush_classify(SPObject *parent, int depth, Inkscape::Pixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor); + static void swapRBinRGBA(char *px, int pixels); + + int hold_gradient(void *gr, int mode); + static int snprintf_dots(char * s, size_t n, const char * format, ...); + static Geom::PathVector center_ellipse_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F); + static Geom::PathVector center_elliptical_ring_as_SVG_PathV(Geom::Point ctr, double rx1, double ry1, double rx2, double ry2, double F); + static Geom::PathVector center_elliptical_hole_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F); + static Geom::PathVector rect_cutter(Geom::Point ctr, Geom::Point pos, Geom::Point neg, Geom::Point width); + static FillRule SPWR_to_LVFR(SPWindRule wr); + + virtual int create_brush(SPStyle const *style, PU_COLORREF fcolor) = 0; + virtual void destroy_brush() = 0; + virtual int create_pen(SPStyle const *style, const Geom::Affine &transform) = 0; + virtual void destroy_pen() = 0; +}; + +} // namespace Internal +} // namespace Extension +} // namespace Inkscape + +#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/extension/internal/odf.cpp b/src/extension/internal/odf.cpp new file mode 100644 index 0000000..8d77345 --- /dev/null +++ b/src/extension/internal/odf.cpp @@ -0,0 +1,2131 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/** @file + * OpenDocument (drawing) input and output + *//* + * Authors: + * Bob Jamison + * Abhishek Sharma + * Kris De Gussem + * + * Copyright (C) 2018 Authors + * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information. + */ +/* + * This is an an entry in the extensions mechanism to begin to enable + * the inputting and outputting of OpenDocument Format (ODF) files from + * within Inkscape. Although the initial implementations will be very lossy + * due to the differences in the models of SVG and ODF, they will hopefully + * improve greatly with time. People should consider this to be a framework + * that can be continuously upgraded for ever improving fidelity. Potential + * developers should especially look in preprocess() and writeTree() to see how + * the SVG tree is scanned, read, translated, and then written to ODF. + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + * + */ + +#include "odf.h" + +//# System includes +#include <cstdio> +#include <ctime> +#include <vector> +#include <cmath> + +//# Inkscape includes +#include "clear-n_.h" +#include "inkscape.h" +#include "display/curve.h" +#include <2geom/pathvector.h> +#include <2geom/curves.h> +#include <2geom/transforms.h> +#include <helper/geom.h> +#include "helper/geom-curves.h" +#include "extension/system.h" + +#include "xml/repr.h" +#include "xml/attribute-record.h" +#include "object/sp-image.h" +#include "object/sp-gradient.h" +#include "object/sp-stop.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-root.h" +#include "object/sp-path.h" +#include "object/sp-text.h" +#include "object/sp-flowtext.h" +#include "object/uri.h" +#include "style.h" + +#include "svg/svg.h" +#include "text-editing.h" +#include "util/units.h" + + +#include "inkscape-version.h" +#include "document.h" +#include "extension/extension.h" + +#include "io/stream/bufferstream.h" +#include "io/stream/stringstream.h" +#include "io/sys.h" +#include <util/ziptool.h> +#include <iomanip> +namespace Inkscape +{ +namespace Extension +{ +namespace Internal +{ +//# Shorthand notation +typedef Inkscape::IO::BufferOutputStream BufferOutputStream; +typedef Inkscape::IO::OutputStreamWriter OutputStreamWriter; +typedef Inkscape::IO::StringOutputStream StringOutputStream; + + +//######################################################################## +//# C L A S S SingularValueDecomposition +//######################################################################## +#include <cmath> + +class SVDMatrix +{ +public: + + SVDMatrix() + { + init(); + } + + SVDMatrix(unsigned int rowSize, unsigned int colSize) + { + init(); + rows = rowSize; + cols = colSize; + size = rows * cols; + d = new double[size]; + for (unsigned int i=0 ; i<size ; i++) + d[i] = 0.0; + } + + SVDMatrix(double *vals, unsigned int rowSize, unsigned int colSize) + { + init(); + rows = rowSize; + cols = colSize; + size = rows * cols; + d = new double[size]; + for (unsigned int i=0 ; i<size ; i++) + d[i] = vals[i]; + } + + + SVDMatrix(const SVDMatrix &other) + { + init(); + assign(other); + } + + SVDMatrix &operator=(const SVDMatrix &other) + { + assign(other); + return *this; + } + + virtual ~SVDMatrix() + { + delete[] d; + } + + double& operator() (unsigned int row, unsigned int col) + { + if (row >= rows || col >= cols) + return badval; + return d[cols*row + col]; + } + + double operator() (unsigned int row, unsigned int col) const + { + if (row >= rows || col >= cols) + return badval; + return d[cols*row + col]; + } + + unsigned int getRows() + { + return rows; + } + + unsigned int getCols() + { + return cols; + } + + SVDMatrix multiply(const SVDMatrix &other) + { + if (cols != other.rows) + { + SVDMatrix dummy; + return dummy; + } + SVDMatrix result(rows, other.cols); + for (unsigned int i=0 ; i<rows ; i++) + { + for (unsigned int j=0 ; j<other.cols ; j++) + { + double sum = 0.0; + for (unsigned int k=0 ; k<cols ; k++) + { + //sum += a[i][k] * b[k][j]; + sum += d[i*cols +k] * other(k, j); + } + result(i, j) = sum; + } + + } + return result; + } + + SVDMatrix transpose() + { + SVDMatrix result(cols, rows); + for (unsigned int i=0 ; i<rows ; i++){ + for (unsigned int j=0 ; j<cols ; j++){ + result(j, i) = d[i*cols + j]; + } + } + return result; + } + +private: + + + virtual void init() + { + badval = 0.0; + d = nullptr; + rows = 0; + cols = 0; + size = 0; + } + + void assign(const SVDMatrix &other) + { + if (d) + { + delete[] d; + d = nullptr; + } + rows = other.rows; + cols = other.cols; + size = other.size; + badval = other.badval; + d = new double[size]; + for (unsigned int i=0 ; i<size ; i++){ + d[i] = other.d[i]; + } + } + + double badval; + + double *d; + unsigned int rows; + unsigned int cols; + unsigned int size; +}; + + + +/** + * + * ==================================================== + * + * NOTE: + * This class is ported almost verbatim from the public domain + * JAMA Matrix package. It is modified to handle only 3x3 matrices + * and our Geom::Affine affine transform class. We give full + * attribution to them, along with many thanks. JAMA can be found at: + * http://math.nist.gov/javanumerics/jama + * + * ==================================================== + * + * Singular Value Decomposition. + * <P> + * For an m-by-n matrix A with m >= n, the singular value decomposition is + * an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and + * an n-by-n orthogonal matrix V so that A = U*S*V'. + * <P> + * The singular values, sigma[k] = S[k][k], are ordered so that + * sigma[0] >= sigma[1] >= ... >= sigma[n-1]. + * <P> + * The singular value decomposition always exists, so the constructor will + * never fail. The matrix condition number and the effective numerical + * rank can be computed from this decomposition. + */ +class SingularValueDecomposition +{ +public: + + /** Construct the singular value decomposition + @param A Rectangular matrix + @return Structure to access U, S and V. + */ + + SingularValueDecomposition (const SVDMatrix &mat) : + A (mat), + U (), + s (nullptr), + s_size (0), + V () + { + calculate(); + } + + virtual ~SingularValueDecomposition() + { + delete[] s; + } + + /** + * Return the left singular vectors + * @return U + */ + SVDMatrix &getU(); + + /** + * Return the right singular vectors + * @return V + */ + SVDMatrix &getV(); + + /** + * Return the s[index] value + */ double getS(unsigned int index); + + /** + * Two norm + * @return max(S) + */ + double norm2(); + + /** + * Two norm condition number + * @return max(S)/min(S) + */ + double cond(); + + /** + * Effective numerical matrix rank + * @return Number of nonnegligible singular values. + */ + int rank(); + +private: + + void calculate(); + + SVDMatrix A; + SVDMatrix U; + double *s; + unsigned int s_size; + SVDMatrix V; + +}; + + +static double svd_hypot(double a, double b) +{ + double r; + + if (fabs(a) > fabs(b)) + { + r = b/a; + r = fabs(a) * sqrt(1+r*r); + } + else if (b != 0) + { + r = a/b; + r = fabs(b) * sqrt(1+r*r); + } + else + { + r = 0.0; + } + return r; +} + + + +void SingularValueDecomposition::calculate() +{ + // Initialize. + int m = A.getRows(); + int n = A.getCols(); + + int nu = (m > n) ? m : n; + s_size = (m+1 < n) ? m+1 : n; + s = new double[s_size]; + U = SVDMatrix(m, nu); + V = SVDMatrix(n, n); + double *e = new double[n]; + double *work = new double[m]; + bool wantu = true; + bool wantv = true; + + // Reduce A to bidiagonal form, storing the diagonal elements + // in s and the super-diagonal elements in e. + + int nct = (m-1<n) ? m-1 : n; + int nrtx = (n-2<m) ? n-2 : m; + int nrt = (nrtx>0) ? nrtx : 0; + for (int k = 0; k < 2; k++) { + if (k < nct) { + + // Compute the transformation for the k-th column and + // place the k-th diagonal in s[k]. + // Compute 2-norm of k-th column without under/overflow. + s[k] = 0; + for (int i = k; i < m; i++) { + s[k] = svd_hypot(s[k],A(i, k)); + } + if (s[k] != 0.0) { + if (A(k, k) < 0.0) { + s[k] = -s[k]; + } + for (int i = k; i < m; i++) { + A(i, k) /= s[k]; + } + A(k, k) += 1.0; + } + s[k] = -s[k]; + } + for (int j = k+1; j < n; j++) { + if ((k < nct) & (s[k] != 0.0)) { + + // Apply the transformation. + + double t = 0; + for (int i = k; i < m; i++) { + t += A(i, k) * A(i, j); + } + t = -t/A(k, k); + for (int i = k; i < m; i++) { + A(i, j) += t*A(i, k); + } + } + + // Place the k-th row of A into e for the + // subsequent calculation of the row transformation. + + e[j] = A(k, j); + } + if (wantu & (k < nct)) { + + // Place the transformation in U for subsequent back + // multiplication. + + for (int i = k; i < m; i++) { + U(i, k) = A(i, k); + } + } + if (k < nrt) { + + // Compute the k-th row transformation and place the + // k-th super-diagonal in e[k]. + // Compute 2-norm without under/overflow. + e[k] = 0; + for (int i = k+1; i < n; i++) { + e[k] = svd_hypot(e[k],e[i]); + } + if (e[k] != 0.0) { + if (e[k+1] < 0.0) { + e[k] = -e[k]; + } + for (int i = k+1; i < n; i++) { + e[i] /= e[k]; + } + e[k+1] += 1.0; + } + e[k] = -e[k]; + if ((k+1 < m) & (e[k] != 0.0)) { + + // Apply the transformation. + + for (int i = k+1; i < m; i++) { + work[i] = 0.0; + } + for (int j = k+1; j < n; j++) { + for (int i = k+1; i < m; i++) { + work[i] += e[j]*A(i, j); + } + } + for (int j = k+1; j < n; j++) { + double t = -e[j]/e[k+1]; + for (int i = k+1; i < m; i++) { + A(i, j) += t*work[i]; + } + } + } + if (wantv) { + + // Place the transformation in V for subsequent + // back multiplication. + + for (int i = k+1; i < n; i++) { + V(i, k) = e[i]; + } + } + } + } + + // Set up the final bidiagonal matrix or order p. + + int p = (n < m+1) ? n : m+1; + if (nct < n) { + s[nct] = A(nct, nct); + } + if (m < p) { + s[p-1] = 0.0; + } + if (nrt+1 < p) { + e[nrt] = A(nrt, p-1); + } + e[p-1] = 0.0; + + // If required, generate U. + + if (wantu) { + for (int j = nct; j < nu; j++) { + for (int i = 0; i < m; i++) { + U(i, j) = 0.0; + } + U(j, j) = 1.0; + } + for (int k = nct-1; k >= 0; k--) { + if (s[k] != 0.0) { + for (int j = k+1; j < nu; j++) { + double t = 0; + for (int i = k; i < m; i++) { + t += U(i, k)*U(i, j); + } + t = -t/U(k, k); + for (int i = k; i < m; i++) { + U(i, j) += t*U(i, k); + } + } + for (int i = k; i < m; i++ ) { + U(i, k) = -U(i, k); + } + U(k, k) = 1.0 + U(k, k); + for (int i = 0; i < k-1; i++) { + U(i, k) = 0.0; + } + } else { + for (int i = 0; i < m; i++) { + U(i, k) = 0.0; + } + U(k, k) = 1.0; + } + } + } + + // If required, generate V. + + if (wantv) { + for (int k = n-1; k >= 0; k--) { + if ((k < nrt) & (e[k] != 0.0)) { + for (int j = k+1; j < nu; j++) { + double t = 0; + for (int i = k+1; i < n; i++) { + t += V(i, k)*V(i, j); + } + t = -t/V(k+1, k); + for (int i = k+1; i < n; i++) { + V(i, j) += t*V(i, k); + } + } + } + for (int i = 0; i < n; i++) { + V(i, k) = 0.0; + } + V(k, k) = 1.0; + } + } + + // Main iteration loop for the singular values. + + int pp = p-1; + //double eps = pow(2.0,-52.0); + //double tiny = pow(2.0,-966.0); + //let's just calculate these now + //a double can be e ± 308.25, so this is safe + double eps = 2.22e-16; + double tiny = 1.6e-291; + while (p > 0) { + int k,kase; + + // Here is where a test for too many iterations would go. + + // This section of the program inspects for + // negligible elements in the s and e arrays. On + // completion the variables kase and k are set as follows. + + // kase = 1 if s(p) and e[k-1] are negligible and k<p + // kase = 2 if s(k) is negligible and k<p + // kase = 3 if e[k-1] is negligible, k<p, and + // s(k), ..., s(p) are not negligible (qr step). + // kase = 4 if e(p-1) is negligible (convergence). + + for (k = p-2; k >= -1; k--) { + if (k == -1) { + break; + } + if (fabs(e[k]) <= + tiny + eps*(fabs(s[k]) + fabs(s[k+1]))) { + e[k] = 0.0; + break; + } + } + if (k == p-2) { + kase = 4; + } else { + int ks; + for (ks = p-1; ks >= k; ks--) { + if (ks == k) { + break; + } + double t = (ks != p ? fabs(e[ks]) : 0.) + + (ks != k+1 ? fabs(e[ks-1]) : 0.); + if (fabs(s[ks]) <= tiny + eps*t) { + s[ks] = 0.0; + break; + } + } + if (ks == k) { + kase = 3; + } else if (ks == p-1) { + kase = 1; + } else { + kase = 2; + k = ks; + } + } + k++; + + // Perform the task indicated by kase. + + switch (kase) { + + // Deflate negligible s(p). + + case 1: { + double f = e[p-2]; + e[p-2] = 0.0; + for (int j = p-2; j >= k; j--) { + double t = svd_hypot(s[j],f); + double cs = s[j]/t; + double sn = f/t; + s[j] = t; + if (j != k) { + f = -sn*e[j-1]; + e[j-1] = cs*e[j-1]; + } + if (wantv) { + for (int i = 0; i < n; i++) { + t = cs*V(i, j) + sn*V(i, p-1); + V(i, p-1) = -sn*V(i, j) + cs*V(i, p-1); + V(i, j) = t; + } + } + } + } + break; + + // Split at negligible s(k). + + case 2: { + double f = e[k-1]; + e[k-1] = 0.0; + for (int j = k; j < p; j++) { + double t = svd_hypot(s[j],f); + double cs = s[j]/t; + double sn = f/t; + s[j] = t; + f = -sn*e[j]; + e[j] = cs*e[j]; + if (wantu) { + for (int i = 0; i < m; i++) { + t = cs*U(i, j) + sn*U(i, k-1); + U(i, k-1) = -sn*U(i, j) + cs*U(i, k-1); + U(i, j) = t; + } + } + } + } + break; + + // Perform one qr step. + + case 3: { + + // Calculate the shift. + + double scale = fabs(s[p-1]); + double d = fabs(s[p-2]); + if (d>scale) scale=d; + d = fabs(e[p-2]); + if (d>scale) scale=d; + d = fabs(s[k]); + if (d>scale) scale=d; + d = fabs(e[k]); + if (d>scale) scale=d; + double sp = s[p-1]/scale; + double spm1 = s[p-2]/scale; + double epm1 = e[p-2]/scale; + double sk = s[k]/scale; + double ek = e[k]/scale; + double b = ((spm1 + sp)*(spm1 - sp) + epm1*epm1)/2.0; + double c = (sp*epm1)*(sp*epm1); + double shift = 0.0; + if ((b != 0.0) | (c != 0.0)) { + shift = sqrt(b*b + c); + if (b < 0.0) { + shift = -shift; + } + shift = c/(b + shift); + } + double f = (sk + sp)*(sk - sp) + shift; + double g = sk*ek; + + // Chase zeros. + + for (int j = k; j < p-1; j++) { + double t = svd_hypot(f,g); + double cs = f/t; + double sn = g/t; + if (j != k) { + e[j-1] = t; + } + f = cs*s[j] + sn*e[j]; + e[j] = cs*e[j] - sn*s[j]; + g = sn*s[j+1]; + s[j+1] = cs*s[j+1]; + if (wantv) { + for (int i = 0; i < n; i++) { + t = cs*V(i, j) + sn*V(i, j+1); + V(i, j+1) = -sn*V(i, j) + cs*V(i, j+1); + V(i, j) = t; + } + } + t = svd_hypot(f,g); + cs = f/t; + sn = g/t; + s[j] = t; + f = cs*e[j] + sn*s[j+1]; + s[j+1] = -sn*e[j] + cs*s[j+1]; + g = sn*e[j+1]; + e[j+1] = cs*e[j+1]; + if (wantu && (j < m-1)) { + for (int i = 0; i < m; i++) { + t = cs*U(i, j) + sn*U(i, j+1); + U(i, j+1) = -sn*U(i, j) + cs*U(i, j+1); + U(i, j) = t; + } + } + } + e[p-2] = f; + } + break; + + // Convergence. + + case 4: { + + // Make the singular values positive. + + if (s[k] <= 0.0) { + s[k] = (s[k] < 0.0 ? -s[k] : 0.0); + if (wantv) { + for (int i = 0; i <= pp; i++) { + V(i, k) = -V(i, k); + } + } + } + + // Order the singular values. + + while (k < pp) { + if (s[k] >= s[k+1]) { + break; + } + double t = s[k]; + s[k] = s[k+1]; + s[k+1] = t; + if (wantv && (k < n-1)) { + for (int i = 0; i < n; i++) { + t = V(i, k+1); V(i, k+1) = V(i, k); V(i, k) = t; + } + } + if (wantu && (k < m-1)) { + for (int i = 0; i < m; i++) { + t = U(i, k+1); U(i, k+1) = U(i, k); U(i, k) = t; + } + } + k++; + } + p--; + } + break; + } + } + + delete [] e; + delete [] work; + +} + + +/** + * Return the left singular vectors + * @return U + */ +SVDMatrix &SingularValueDecomposition::getU() +{ + return U; +} + +/** + * Return the right singular vectors + * @return V + */ + +SVDMatrix &SingularValueDecomposition::getV() +{ + return V; +} + +/** + * Return the s[0] value + */ +double SingularValueDecomposition::getS(unsigned int index) +{ + if (index >= s_size) + return 0.0; + return s[index]; +} + +/** + * Two norm + * @return max(S) + */ +double SingularValueDecomposition::norm2() +{ + return s[0]; +} + +/** + * Two norm condition number + * @return max(S)/min(S) + */ + +double SingularValueDecomposition::cond() +{ + return s[0]/s[2]; +} + +/** + * Effective numerical matrix rank + * @return Number of nonnegligible singular values. + */ +int SingularValueDecomposition::rank() +{ + double eps = pow(2.0,-52.0); + double tol = 3.0*s[0]*eps; + int r = 0; + for (int i = 0; i < 3; i++) + { + if (s[i] > tol) + r++; + } + return r; +} + +//######################################################################## +//# E N D C L A S S SingularValueDecomposition +//######################################################################## + + + + + +//#define pxToCm 0.0275 +#define pxToCm 0.03 + + +//######################################################################## +//# O U T P U T +//######################################################################## + +/** + * Get the value of a node/attribute pair + */ +static Glib::ustring getAttribute( Inkscape::XML::Node *node, char const *attrName) +{ + Glib::ustring val; + char const *valstr = node->attribute(attrName); + if (valstr) + val = valstr; + return val; +} + + +static Glib::ustring formatTransform(Geom::Affine &tf) +{ + Glib::ustring str; + if (!tf.isIdentity()) + { + StringOutputStream outs; + OutputStreamWriter out(outs); + out.printf("matrix(%.3f %.3f %.3f %.3f %.3f %.3f)", + tf[0], tf[1], tf[2], tf[3], tf[4], tf[5]); + str = outs.getString(); + } + return str; +} + + +/** + * Get the general transform from SVG pixels to + * ODF cm + */ +static Geom::Affine getODFTransform(const SPItem *item) +{ + //### Get SVG-to-ODF transform + Geom::Affine tf (item->i2doc_affine()); + tf = tf * Geom::Affine(Geom::Scale(pxToCm)); + return tf; +} + + +/** + * Get the bounding box of an item, as mapped onto + * an ODF document, in cm. + */ +static Geom::OptRect getODFBoundingBox(const SPItem *item) +{ + // TODO: geometric or visual? + Geom::OptRect bbox = item->documentVisualBounds(); + if (bbox) { + *bbox *= Geom::Affine(Geom::Scale(pxToCm)); + } + return bbox; +} + + +/** + * Get the transform for an item, including parents, but without + * root viewBox transformation. + */ +static Geom::Affine getODFItemTransform(const SPItem *item) +{ + Geom::Affine itemTransform (item->i2doc_affine() * + SP_ACTIVE_DOCUMENT->getRoot()->c2p.inverse()); + return itemTransform; +} + + +/** + * Get some fun facts from the transform + */ +static void analyzeTransform(Geom::Affine &tf, + double &rotate, double &/*xskew*/, double &/*yskew*/, + double &xscale, double &yscale) +{ + SVDMatrix mat(2, 2); + mat(0, 0) = tf[0]; + mat(0, 1) = tf[1]; + mat(1, 0) = tf[2]; + mat(1, 1) = tf[3]; + + SingularValueDecomposition svd(mat); + + SVDMatrix U = svd.getU(); + SVDMatrix V = svd.getV(); + SVDMatrix Vt = V.transpose(); + SVDMatrix UVt = U.multiply(Vt); + double s0 = svd.getS(0); + double s1 = svd.getS(1); + xscale = s0; + yscale = s1; + rotate = UVt(0,0); +} + +static void gatherText(Inkscape::XML::Node *node, Glib::ustring &buf) +{ + if (node->type() == Inkscape::XML::TEXT_NODE) + { + char *s = (char *)node->content(); + if (s) + buf.append(s); + } + + for (Inkscape::XML::Node *child = node->firstChild() ; + child != nullptr; child = child->next()) + { + gatherText(child, buf); + } + +} + + +/** + * FIRST PASS. + * Method descends into the repr tree, converting image, style, and gradient info + * into forms compatible in ODF. + */ +void OdfOutput::preprocess(ZipFile &zf, Inkscape::XML::Node *node) +{ + Glib::ustring nodeName = node->name(); + Glib::ustring id = getAttribute(node, "id"); + + //### First, check for metadata + if (nodeName == "metadata" || nodeName == "svg:metadata") + { + Inkscape::XML::Node *mchild = node->firstChild() ; + if (!mchild || strcmp(mchild->name(), "rdf:RDF")) + return; + Inkscape::XML::Node *rchild = mchild->firstChild() ; + if (!rchild || strcmp(rchild->name(), "cc:Work")) + return; + for (Inkscape::XML::Node *cchild = rchild->firstChild() ; + cchild ; cchild = cchild->next()) + { + Glib::ustring ccName = cchild->name(); + Glib::ustring ccVal; + gatherText(cchild, ccVal); + //g_message("ccName: %s ccVal:%s", ccName.c_str(), ccVal.c_str()); + metadata[ccName] = ccVal; + } + return; + } + + //Now consider items. + SPObject *reprobj = SP_ACTIVE_DOCUMENT->getObjectByRepr(node); + if (!reprobj) + { + return; + } + if (!SP_IS_ITEM(reprobj)) + { + return; + } + + if (nodeName == "image" || nodeName == "svg:image") { + Glib::ustring href = getAttribute(node, "xlink:href"); + if (href.size() > 0 && imageTable.count(href) == 0) { + try { + auto uri = Inkscape::URI(href.c_str(), docBaseUri.c_str()); + auto mimetype = uri.getMimeType(); + + if (mimetype.substr(0, 6) != "image/") { + return; + } + + auto ext = mimetype.substr(6); + auto newName = Glib::ustring("Pictures/image") + std::to_string(imageTable.size()) + "." + ext; + + imageTable[href] = newName; + + auto ze = zf.newEntry(newName, ""); + ze->setUncompressedData(uri.getContents()); + ze->finish(); + } catch (...) { + g_warning("Could not handle URI '%.100s'", href.c_str()); + } + } + } + + for (Inkscape::XML::Node *child = node->firstChild() ; + child ; child = child->next()) + preprocess(zf, child); +} + + +/** + * Writes the manifest. Currently it only changes according to the + * file names of images packed into the zip file. + */ +bool OdfOutput::writeManifest(ZipFile &zf) +{ + BufferOutputStream bouts; + OutputStreamWriter outs(bouts); + + time_t tim; + time(&tim); + + outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + outs.writeString("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n"); + outs.writeString("\n"); + outs.writeString("\n"); + outs.writeString("<!--\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString(" file: manifest.xml\n"); + outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> + outs.writeString(" http://www.inkscape.org\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString("-->\n"); + outs.writeString("\n"); + outs.writeString("\n"); + outs.writeString("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n"); + outs.writeString(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n"); + outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n"); + outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"styles.xml\"/>\n"); + outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n"); + outs.writeString(" <!--List our images here-->\n"); + std::map<Glib::ustring, Glib::ustring>::iterator iter; + for (iter = imageTable.begin() ; iter!=imageTable.end() ; ++iter) + { + Glib::ustring newName = iter->second; + + // note: mime subtype was added as file extension in OdfOutput::preprocess + Glib::ustring mimesubtype = Inkscape::IO::get_file_extension(newName); + + outs.printf(" <manifest:file-entry manifest:media-type=\""); + outs.printf("image/"); + outs.printf("%s", mimesubtype.c_str()); + outs.printf("\" manifest:full-path=\""); + outs.writeString(newName.c_str()); + outs.printf("\"/>\n"); + } + outs.printf("</manifest:manifest>\n"); + + outs.close(); + + //Make our entry + ZipEntry *ze = zf.newEntry("META-INF/manifest.xml", "ODF file manifest"); + ze->setUncompressedData(bouts.getBuffer()); + ze->finish(); + + return true; +} + + +/** + * This writes the document meta information to meta.xml + */ +bool OdfOutput::writeMeta(ZipFile &zf) +{ + BufferOutputStream bouts; + OutputStreamWriter outs(bouts); + + time_t tim; + time(&tim); + + std::map<Glib::ustring, Glib::ustring>::iterator iter; + Glib::ustring InkscapeVersion = Glib::ustring("Inkscape.org - ") + Inkscape::version_string; + Glib::ustring creator = InkscapeVersion; + iter = metadata.find("dc:creator"); + if (iter != metadata.end()) + { + creator = iter->second; + } + + Glib::ustring date; + Glib::ustring moddate; + char buf [80]; + time_t rawtime; + struct tm * timeinfo; + time (&rawtime); + timeinfo = localtime (&rawtime); + strftime (buf,80,"%Y-%m-%d %H:%M:%S",timeinfo); + moddate = Glib::ustring(buf); + + iter = metadata.find("dc:date"); + if (iter != metadata.end()) + { + date = iter->second; + } + else + { + date = moddate; + } + + outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + outs.writeString("\n"); + outs.writeString("<!--\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString(" file: meta.xml\n"); + outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> + outs.writeString(" http://www.inkscape.org\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString("-->\n"); + outs.writeString("\n"); + outs.writeString("<office:document-meta\n"); + outs.writeString("xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); + outs.writeString("xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); + outs.writeString("xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); + outs.writeString("xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); + outs.writeString("xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); + outs.writeString("xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); + outs.writeString("xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); + outs.writeString("xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); + outs.writeString("office:version=\"1.0\">\n"); + outs.writeString("<office:meta>\n"); + Glib::ustring tmp = Glib::ustring::compose(" <meta:generator>%1</meta:generator>\n", InkscapeVersion); + tmp += Glib::ustring::compose(" <meta:initial-creator>%1</meta:initial-creator>\n", creator); + tmp += Glib::ustring::compose(" <meta:creation-date>%1</meta:creation-date>\n", date); + tmp += Glib::ustring::compose(" <dc:date>%1</dc:date>\n", moddate); + outs.writeUString(tmp); + for (iter = metadata.begin() ; iter != metadata.end() ; ++iter) + { + Glib::ustring name = iter->first; + Glib::ustring value = iter->second; + if (!name.empty() && !value.empty()) + { + tmp = Glib::ustring::compose(" <%1>%2</%3>\n", name, value, name); + outs.writeUString(tmp); + } + } + // outs.writeString(" <meta:editing-cycles>2</meta:editing-cycles>\n"); + // outs.writeString(" <meta:editing-duration>PT56S</meta:editing-duration>\n"); + // outs.writeString(" <meta:user-defined meta:name=\"Info 1\"/>\n"); + // outs.writeString(" <meta:user-defined meta:name=\"Info 2\"/>\n"); + // outs.writeString(" <meta:user-defined meta:name=\"Info 3\"/>\n"); + // outs.writeString(" <meta:user-defined meta:name=\"Info 4\"/>\n"); + // outs.writeString(" <meta:document-statistic meta:object-count=\"2\"/>\n"); + outs.writeString("</office:meta>\n"); + outs.writeString("</office:document-meta>\n"); + outs.close(); + + //Make our entry + ZipEntry *ze = zf.newEntry("meta.xml", "ODF info file"); + ze->setUncompressedData(bouts.getBuffer()); + ze->finish(); + + return true; +} + + +/** + * Writes an SVG path as an ODF <draw:path> and returns the number of points written + */ +static int +writePath(Writer &outs, Geom::PathVector const &pathv, + Geom::Affine const &tf, double xoff, double yoff) +{ + using Geom::X; + using Geom::Y; + + int nrPoints = 0; + + // convert the path to only lineto's and cubic curveto's: + Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv * tf * Geom::Translate(xoff, yoff) * Geom::Scale(1000.)); + + for (const auto & pit : pv) { + + double destx = pit.initialPoint()[X]; + double desty = pit.initialPoint()[Y]; + if (fabs(destx)<1.0) destx = 0.0; // Why is this needed? Shouldn't we just round all numbers then? + if (fabs(desty)<1.0) desty = 0.0; + outs.printf("M %.3f %.3f ", destx, desty); + nrPoints++; + + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_closed(); ++cit) { + + if( is_straight_curve(*cit) ) + { + double destx = cit->finalPoint()[X]; + double desty = cit->finalPoint()[Y]; + if (fabs(destx)<1.0) destx = 0.0; // Why is this needed? Shouldn't we just round all numbers then? + if (fabs(desty)<1.0) desty = 0.0; + outs.printf("L %.3f %.3f ", destx, desty); + } + else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) { + std::vector<Geom::Point> points = cubic->controlPoints(); + for (unsigned i = 1; i <= 3; i++) { + if (fabs(points[i][X])<1.0) points[i][X] = 0.0; // Why is this needed? Shouldn't we just round all numbers then? + if (fabs(points[i][Y])<1.0) points[i][Y] = 0.0; + } + outs.printf("C %.3f %.3f %.3f %.3f %.3f %.3f ", points[1][X],points[1][Y], points[2][X],points[2][Y], points[3][X],points[3][Y]); + } + else { + g_error ("logical error, because pathv_to_linear_and_cubic_beziers was used"); + } + + nrPoints++; + } + + if (pit.closed()) { + outs.printf("Z"); + } + } + + return nrPoints; +} + +bool OdfOutput::processStyle(SPItem *item, const Glib::ustring &id, const Glib::ustring &gradientNameFill, const Glib::ustring &gradientNameStroke, Glib::ustring& output) +{ + output.clear(); + if (!item) + { + return false; + } + + SPStyle *style = item->style; + if (!style) + { + return false; + } + + StyleInfo si; + + // FILL + if (style->fill.isColor()) + { + guint32 fillCol = style->fill.value.color.toRGBA32( 0 ); + char buf[16]; + int r = (fillCol >> 24) & 0xff; + int g = (fillCol >> 16) & 0xff; + int b = (fillCol >> 8) & 0xff; + snprintf(buf, 15, "#%02x%02x%02x", r, g, b); + si.fillColor = buf; + si.fill = "solid"; + double opacityPercent = 100.0 * + (SP_SCALE24_TO_FLOAT(style->fill_opacity.value)); + snprintf(buf, 15, "%.3f%%", opacityPercent); + si.fillOpacity = buf; + } + else if (style->fill.isPaintserver()) + { + SPGradient *gradient = SP_GRADIENT(SP_STYLE_FILL_SERVER(style)); + if (gradient) + { + si.fill = "gradient"; + } + } + + // STROKE + if (style->stroke.isColor()) + { + guint32 strokeCol = style->stroke.value.color.toRGBA32( 0 ); + char buf[16]; + int r = (strokeCol >> 24) & 0xff; + int g = (strokeCol >> 16) & 0xff; + int b = (strokeCol >> 8) & 0xff; + snprintf(buf, 15, "#%02x%02x%02x", r, g, b); + si.strokeColor = buf; + snprintf(buf, 15, "%.3fpt", style->stroke_width.value); + si.strokeWidth = buf; + si.stroke = "solid"; + double opacityPercent = 100.0 * + (SP_SCALE24_TO_FLOAT(style->stroke_opacity.value)); + snprintf(buf, 15, "%.3f%%", opacityPercent); + si.strokeOpacity = buf; + } + else if (style->stroke.isPaintserver()) + { + SPGradient *gradient = SP_GRADIENT(SP_STYLE_STROKE_SERVER(style)); + if (gradient) + { + si.stroke = "gradient"; + } + } + + //Look for existing identical style; + bool styleMatch = false; + std::vector<StyleInfo>::iterator iter; + for (iter=styleTable.begin() ; iter!=styleTable.end() ; ++iter) + { + if (si.equals(*iter)) + { + //map to existing styleTable entry + Glib::ustring styleName = iter->name; + styleLookupTable[id] = styleName; + styleMatch = true; + break; + } + } + + // Don't need a new style + if (styleMatch) + { + return false; + } + + Glib::ustring styleName = Glib::ustring::compose("style%1", styleTable.size()); + si.name = styleName; + styleTable.push_back(si); + styleLookupTable[id] = styleName; + + output = Glib::ustring::compose ("<style:style style:name=\"%1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n", si.name); + output += "<style:graphic-properties"; + if (si.fill == "gradient") + { + output += Glib::ustring::compose (" draw:fill=\"gradient\" draw:fill-gradient-name=\"%1\"", gradientNameFill); + } + else + { + output += Glib::ustring(" draw:fill=\"") + si.fill + "\""; + if(si.fill != "none") + { + output += Glib::ustring::compose(" draw:fill-color=\"%1\"", si.fillColor); + } + } + if (si.stroke == "gradient") + { + //does not seem to be supported by Open Office.org + output += Glib::ustring::compose (" draw:stroke=\"gradient\" draw:stroke-gradient-name=\"%1\"", gradientNameStroke); + } + else + { + output += Glib::ustring(" draw:stroke=\"") + si.stroke + "\""; + if (si.stroke != "none") + { + output += Glib::ustring::compose (" svg:stroke-width=\"%1\" svg:stroke-color=\"%2\" ", si.strokeWidth, si.strokeColor); + } + } + output += "/>\n</style:style>\n"; + + return true; +} + +bool OdfOutput::processGradient(SPItem *item, + const Glib::ustring &id, Geom::Affine &/*tf*/, + Glib::ustring& gradientName, Glib::ustring& output, bool checkFillGradient) +{ + output.clear(); + if (!item) + { + return false; + } + + SPStyle *style = item->style; + if (!style) + { + return false; + } + + if ((checkFillGradient? (!style->fill.isPaintserver()) : (!style->stroke.isPaintserver()))) + { + return false; + } + + //## Gradient + SPGradient *gradient = SP_GRADIENT((checkFillGradient?(SP_STYLE_FILL_SERVER(style)) :(SP_STYLE_STROKE_SERVER(style)))); + + if (gradient == nullptr) + { + return false; + } + GradientInfo gi; + SPGradient *grvec = gradient->getVector(FALSE); + for (SPStop *stop = grvec->getFirstStop(); + stop ; stop = stop->getNextStop()) + { + unsigned long rgba = stop->get_rgba32(); + unsigned long rgb = (rgba >> 8) & 0xffffff; + double opacity = (static_cast<double>(rgba & 0xff)) / 256.0; + GradientStop gs(rgb, opacity); + gi.stops.push_back(gs); + } + + Glib::ustring gradientName2; + if (SP_IS_LINEARGRADIENT(gradient)) + { + gi.style = "linear"; + SPLinearGradient *linGrad = SP_LINEARGRADIENT(gradient); + gi.x1 = linGrad->x1.value; + gi.y1 = linGrad->y1.value; + gi.x2 = linGrad->x2.value; + gi.y2 = linGrad->y2.value; + gradientName2 = Glib::ustring::compose("ImportedLinearGradient%1", gradientTable.size()); + } + else if (SP_IS_RADIALGRADIENT(gradient)) + { + gi.style = "radial"; + SPRadialGradient *radGrad = SP_RADIALGRADIENT(gradient); + Geom::OptRect bbox = item->documentVisualBounds(); + gi.cx = (radGrad->cx.value-bbox->left())/bbox->width(); + gi.cy = (radGrad->cy.value-bbox->top())/bbox->height(); + gradientName2 = Glib::ustring::compose("ImportedRadialGradient%1", gradientTable.size()); + } + else + { + g_warning("not a supported gradient type"); + return false; + } + + //Look for existing identical style; + bool gradientMatch = false; + std::vector<GradientInfo>::iterator iter; + for (iter=gradientTable.begin() ; iter!=gradientTable.end() ; ++iter) + { + if (gi.equals(*iter)) + { + //map to existing gradientTable entry + gradientName = iter->name; + gradientLookupTable[id] = gradientName; + gradientMatch = true; + break; + } + } + + if (gradientMatch) + { + return true; + } + + // No match, let us write a new entry + gradientName = gradientName2; + gi.name = gradientName; + gradientTable.push_back(gi); + gradientLookupTable[id] = gradientName; + + // int gradientCount = gradientTable.size(); + char buf[128]; + if (gi.style == "linear") + { + /* + =================================================================== + LINEAR gradient. We need something that looks like this: + <draw:gradient draw:name="Gradient_20_7" + draw:display-name="Gradient 7" + draw:style="linear" + draw:start-color="#008080" draw:end-color="#993366" + draw:start-intensity="100%" draw:end-intensity="100%" + draw:angle="150" draw:border="0%"/> + =================================================================== + */ + if (gi.stops.size() < 2) + { + g_warning("Need at least 2 stops for a linear gradient"); + return false; + } + output += Glib::ustring::compose("<draw:gradient draw:name=\"%1\"", gi.name); + output += Glib::ustring::compose(" draw:display-name=\"%1\"", gi.name); + output += " draw:style=\"linear\""; + snprintf(buf, 127, " draw:start-color=\"#%06lx\" draw:end-color=\"#%06lx\"", gi.stops[0].rgb, gi.stops[1].rgb); + output += buf; + //TODO: apply maths, to define begin of gradient, taking gradient begin and end, as well as object boundary into account + double angle = (gi.y2-gi.y1); + angle = (angle != 0.) ? (atan((gi.x2-gi.x1)/(gi.y2-gi.y1))* 180. / M_PI) : 90; + angle = (angle < 0)?(180+angle):angle; + angle = angle * 10; //why do we need this: precision????????????? + output += Glib::ustring::compose(" draw:start-intensity=\"%1\" draw:end-intensity=\"%2\" draw:angle=\"%3\"/>\n", + gi.stops[0].opacity * 100.0, gi.stops[1].opacity * 100.0, angle);// draw:border=\"0%%\" + } + else if (gi.style == "radial") + { + /* + =================================================================== + RADIAL gradient. We need something that looks like this: + <!-- radial gradient, light gray to white, centered, 0% border --> + <draw:gradient draw:name="radial_20_borderless" + draw:display-name="radial borderless" + draw:style="radial" + draw:cx="50%" draw:cy="50%" + draw:start-color="#999999" draw:end-color="#ffffff" + draw:border="0%"/> + =================================================================== + */ + if (gi.stops.size() < 2) + { + g_warning("Need at least 2 stops for a radial gradient"); + return false; + } + output += Glib::ustring::compose("<draw:gradient draw:name=\"%1\" draw:display-name=\"%1\" ", gi.name); + snprintf(buf, 127, "draw:cx=\"%05.3f\" draw:cy=\"%05.3f\" ", gi.cx*100, gi.cy*100); + output += Glib::ustring("draw:style=\"radial\" ") + buf; + snprintf(buf, 127, "draw:start-color=\"#%06lx\" draw:end-color=\"#%06lx\" ", gi.stops[0].rgb, gi.stops[1].rgb); + output += buf; + snprintf(buf, 127, "draw:start-intensity=\"%f%%\" draw:end-intensity=\"%f%%\" ", gi.stops[0].opacity*100.0, gi.stops[1].opacity*100.0); + output += buf; + output += "/>\n";//draw:border=\"0%\" + } + else + { + g_warning("unsupported gradient style '%s'", gi.style.c_str()); + return false; + } + return true; +} + + +/** + * SECOND PASS. + * This is the main SPObject tree output to ODF. + */ +bool OdfOutput::writeTree(Writer &couts, Writer &souts, + Inkscape::XML::Node *node) +{ + //# Get the SPItem, if applicable + SPObject *reprobj = SP_ACTIVE_DOCUMENT->getObjectByRepr(node); + if (!reprobj) + { + return true; + } + if (!SP_IS_ITEM(reprobj)) + { + return true; + } + SPItem *item = SP_ITEM(reprobj); + + Glib::ustring nodeName = node->name(); + Glib::ustring id = getAttribute(node, "id"); + Geom::Affine tf = getODFTransform(item);//Get SVG-to-ODF transform + Geom::OptRect bbox = getODFBoundingBox(item);//Get ODF bounding box params for item + if (!bbox) { + return true; + } + + double bbox_x = bbox->min()[Geom::X]; + double bbox_y = bbox->min()[Geom::Y]; + double bbox_width = (*bbox)[Geom::X].extent(); + double bbox_height = (*bbox)[Geom::Y].extent(); + + double rotate; + double xskew; + double yskew; + double xscale; + double yscale; + analyzeTransform(tf, rotate, xskew, yskew, xscale, yscale); + + //# Do our stuff + SPCurve *curve = nullptr; + + if (nodeName == "svg" || nodeName == "svg:svg") + { + //# Iterate through the children + for (Inkscape::XML::Node *child = node->firstChild() ; + child ; child = child->next()) + { + if (!writeTree(couts, souts, child)) + { + return false; + } + } + return true; + } + else if (nodeName == "g" || nodeName == "svg:g") + { + if (!id.empty()) + { + couts.printf("<draw:g id=\"%s\">\n", id.c_str()); + } + else + { + couts.printf("<draw:g>\n"); + } + //# Iterate through the children + for (Inkscape::XML::Node *child = node->firstChild() ; + child ; child = child->next()) + { + if (!writeTree(couts, souts, child)) + { + return false; + } + } + if (!id.empty()) + { + couts.printf("</draw:g> <!-- id=\"%s\" -->\n", id.c_str()); + } + else + { + couts.printf("</draw:g>\n"); + } + return true; + } + + //# GRADIENT + Glib::ustring gradientNameFill; + Glib::ustring gradientNameStroke; + Glib::ustring outputFill; + Glib::ustring outputStroke; + Glib::ustring outputStyle; + + processGradient(item, id, tf, gradientNameFill, outputFill, true); + processGradient(item, id, tf, gradientNameStroke, outputStroke, false); + souts.writeUString(outputFill); + souts.writeUString(outputStroke); + + //# STYLE + processStyle(item, id, gradientNameFill, gradientNameStroke, outputStyle); + souts.writeUString(outputStyle); + + //# ITEM DATA + if (nodeName == "image" || nodeName == "svg:image") + { + if (!SP_IS_IMAGE(item)) + { + g_warning("<image> is not an SPImage."); + return false; + } + + SPImage *img = SP_IMAGE(item); + double ix = img->x.value; + double iy = img->y.value; + double iwidth = img->width.value; + double iheight = img->height.value; + + Geom::Point ibbox_min = Geom::Point(ix, iy) * tf; + ix = ibbox_min.x(); + iy = ibbox_min.y(); + iwidth = xscale * iwidth; + iheight = yscale * iheight; + + Geom::Affine itemTransform = getODFItemTransform(item); + + Glib::ustring itemTransformString = formatTransform(itemTransform); + + Glib::ustring href = getAttribute(node, "xlink:href"); + std::map<Glib::ustring, Glib::ustring>::iterator iter = imageTable.find(href); + if (iter == imageTable.end()) + { + g_warning("image '%s' not in table", href.c_str()); + return false; + } + Glib::ustring newName = iter->second; + + couts.printf("<draw:frame "); + if (!id.empty()) + { + couts.printf("id=\"%s\" ", id.c_str()); + } + couts.printf("draw:style-name=\"gr1\" draw:text-style-name=\"P1\" draw:layer=\"layout\" "); + //no x or y. make them the translate transform, last one + couts.printf("svg:width=\"%.3fcm\" svg:height=\"%.3fcm\" ", + iwidth, iheight); + if (!itemTransformString.empty()) + { + couts.printf("draw:transform=\"%s translate(%.3fcm, %.3fcm)\" ", + itemTransformString.c_str(), ix, iy); + } + else + { + couts.printf("draw:transform=\"translate(%.3fcm, %.3fcm)\" ", ix, iy); + } + + couts.writeString(">\n"); + couts.printf(" <draw:image xlink:href=\"%s\" xlink:type=\"simple\"\n", + newName.c_str()); + couts.writeString(" xlink:show=\"embed\" xlink:actuate=\"onLoad\">\n"); + couts.writeString(" <text:p/>\n"); + couts.writeString(" </draw:image>\n"); + couts.writeString("</draw:frame>\n"); + return true; + } + else if (SP_IS_SHAPE(item)) + { + curve = SP_SHAPE(item)->getCurve(); + } + else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) + { + curve = te_get_layout(item)->convertToCurves(); + } + + if (curve) + { + //### Default <path> output + couts.writeString("<draw:path "); + if (!id.empty()) + { + couts.printf("id=\"%s\" ", id.c_str()); + } + + std::map<Glib::ustring, Glib::ustring>::iterator siter; + siter = styleLookupTable.find(id); + if (siter != styleLookupTable.end()) + { + Glib::ustring styleName = siter->second; + couts.printf("draw:style-name=\"%s\" ", styleName.c_str()); + } + + couts.printf("draw:layer=\"layout\" svg:x=\"%.3fcm\" svg:y=\"%.3fcm\" ", + bbox_x, bbox_y); + couts.printf("svg:width=\"%.3fcm\" svg:height=\"%.3fcm\" ", + bbox_width, bbox_height); + couts.printf("svg:viewBox=\"0.0 0.0 %.3f %.3f\"", + bbox_width * 1000.0, bbox_height * 1000.0); + + couts.printf(" svg:d=\""); + int nrPoints = writePath(couts, curve->get_pathvector(), + tf, bbox_x, bbox_y); + couts.writeString("\""); + + couts.writeString(">\n"); + couts.printf(" <!-- %d nodes -->\n", nrPoints); + couts.writeString("</draw:path>\n\n"); + + curve->unref(); + } + + return true; +} + + +/** + * Write the header for the content.xml file + */ +bool OdfOutput::writeStyleHeader(Writer &outs) +{ + time_t tim; + time(&tim); + + outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + outs.writeString("\n"); + outs.writeString("<!--\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString(" file: styles.xml\n"); + outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> + outs.writeString(" http://www.inkscape.org\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString("-->\n"); + outs.writeString("\n"); + outs.writeString("<office:document-styles\n"); + outs.writeString(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); + outs.writeString(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n"); + outs.writeString(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n"); + outs.writeString(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n"); + outs.writeString(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n"); + outs.writeString(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n"); + outs.writeString(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); + outs.writeString(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); + outs.writeString(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); + outs.writeString(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n"); + outs.writeString(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); + outs.writeString(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n"); + outs.writeString(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n"); + outs.writeString(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n"); + outs.writeString(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n"); + outs.writeString(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n"); + outs.writeString(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n"); + outs.writeString(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); + outs.writeString(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n"); + outs.writeString(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n"); + outs.writeString(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n"); + outs.writeString(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n"); + outs.writeString(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"); + outs.writeString(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); + outs.writeString(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); + outs.writeString(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); + outs.writeString(" office:version=\"1.0\">\n"); + outs.writeString("\n"); + outs.writeString("<!--\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString(" S T Y L E S\n"); + outs.writeString(" Style entries have been pulled from the svg style and\n"); + outs.writeString(" representation attributes in the SVG tree. The tree elements\n"); + outs.writeString(" then refer to them by name, in the ODF manner\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString("-->\n"); + outs.writeString("\n"); + outs.writeString("<office:styles>\n"); + outs.writeString("\n"); + + return true; +} + + +/** + * Write the footer for the style.xml file + */ +bool OdfOutput::writeStyleFooter(Writer &outs) +{ + outs.writeString("\n"); + outs.writeString("</office:styles>\n"); + outs.writeString("\n"); + outs.writeString("<office:automatic-styles>\n"); + outs.writeString("<!-- ####### 'Standard' styles ####### -->\n"); + outs.writeString("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n"); + outs.writeString("<style:style style:name=\"standard\" style:family=\"graphic\">\n"); + +///TODO: add default document style here + + outs.writeString("</style:style>\n"); + outs.writeString("<style:style style:name=\"gr1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n"); + outs.writeString(" <style:graphic-properties draw:stroke=\"none\" draw:fill=\"none\"\n"); + outs.writeString(" draw:textarea-horizontal-align=\"center\"\n"); + outs.writeString(" draw:textarea-vertical-align=\"middle\" draw:color-mode=\"standard\"\n"); + outs.writeString(" draw:luminance=\"0%\" draw:contrast=\"0%\" draw:gamma=\"100%\" draw:red=\"0%\"\n"); + outs.writeString(" draw:green=\"0%\" draw:blue=\"0%\" fo:clip=\"rect(0cm 0cm 0cm 0cm)\"\n"); + outs.writeString(" draw:image-opacity=\"100%\" style:mirror=\"none\"/>\n"); + outs.writeString("</style:style>\n"); + outs.writeString("<style:style style:name=\"P1\" style:family=\"paragraph\">\n"); + outs.writeString(" <style:paragraph-properties fo:text-align=\"center\"/>\n"); + outs.writeString("</style:style>\n"); + outs.writeString("</office:automatic-styles>\n"); + outs.writeString("\n"); + outs.writeString("<office:master-styles>\n"); + outs.writeString("<draw:layer-set>\n"); + outs.writeString(" <draw:layer draw:name=\"layout\"/>\n"); + outs.writeString(" <draw:layer draw:name=\"background\"/>\n"); + outs.writeString(" <draw:layer draw:name=\"backgroundobjects\"/>\n"); + outs.writeString(" <draw:layer draw:name=\"controls\"/>\n"); + outs.writeString(" <draw:layer draw:name=\"measurelines\"/>\n"); + outs.writeString("</draw:layer-set>\n"); + outs.writeString("\n"); + outs.writeString("<style:master-page style:name=\"Default\"\n"); + outs.writeString(" style:page-master-name=\"PM1\" draw:style-name=\"dp1\"/>\n"); + outs.writeString("</office:master-styles>\n"); + outs.writeString("\n"); + outs.writeString("</office:document-styles>\n"); + + return true; +} + + +/** + * Write the header for the content.xml file + */ +bool OdfOutput::writeContentHeader(Writer &outs) +{ + time_t tim; + time(&tim); + + outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); + outs.writeString("\n"); + outs.writeString("<!--\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString(" file: content.xml\n"); + outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> + outs.writeString(" http://www.inkscape.org\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString("-->\n"); + outs.writeString("\n"); + outs.writeString("<office:document-content\n"); + outs.writeString(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); + outs.writeString(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n"); + outs.writeString(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n"); + outs.writeString(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n"); + outs.writeString(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n"); + outs.writeString(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n"); + outs.writeString(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); + outs.writeString(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); + outs.writeString(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); + outs.writeString(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n"); + outs.writeString(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); + outs.writeString(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n"); + outs.writeString(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n"); + outs.writeString(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n"); + outs.writeString(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n"); + outs.writeString(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n"); + outs.writeString(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n"); + outs.writeString(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); + outs.writeString(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n"); + outs.writeString(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n"); + outs.writeString(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n"); + outs.writeString(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n"); + outs.writeString(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"); + outs.writeString(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); + outs.writeString(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); + outs.writeString(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); + outs.writeString(" office:version=\"1.0\">\n"); + outs.writeString("<office:scripts/>\n"); + outs.writeString("\n"); + outs.writeString("<!--\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString(" D R A W I N G\n"); + outs.writeString(" This section is the heart of SVG-ODF conversion. We are\n"); + outs.writeString(" starting with simple conversions, and will slowly evolve\n"); + outs.writeString(" into a 'smarter' translation as time progresses. Any help\n"); + outs.writeString(" in improving .odg export is welcome.\n"); + outs.writeString("*************************************************************************\n"); + outs.writeString("-->\n"); + outs.writeString("\n"); + outs.writeString("<office:body>\n"); + outs.writeString("<office:drawing>\n"); + outs.writeString("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\"\n"); + outs.writeString(" draw:master-page-name=\"Default\">\n"); + outs.writeString("\n"); + return true; +} + + +/** + * Write the footer for the content.xml file + */ +bool OdfOutput::writeContentFooter(Writer &outs) +{ + outs.writeString("\n"); + outs.writeString("</draw:page>\n"); + outs.writeString("</office:drawing>\n"); + outs.writeString("\n"); + outs.writeString("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n"); + outs.writeString("\n"); + outs.writeString("</office:body>\n"); + outs.writeString("</office:document-content>\n"); + return true; +} + + +/** + * Write the content.xml file. Writes the namesspace headers, then + * calls writeTree(). + */ +bool OdfOutput::writeContent(ZipFile &zf, Inkscape::XML::Node *node) +{ + //Content.xml stream + BufferOutputStream cbouts; + OutputStreamWriter couts(cbouts); + + if (!writeContentHeader(couts)) + { + return false; + } + + //Style.xml stream + BufferOutputStream sbouts; + OutputStreamWriter souts(sbouts); + + if (!writeStyleHeader(souts)) + { + return false; + } + + //# Descend into the tree, doing all of our conversions + //# to both files at the same time + char *oldlocale = g_strdup (setlocale (LC_NUMERIC, nullptr)); + setlocale (LC_NUMERIC, "C"); + if (!writeTree(couts, souts, node)) + { + g_warning("Failed to convert SVG tree"); + setlocale (LC_NUMERIC, oldlocale); + g_free (oldlocale); + return false; + } + setlocale (LC_NUMERIC, oldlocale); + g_free (oldlocale); + + //# Finish content file + if (!writeContentFooter(couts)) + { + return false; + } + + ZipEntry *ze = zf.newEntry("content.xml", "ODF master content file"); + ze->setUncompressedData(cbouts.getBuffer()); + ze->finish(); + + //# Finish style file + if (!writeStyleFooter(souts)) + { + return false; + } + + ze = zf.newEntry("styles.xml", "ODF style file"); + ze->setUncompressedData(sbouts.getBuffer()); + ze->finish(); + + return true; +} + + +/** + * Resets class to its pristine condition, ready to use again + */ +void OdfOutput::reset() +{ + metadata.clear(); + styleTable.clear(); + styleLookupTable.clear(); + gradientTable.clear(); + gradientLookupTable.clear(); + imageTable.clear(); +} + + +/** + * Descends into the SVG tree, mapping things to ODF when appropriate + */ +void OdfOutput::save(Inkscape::Extension::Output */*mod*/, SPDocument *doc, gchar const *filename) +{ + if (doc != SP_ACTIVE_DOCUMENT) { + g_warning("OdfOutput can only save the active document"); + return; + } + + reset(); + + docBaseUri = Inkscape::URI::from_dirname(doc->getDocumentBase()).str(); + + ZipFile zf; + preprocess(zf, doc->getReprRoot()); + + if (!writeManifest(zf)) + { + g_warning("Failed to write manifest"); + return; + } + + if (!writeContent(zf, doc->getReprRoot())) + { + g_warning("Failed to write content"); + return; + } + + if (!writeMeta(zf)) + { + g_warning("Failed to write metafile"); + return; + } + + if (!zf.writeFile(filename)) + { + return; + } +} + + +/** + * This is the definition of PovRay output. This function just + * calls the extension system with the memory allocated XML that + * describes the data. +*/ +void OdfOutput::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("OpenDocument Drawing Output") "</name>\n" + "<id>org.inkscape.output.odf</id>\n" + "<output>\n" + "<extension>.odg</extension>\n" + "<mimetype>text/x-povray-script</mimetype>\n" + "<filetypename>" N_("OpenDocument drawing (*.odg)") "</filetypename>\n" + "<filetypetooltip>" N_("OpenDocument drawing file") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", + new OdfOutput()); +} + +/** + * Make sure that we are in the database + */ +bool OdfOutput::check (Inkscape::Extension::Extension */*module*/) +{ + /* We don't need a Key + if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV)) + return FALSE; + */ + + return TRUE; +} + +} //namespace Internal +} //namespace Extension +} //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)(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/extension/internal/odf.h b/src/extension/internal/odf.h new file mode 100644 index 0000000..94cd360 --- /dev/null +++ b/src/extension/internal/odf.h @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/** @file + * OpenDocument (drawing) input and output + *//* + * Authors: + * Bob Jamison + * Abhishek Sharma + * + * Copyright (C) 2018 Authors + * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information. + */ + +#ifndef EXTENSION_INTERNAL_ODG_OUT_H +#define EXTENSION_INTERNAL_ODG_OUT_H + +#include <util/ziptool.h> + +#include "extension/implementation/implementation.h" + +#include <xml/repr.h> +#include <string> +#include <map> + +#include "object/uri.h" +class SPItem; + +#include <glibmm/ustring.h> + +namespace Inkscape +{ +namespace Extension +{ +namespace Internal +{ + +typedef Inkscape::IO::Writer Writer; + +class StyleInfo +{ +public: + + StyleInfo() + { + init(); + } + + StyleInfo(const StyleInfo &other) + { + assign(other); + } + + StyleInfo &operator=(const StyleInfo &other) + { + assign(other); + return *this; + } + + void assign(const StyleInfo &other) + { + name = other.name; + stroke = other.stroke; + strokeColor = other.strokeColor; + strokeWidth = other.strokeWidth; + strokeOpacity = other.strokeOpacity; + fill = other.fill; + fillColor = other.fillColor; + fillOpacity = other.fillOpacity; + } + + void init() + { + name = "none"; + stroke = "none"; + strokeColor = "none"; + strokeWidth = "none"; + strokeOpacity = "none"; + fill = "none"; + fillColor = "none"; + fillOpacity = "none"; + } + + virtual ~StyleInfo() + = default; + + //used for eliminating duplicates in the styleTable + bool equals(const StyleInfo &other) + { + if ( + stroke != other.stroke || + strokeColor != other.strokeColor || + strokeWidth != other.strokeWidth || + strokeOpacity != other.strokeOpacity || + fill != other.fill || + fillColor != other.fillColor || + fillOpacity != other.fillOpacity + ) + return false; + return true; + } + + Glib::ustring name; + Glib::ustring stroke; + Glib::ustring strokeColor; + Glib::ustring strokeWidth; + Glib::ustring strokeOpacity; + Glib::ustring fill; + Glib::ustring fillColor; + Glib::ustring fillOpacity; + +}; + + + + +class GradientStop +{ +public: + GradientStop() : rgb(0), opacity(0) + {} + GradientStop(unsigned long rgbArg, double opacityArg) + { rgb = rgbArg; opacity = opacityArg; } + virtual ~GradientStop() + = default; + GradientStop(const GradientStop &other) + { assign(other); } + virtual GradientStop& operator=(const GradientStop &other) + { assign(other); return *this; } + void assign(const GradientStop &other) + { + rgb = other.rgb; + opacity = other.opacity; + } + unsigned long rgb; + double opacity; +}; + + + +class GradientInfo +{ +public: + + GradientInfo() + { + init(); + } + + GradientInfo(const GradientInfo &other) + { + assign(other); + } + + GradientInfo &operator=(const GradientInfo &other) + { + assign(other); + return *this; + } + + void assign(const GradientInfo &other) + { + name = other.name; + style = other.style; + cx = other.cx; + cy = other.cy; + fx = other.fx; + fy = other.fy; + r = other.r; + x1 = other.x1; + y1 = other.y1; + x2 = other.x2; + y2 = other.y2; + stops = other.stops; + } + + void init() + { + name = "none"; + style = "none"; + cx = 0.0; + cy = 0.0; + fx = 0.0; + fy = 0.0; + r = 0.0; + x1 = 0.0; + y1 = 0.0; + x2 = 0.0; + y2 = 0.0; + stops.clear(); + } + + virtual ~GradientInfo() + = default; + + //used for eliminating duplicates in the styleTable + bool equals(const GradientInfo &other) + { + if ( + name != other.name || + style != other.style || + cx != other.cx || + cy != other.cy || + fx != other.fx || + fy != other.fy || + r != other.r || + x1 != other.x1 || + y1 != other.y1 || + x2 != other.x2 || + y2 != other.y2 + ) + return false; + if (stops.size() != other.stops.size()) + return false; + for (unsigned int i=0 ; i<stops.size() ; i++) + { + GradientStop g1 = stops[i]; + GradientStop g2 = other.stops[i]; + if (g1.rgb != g2.rgb) + return false; + if (g1.opacity != g2.opacity) + return false; + } + return true; + } + + Glib::ustring name; + Glib::ustring style; + double cx; + double cy; + double fx; + double fy; + double r; + double x1; + double y1; + double x2; + double y2; + std::vector<GradientStop> stops; + +}; + + + +/** + * OpenDocument <drawing> input and output + * + * This is an an entry in the extensions mechanism to begin to enable + * the inputting and outputting of OpenDocument Format (ODF) files from + * within Inkscape. Although the initial implementations will be very lossy + * do to the differences in the models of SVG and ODF, they will hopefully + * improve greatly with time. + * + * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html + */ +class OdfOutput : public Inkscape::Extension::Implementation::Implementation +{ + +public: + + bool check (Inkscape::Extension::Extension * module) override; + + void save (Inkscape::Extension::Output *mod, + SPDocument *doc, + gchar const *filename) override; + + static void init (); + +private: + + std::string docBaseUri; + + void reset(); + + //cc or dc metadata name/value pairs + std::map<Glib::ustring, Glib::ustring> metadata; + + /* Style table + Uses a two-stage lookup to avoid style duplication. + Use like: + StyleInfo si = styleTable[styleLookupTable[id]]; + but check for errors, of course + */ + //element id -> style entry name + std::map<Glib::ustring, Glib::ustring> styleLookupTable; + //style entry name -> style info + std::vector<StyleInfo> styleTable; + + //element id -> gradient entry name + std::map<Glib::ustring, Glib::ustring> gradientLookupTable; + //gradient entry name -> gradient info + std::vector<GradientInfo> gradientTable; + + //for renaming image file names + std::map<Glib::ustring, Glib::ustring> imageTable; + + void preprocess(ZipFile &zf, Inkscape::XML::Node *node); + + bool writeManifest(ZipFile &zf); + + bool writeMeta(ZipFile &zf); + + bool writeStyle(ZipFile &zf); + + bool processStyle(SPItem *item, const Glib::ustring &id, const Glib::ustring &gradientNameFill, const Glib::ustring &gradientNameStroke, Glib::ustring& output); + + bool processGradient(SPItem *item, + const Glib::ustring &id, Geom::Affine &tf, Glib::ustring& gradientName, Glib::ustring& output, bool checkFillGradient = true); + + bool writeStyleHeader(Writer &outs); + + bool writeStyleFooter(Writer &outs); + + bool writeContentHeader(Writer &outs); + + bool writeContentFooter(Writer &outs); + + bool writeTree(Writer &couts, Writer &souts, Inkscape::XML::Node *node); + + bool writeContent(ZipFile &zf, Inkscape::XML::Node *node); + +}; + + +} //namespace Internal +} //namespace Extension +} //namespace Inkscape + + + +#endif /* EXTENSION_INTERNAL_ODG_OUT_H */ + diff --git a/src/extension/internal/pdfinput/pdf-input.cpp b/src/extension/internal/pdfinput/pdf-input.cpp new file mode 100644 index 0000000..a79ceae --- /dev/null +++ b/src/extension/internal/pdfinput/pdf-input.cpp @@ -0,0 +1,988 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Native PDF import using libpoppler. + * + * Authors: + * miklos erdelyi + * Abhishek Sharma + * + * Copyright (C) 2007 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 + +#include "pdf-input.h" + +#ifdef HAVE_POPPLER +#include <poppler/goo/GooString.h> +#include <poppler/ErrorCodes.h> +#include <poppler/GlobalParams.h> +#include <poppler/PDFDoc.h> +#include <poppler/Page.h> +#include <poppler/Catalog.h> + +#ifdef HAVE_POPPLER_CAIRO +#include <poppler/glib/poppler.h> +#include <poppler/glib/poppler-document.h> +#include <poppler/glib/poppler-page.h> +#endif + +#include <gdkmm/general.h> +#include <glibmm/convert.h> +#include <glibmm/i18n.h> +#include <glibmm/miscutils.h> +#include <gtk/gtk.h> +#include <gtkmm/checkbutton.h> +#include <gtkmm/comboboxtext.h> +#include <gtkmm/drawingarea.h> +#include <gtkmm/frame.h> +#include <gtkmm/radiobutton.h> +#include <gtkmm/scale.h> +#include <utility> + +#include "document-undo.h" +#include "extension/input.h" +#include "extension/system.h" +#include "inkscape.h" +#include "object/sp-root.h" +#include "pdf-parser.h" +#include "svg-builder.h" +#include "ui/dialog-events.h" +#include "ui/widget/frame.h" +#include "ui/widget/spinbutton.h" +#include "util/units.h" + + + +namespace { + +void sanitize_page_number(int &page_num, const int num_pages) { + if (page_num < 1 || page_num > num_pages) { + std::cerr << "Inkscape::Extension::Internal::PdfInput::open: Bad page number " + << page_num + << ". Import first page instead." + << std::endl; + page_num = 1; + } +} + +} + +namespace Inkscape { +namespace Extension { +namespace Internal { + +/** + * \brief The PDF import dialog + * FIXME: Probably this should be placed into src/ui/dialog + */ + +static const gchar * crop_setting_choices[] = { + //TRANSLATORS: The following are document crop settings for PDF import + // more info: http://www.acrobatusers.com/tech_corners/javascript_corner/tips/2006/page_bounds/ + N_("media box"), + N_("crop box"), + N_("trim box"), + N_("bleed box"), + N_("art box") +}; + +PdfImportDialog::PdfImportDialog(std::shared_ptr<PDFDoc> doc, const gchar */*uri*/) + : _pdf_doc(std::move(doc)) +{ + assert(_pdf_doc); +#ifdef HAVE_POPPLER_CAIRO + _poppler_doc = NULL; +#endif // HAVE_POPPLER_CAIRO + cancelbutton = Gtk::manage(new Gtk::Button(_("_Cancel"), true)); + okbutton = Gtk::manage(new Gtk::Button(_("_OK"), true)); + _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:"))); + + // Page number + auto _pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _pdf_doc->getNumPages(), 1, 10, 0); + _pageNumberSpin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(_pageNumberSpin_adj, 1, 1)); + _labelTotalPages = Gtk::manage(new class Gtk::Label()); + hbox2 = Gtk::manage(new class Gtk::HBox(false, 0)); + // Disable the page selector when there's only one page + int num_pages = _pdf_doc->getCatalog()->getNumPages(); + if ( num_pages == 1 ) { + _pageNumberSpin->set_sensitive(false); + } else { + // Display total number of pages + gchar *label_text = g_strdup_printf(_("out of %i"), num_pages); + _labelTotalPages->set_label(label_text); + g_free(label_text); + } + + // Crop settings + _cropCheck = Gtk::manage(new class Gtk::CheckButton(_("Clip to:"))); + _cropTypeCombo = Gtk::manage(new class Gtk::ComboBoxText()); + int num_crop_choices = sizeof(crop_setting_choices) / sizeof(crop_setting_choices[0]); + for ( int i = 0 ; i < num_crop_choices ; i++ ) { + _cropTypeCombo->append(_(crop_setting_choices[i])); + } + _cropTypeCombo->set_active_text(_(crop_setting_choices[0])); + _cropTypeCombo->set_sensitive(false); + + hbox3 = Gtk::manage(new class Gtk::HBox(false, 4)); + vbox2 = Gtk::manage(new class Gtk::VBox(false, 4)); + _pageSettingsFrame = Gtk::manage(new class Inkscape::UI::Widget::Frame(_("Page settings"))); + _labelPrecision = Gtk::manage(new class Gtk::Label(_("Precision of approximating gradient meshes:"))); + _labelPrecisionWarning = Gtk::manage(new class Gtk::Label(_("<b>Note</b>: setting the precision too high may result in a large SVG file and slow performance."))); + _labelPrecisionWarning->set_max_width_chars(50); + +#ifdef HAVE_POPPLER_CAIRO + Gtk::RadioButton::Group group; + _importViaPoppler = Gtk::manage(new class Gtk::RadioButton(group,_("Poppler/Cairo import"))); + _labelViaPoppler = Gtk::manage(new class Gtk::Label(_("Import via external library. Text consists of groups containing cloned glyphs where each glyph is a path. Images are stored internally. Meshes cause entire document to be rendered as a raster image."))); + _labelViaPoppler->set_max_width_chars(50); + _importViaInternal = Gtk::manage(new class Gtk::RadioButton(group,_("Internal import"))); + _labelViaInternal = Gtk::manage(new class Gtk::Label(_("Import via internal (Poppler derived) library. Text is stored as text but white space is missing. Meshes are converted to tiles, the number depends on the precision set below."))); + _labelViaInternal->set_max_width_chars(50); +#endif + + _fallbackPrecisionSlider_adj = Gtk::Adjustment::create(2, 1, 256, 1, 10, 10); + _fallbackPrecisionSlider = Gtk::manage(new class Gtk::Scale(_fallbackPrecisionSlider_adj)); + _fallbackPrecisionSlider->set_value(2.0); + _labelPrecisionComment = Gtk::manage(new class Gtk::Label(_("rough"))); + hbox6 = Gtk::manage(new class Gtk::HBox(false, 4)); + + // Text options + // _labelText = Gtk::manage(new class Gtk::Label(_("Text handling:"))); + // _textHandlingCombo = Gtk::manage(new class Gtk::ComboBoxText()); + // _textHandlingCombo->append(_("Import text as text")); + // _textHandlingCombo->set_active_text(_("Import text as text")); + // hbox5 = Gtk::manage(new class Gtk::HBox(false, 4)); + + // Font option + _localFontsCheck = Gtk::manage(new class Gtk::CheckButton(_("Replace PDF fonts by closest-named installed fonts"))); + + _embedImagesCheck = Gtk::manage(new class Gtk::CheckButton(_("Embed images"))); + vbox3 = Gtk::manage(new class Gtk::VBox(false, 4)); + _importSettingsFrame = Gtk::manage(new class Inkscape::UI::Widget::Frame(_("Import settings"))); + vbox1 = Gtk::manage(new class Gtk::VBox(false, 4)); + _previewArea = Gtk::manage(new class Gtk::DrawingArea()); + hbox1 = Gtk::manage(new class Gtk::HBox(false, 4)); + cancelbutton->set_can_focus(); + cancelbutton->set_can_default(); + cancelbutton->set_relief(Gtk::RELIEF_NORMAL); + okbutton->set_can_focus(); + okbutton->set_can_default(); + okbutton->set_relief(Gtk::RELIEF_NORMAL); + + _labelSelect->set_xalign(0.5); + _labelSelect->set_yalign(0.5); + _labelTotalPages->set_xalign(0.5); + _labelTotalPages->set_yalign(0.5); + _labelPrecision->set_xalign(0.0); + _labelPrecision->set_yalign(0.5); + _labelPrecisionWarning->set_xalign(0.0); + _labelPrecisionWarning->set_yalign(0.5); + _labelPrecisionComment->set_xalign(0.5); + _labelPrecisionComment->set_yalign(0.5); + + _labelSelect->set_margin_start(4); + _labelSelect->set_margin_end(4); + _labelTotalPages->set_margin_start(4); + _labelTotalPages->set_margin_end(4); + _labelPrecision->set_margin_start(4); + _labelPrecision->set_margin_end(4); + _labelPrecisionWarning->set_margin_start(4); + _labelPrecisionWarning->set_margin_end(4); + _labelPrecisionComment->set_margin_start(4); + _labelPrecisionComment->set_margin_end(4); + + _labelSelect->set_justify(Gtk::JUSTIFY_LEFT); + _labelSelect->set_line_wrap(false); + _labelSelect->set_use_markup(false); + _labelSelect->set_selectable(false); + _pageNumberSpin->set_can_focus(); + _pageNumberSpin->set_update_policy(Gtk::UPDATE_ALWAYS); + _pageNumberSpin->set_numeric(true); + _pageNumberSpin->set_digits(0); + _pageNumberSpin->set_wrap(false); + _labelTotalPages->set_justify(Gtk::JUSTIFY_LEFT); + _labelTotalPages->set_line_wrap(false); + _labelTotalPages->set_use_markup(false); + _labelTotalPages->set_selectable(false); + hbox2->pack_start(*_labelSelect, Gtk::PACK_SHRINK, 4); + hbox2->pack_start(*_pageNumberSpin, Gtk::PACK_SHRINK, 4); + hbox2->pack_start(*_labelTotalPages, Gtk::PACK_SHRINK, 4); + _cropCheck->set_can_focus(); + _cropCheck->set_relief(Gtk::RELIEF_NORMAL); + _cropCheck->set_mode(true); + _cropCheck->set_active(false); + _cropTypeCombo->set_border_width(1); + hbox3->pack_start(*_cropCheck, Gtk::PACK_SHRINK, 4); + hbox3->pack_start(*_cropTypeCombo, Gtk::PACK_SHRINK, 0); + vbox2->pack_start(*hbox2); + vbox2->pack_start(*hbox3); + _pageSettingsFrame->add(*vbox2); + _pageSettingsFrame->set_border_width(4); + _labelPrecision->set_justify(Gtk::JUSTIFY_LEFT); + _labelPrecision->set_line_wrap(true); + _labelPrecision->set_use_markup(false); + _labelPrecision->set_selectable(false); + _labelPrecisionWarning->set_justify(Gtk::JUSTIFY_LEFT); + _labelPrecisionWarning->set_line_wrap(true); + _labelPrecisionWarning->set_use_markup(true); + _labelPrecisionWarning->set_selectable(false); + +#ifdef HAVE_POPPLER_CAIRO + _importViaPoppler->set_can_focus(); + _importViaPoppler->set_relief(Gtk::RELIEF_NORMAL); + _importViaPoppler->set_mode(true); + _importViaPoppler->set_active(false); + _importViaInternal->set_can_focus(); + _importViaInternal->set_relief(Gtk::RELIEF_NORMAL); + _importViaInternal->set_mode(true); + _importViaInternal->set_active(true); + _labelViaPoppler->set_line_wrap(true); + _labelViaInternal->set_line_wrap(true); + _labelViaPoppler->set_xalign(0); + _labelViaInternal->set_xalign(0); +#endif + + _fallbackPrecisionSlider->set_size_request(180,-1); + _fallbackPrecisionSlider->set_can_focus(); + _fallbackPrecisionSlider->set_inverted(false); + _fallbackPrecisionSlider->set_digits(1); + _fallbackPrecisionSlider->set_draw_value(true); + _fallbackPrecisionSlider->set_value_pos(Gtk::POS_TOP); + _labelPrecisionComment->set_size_request(90,-1); + _labelPrecisionComment->set_justify(Gtk::JUSTIFY_LEFT); + _labelPrecisionComment->set_line_wrap(false); + _labelPrecisionComment->set_use_markup(false); + _labelPrecisionComment->set_selectable(false); + hbox6->pack_start(*_fallbackPrecisionSlider, Gtk::PACK_SHRINK, 4); + hbox6->pack_start(*_labelPrecisionComment, Gtk::PACK_SHRINK, 0); + // _labelText->set_alignment(0.5,0.5); + // _labelText->set_padding(4,0); + // _labelText->set_justify(Gtk::JUSTIFY_LEFT); + // _labelText->set_line_wrap(false); + // _labelText->set_use_markup(false); + // _labelText->set_selectable(false); + // hbox5->pack_start(*_labelText, Gtk::PACK_SHRINK, 0); + // hbox5->pack_start(*_textHandlingCombo, Gtk::PACK_SHRINK, 0); + _localFontsCheck->set_can_focus(); + _localFontsCheck->set_relief(Gtk::RELIEF_NORMAL); + _localFontsCheck->set_mode(true); + _localFontsCheck->set_active(true); + _embedImagesCheck->set_can_focus(); + _embedImagesCheck->set_relief(Gtk::RELIEF_NORMAL); + _embedImagesCheck->set_mode(true); + _embedImagesCheck->set_active(true); +#ifdef HAVE_POPPLER_CAIRO + vbox3->pack_start(*_importViaPoppler, Gtk::PACK_SHRINK, 0); + vbox3->pack_start(*_labelViaPoppler, Gtk::PACK_SHRINK, 0); + vbox3->pack_start(*_importViaInternal, Gtk::PACK_SHRINK, 0); + vbox3->pack_start(*_labelViaInternal, Gtk::PACK_SHRINK, 0); +#endif + vbox3->pack_start(*_localFontsCheck, Gtk::PACK_SHRINK, 0); + vbox3->pack_start(*_embedImagesCheck, Gtk::PACK_SHRINK, 0); + vbox3->pack_start(*_labelPrecision, Gtk::PACK_SHRINK, 0); + vbox3->pack_start(*hbox6, Gtk::PACK_SHRINK, 0); + vbox3->pack_start(*_labelPrecisionWarning, Gtk::PACK_SHRINK, 0); + // vbox3->pack_start(*hbox5, Gtk::PACK_SHRINK, 4); + _importSettingsFrame->add(*vbox3); + _importSettingsFrame->set_border_width(4); + vbox1->pack_start(*_pageSettingsFrame, Gtk::PACK_SHRINK, 0); + vbox1->pack_start(*_importSettingsFrame, Gtk::PACK_SHRINK, 0); + hbox1->pack_start(*vbox1); + hbox1->pack_start(*_previewArea, Gtk::PACK_SHRINK, 4); + + get_content_area()->set_homogeneous(false); + get_content_area()->set_spacing(0); + get_content_area()->pack_start(*hbox1); + + this->set_title(_("PDF Import Settings")); + this->set_modal(true); + sp_transientize(GTK_WIDGET(this->gobj())); //Make transient + this->property_window_position().set_value(Gtk::WIN_POS_NONE); + this->set_resizable(true); + this->property_destroy_with_parent().set_value(false); + this->add_action_widget(*cancelbutton, -6); + this->add_action_widget(*okbutton, -5); + + this->show_all(); + + // Connect signals + _previewArea->signal_draw().connect(sigc::mem_fun(*this, &PdfImportDialog::_onDraw)); + _pageNumberSpin_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PdfImportDialog::_onPageNumberChanged)); + _cropCheck->signal_toggled().connect(sigc::mem_fun(*this, &PdfImportDialog::_onToggleCropping)); + _fallbackPrecisionSlider_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PdfImportDialog::_onPrecisionChanged)); +#ifdef HAVE_POPPLER_CAIRO + _importViaPoppler->signal_toggled().connect(sigc::mem_fun(*this, &PdfImportDialog::_onToggleImport)); +#endif + + _render_thumb = false; +#ifdef HAVE_POPPLER_CAIRO + _cairo_surface = NULL; + _render_thumb = true; + + // Create PopplerDocument + Glib::ustring filename = _pdf_doc->getFileName()->getCString(); + if (!Glib::path_is_absolute(filename)) { + filename = Glib::build_filename(Glib::get_current_dir(),filename); + } + Glib::ustring full_uri = Glib::filename_to_uri(filename); + + if (!full_uri.empty()) { + _poppler_doc = poppler_document_new_from_file(full_uri.c_str(), NULL, NULL); + } + + // Set sensitivity of some widgets based on selected import type. + _onToggleImport(); +#endif + + // Set default preview size + _preview_width = 200; + _preview_height = 300; + + // Init preview + _thumb_data = nullptr; + _pageNumberSpin_adj->set_value(1.0); + _current_page = 1; + _setPreviewPage(_current_page); + + set_default (*okbutton); + set_focus (*okbutton); +} + +PdfImportDialog::~PdfImportDialog() { +#ifdef HAVE_POPPLER_CAIRO + if (_cairo_surface) { + cairo_surface_destroy(_cairo_surface); + } + if (_poppler_doc) { + g_object_unref(G_OBJECT(_poppler_doc)); + } +#endif + if (_thumb_data) { + gfree(_thumb_data); + } +} + +bool PdfImportDialog::showDialog() { + show(); + gint b = run(); + hide(); + if ( b == Gtk::RESPONSE_OK ) { + return TRUE; + } else { + return FALSE; + } +} + +int PdfImportDialog::getSelectedPage() { + return _current_page; +} + +bool PdfImportDialog::getImportMethod() { +#ifdef HAVE_POPPLER_CAIRO + return _importViaPoppler->get_active(); +#else + return false; +#endif +} + +/** + * \brief Retrieves the current settings into a repr which SvgBuilder will use + * for determining the behaviour desired by the user + */ +void PdfImportDialog::getImportSettings(Inkscape::XML::Node *prefs) { + sp_repr_set_svg_double(prefs, "selectedPage", (double)_current_page); + if (_cropCheck->get_active()) { + Glib::ustring current_choice = _cropTypeCombo->get_active_text(); + int num_crop_choices = sizeof(crop_setting_choices) / sizeof(crop_setting_choices[0]); + int i = 0; + for ( ; i < num_crop_choices ; i++ ) { + if ( current_choice == _(crop_setting_choices[i]) ) { + break; + } + } + sp_repr_set_svg_double(prefs, "cropTo", (double)i); + } else { + sp_repr_set_svg_double(prefs, "cropTo", -1.0); + } + sp_repr_set_svg_double(prefs, "approximationPrecision", + _fallbackPrecisionSlider->get_value()); + if (_localFontsCheck->get_active()) { + prefs->setAttribute("localFonts", "1"); + } else { + prefs->setAttribute("localFonts", "0"); + } + if (_embedImagesCheck->get_active()) { + prefs->setAttribute("embedImages", "1"); + } else { + prefs->setAttribute("embedImages", "0"); + } +#ifdef HAVE_POPPLER_CAIRO + if (_importViaPoppler->get_active()) { + prefs->setAttribute("importviapoppler", "1"); + } else { + prefs->setAttribute("importviapoppler", "0"); + } +#endif +} + +/** + * \brief Redisplay the comment on the current approximation precision setting + * Evenly divides the interval of possible values between the available labels. + */ +void PdfImportDialog::_onPrecisionChanged() { + + static Glib::ustring precision_comments[] = { + Glib::ustring(C_("PDF input precision", "rough")), + Glib::ustring(C_("PDF input precision", "medium")), + Glib::ustring(C_("PDF input precision", "fine")), + Glib::ustring(C_("PDF input precision", "very fine")) + }; + + double min = _fallbackPrecisionSlider_adj->get_lower(); + double max = _fallbackPrecisionSlider_adj->get_upper(); + int num_intervals = sizeof(precision_comments) / sizeof(precision_comments[0]); + double interval_len = ( max - min ) / (double)num_intervals; + double value = _fallbackPrecisionSlider_adj->get_value(); + int comment_idx = (int)floor( ( value - min ) / interval_len ); + _labelPrecisionComment->set_label(precision_comments[comment_idx]); +} + +void PdfImportDialog::_onToggleCropping() { + _cropTypeCombo->set_sensitive(_cropCheck->get_active()); +} + +void PdfImportDialog::_onPageNumberChanged() { + int page = _pageNumberSpin->get_value_as_int(); + _current_page = CLAMP(page, 1, _pdf_doc->getCatalog()->getNumPages()); + _setPreviewPage(_current_page); +} + +#ifdef HAVE_POPPLER_CAIRO +void PdfImportDialog::_onToggleImport() { + if( _importViaPoppler->get_active() ) { + hbox3->set_sensitive(false); + _localFontsCheck->set_sensitive(false); + _embedImagesCheck->set_sensitive(false); + hbox6->set_sensitive(false); + } else { + hbox3->set_sensitive(); + _localFontsCheck->set_sensitive(); + _embedImagesCheck->set_sensitive(); + hbox6->set_sensitive(); + } +} +#endif + + +#ifdef HAVE_POPPLER_CAIRO +/** + * \brief Copies image data from a Cairo surface to a pixbuf + * + * Borrowed from libpoppler, from the file poppler-page.cc + * Copyright (C) 2005, Red Hat, Inc. + * + */ +static void copy_cairo_surface_to_pixbuf (cairo_surface_t *surface, + unsigned char *data, + GdkPixbuf *pixbuf) +{ + int cairo_width, cairo_height, cairo_rowstride; + unsigned char *pixbuf_data, *dst, *cairo_data; + int pixbuf_rowstride, pixbuf_n_channels; + unsigned int *src; + int x, y; + + cairo_width = cairo_image_surface_get_width (surface); + cairo_height = cairo_image_surface_get_height (surface); + cairo_rowstride = cairo_width * 4; + cairo_data = data; + + pixbuf_data = gdk_pixbuf_get_pixels (pixbuf); + pixbuf_rowstride = gdk_pixbuf_get_rowstride (pixbuf); + pixbuf_n_channels = gdk_pixbuf_get_n_channels (pixbuf); + + if (cairo_width > gdk_pixbuf_get_width (pixbuf)) + cairo_width = gdk_pixbuf_get_width (pixbuf); + if (cairo_height > gdk_pixbuf_get_height (pixbuf)) + cairo_height = gdk_pixbuf_get_height (pixbuf); + for (y = 0; y < cairo_height; y++) + { + src = reinterpret_cast<unsigned int *>(cairo_data + y * cairo_rowstride); + dst = pixbuf_data + y * pixbuf_rowstride; + for (x = 0; x < cairo_width; x++) + { + dst[0] = (*src >> 16) & 0xff; + dst[1] = (*src >> 8) & 0xff; + dst[2] = (*src >> 0) & 0xff; + if (pixbuf_n_channels == 4) + dst[3] = (*src >> 24) & 0xff; + dst += pixbuf_n_channels; + src++; + } + } +} + +#endif + +bool PdfImportDialog::_onDraw(const Cairo::RefPtr<Cairo::Context>& cr) { + // Check if we have a thumbnail at all + if (!_thumb_data) { + return true; + } + + // Create the pixbuf for the thumbnail + Glib::RefPtr<Gdk::Pixbuf> thumb; + + if (_render_thumb) { + thumb = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true, + 8, _thumb_width, _thumb_height); + } else { + thumb = Gdk::Pixbuf::create_from_data(_thumb_data, Gdk::COLORSPACE_RGB, + false, 8, _thumb_width, _thumb_height, _thumb_rowstride); + } + if (!thumb) { + return true; + } + + // Set background to white + if (_render_thumb) { + thumb->fill(0xffffffff); + Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, 0); + cr->paint(); + } +#ifdef HAVE_POPPLER_CAIRO + // Copy the thumbnail image from the Cairo surface + if (_render_thumb) { + copy_cairo_surface_to_pixbuf(_cairo_surface, _thumb_data, thumb->gobj()); + } +#endif + + Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, _render_thumb ? 0 : 20); + cr->paint(); + return true; +} + +/** + * \brief Renders the given page's thumbnail using Cairo + */ +void PdfImportDialog::_setPreviewPage(int page) { + + _previewed_page = _pdf_doc->getCatalog()->getPage(page); + g_return_if_fail(_previewed_page); + // Try to get a thumbnail from the PDF if possible + if (!_render_thumb) { + if (_thumb_data) { + gfree(_thumb_data); + _thumb_data = nullptr; + } + if (!_previewed_page->loadThumb(&_thumb_data, + &_thumb_width, &_thumb_height, &_thumb_rowstride)) { + return; + } + // Redraw preview area + _previewArea->set_size_request(_thumb_width, _thumb_height + 20); + _previewArea->queue_draw(); + return; + } +#ifdef HAVE_POPPLER_CAIRO + // Get page size by accounting for rotation + double width, height; + int rotate = _previewed_page->getRotate(); + if ( rotate == 90 || rotate == 270 ) { + height = _previewed_page->getCropWidth(); + width = _previewed_page->getCropHeight(); + } else { + width = _previewed_page->getCropWidth(); + height = _previewed_page->getCropHeight(); + } + // Calculate the needed scaling for the page + double scale_x = (double)_preview_width / width; + double scale_y = (double)_preview_height / height; + double scale_factor = ( scale_x > scale_y ) ? scale_y : scale_x; + // Create new Cairo surface + _thumb_width = (int)ceil( width * scale_factor ); + _thumb_height = (int)ceil( height * scale_factor ); + _thumb_rowstride = _thumb_width * 4; + if (_thumb_data) { + gfree(_thumb_data); + } + _thumb_data = reinterpret_cast<unsigned char *>(gmalloc(_thumb_rowstride * _thumb_height)); + if (_cairo_surface) { + cairo_surface_destroy(_cairo_surface); + } + _cairo_surface = cairo_image_surface_create_for_data(_thumb_data, + CAIRO_FORMAT_ARGB32, _thumb_width, _thumb_height, _thumb_rowstride); + cairo_t *cr = cairo_create(_cairo_surface); + cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); // Set fill color to white + cairo_paint(cr); // Clear it + cairo_scale(cr, scale_factor, scale_factor); // Use Cairo for resizing the image + // Render page + if (_poppler_doc != NULL) { + PopplerPage *poppler_page = poppler_document_get_page(_poppler_doc, page-1); + poppler_page_render(poppler_page, cr); + g_object_unref(G_OBJECT(poppler_page)); + } + // Clean up + cairo_destroy(cr); + // Redraw preview area + _previewArea->set_size_request(_preview_width, _preview_height); + _previewArea->queue_draw(); +#endif +} + +//////////////////////////////////////////////////////////////////////////////// + +#ifdef HAVE_POPPLER_CAIRO +/// helper method +static cairo_status_t + _write_ustring_cb(void *closure, const unsigned char *data, unsigned int length) +{ + Glib::ustring* stream = static_cast<Glib::ustring*>(closure); + stream->append(reinterpret_cast<const char*>(data), length); + + return CAIRO_STATUS_SUCCESS; +} +#endif + +/** + * Parses the selected page of the given PDF document using PdfParser. + */ +SPDocument * +PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { + + // Initialize the globalParams variable for poppler + if (!globalParams) { +#ifdef ENABLE_OSX_APP_LOCATIONS + // + // data files for poppler are not relocatable (loaded from + // path defined at build time). This fails to work with relocatable + // application bundles for OS X. + // + // Workaround: + // 1. define $POPPLER_DATADIR env variable in app launcher script + // 2. pass custom $POPPLER_DATADIR via poppler's GlobalParams() + // + // relevant poppler commit: + // <http://cgit.freedesktop.org/poppler/poppler/commit/?id=869584a84eed507775ff1c3183fe484c14b6f77b> + // + // FIXES: Inkscape bug #956282, #1264793 + // TODO: report RFE upstream (full relocation support for OS X packaging) + // + gchar const *poppler_datadir = g_getenv("POPPLER_DATADIR"); + if (poppler_datadir != NULL) { + globalParams = _POPPLER_NEW_GLOBAL_PARAMS(poppler_datadir); + } else { + globalParams = _POPPLER_NEW_GLOBAL_PARAMS(); + } +#else + globalParams = _POPPLER_NEW_GLOBAL_PARAMS(); +#endif // ENABLE_OSX_APP_LOCATIONS + } + + + // Open the file using poppler + // PDFDoc is from poppler. PDFDoc is used for preview and for native import. + std::shared_ptr<PDFDoc> pdf_doc; + +#ifndef _WIN32 + // poppler does not use glib g_open. So on win32 we must use unicode call. code was copied from + // glib gstdio.c + GooString *filename_goo = new GooString(uri); + pdf_doc = std::make_shared<PDFDoc>(filename_goo, nullptr, nullptr, nullptr); // TODO: Could ask for password + //delete filename_goo; +#else + wchar_t *wfilename = reinterpret_cast<wchar_t*>(g_utf8_to_utf16 (uri, -1, NULL, NULL, NULL)); + + if (wfilename == NULL) { + return NULL; + } + + pdf_doc = std::make_shared<PDFDoc>(wfilename, wcslen(wfilename), nullptr, nullptr, nullptr); // TODO: Could ask for password + g_free (wfilename); +#endif + + if (!pdf_doc->isOk()) { + int error = pdf_doc->getErrorCode(); + if (error == errEncrypted) { + g_message("Document is encrypted."); + } else if (error == errOpenFile) { + g_message("couldn't open the PDF file."); + } else if (error == errBadCatalog) { + g_message("couldn't read the page catalog."); + } else if (error == errDamaged) { + g_message("PDF file was damaged and couldn't be repaired."); + } else if (error == errHighlightFile) { + g_message("nonexistent or invalid highlight file."); + } else if (error == errBadPrinter) { + g_message("invalid printer."); + } else if (error == errPrinting) { + g_message("Error during printing."); + } else if (error == errPermission) { + g_message("PDF file does not allow that operation."); + } else if (error == errBadPageNum) { + g_message("invalid page number."); + } else if (error == errFileIO) { + g_message("file IO error."); + } else { + g_message("Failed to load document from data (error %d)", error); + } + + return nullptr; + } + + + std::unique_ptr<PdfImportDialog> dlg; + if (INKSCAPE.use_gui()) { + dlg.reset(new PdfImportDialog(pdf_doc, uri)); + if (!dlg->showDialog()) { + throw Input::open_cancelled(); + } + } + + // Get options + int page_num = 1; + bool is_importvia_poppler = false; + if (dlg) { + page_num = dlg->getSelectedPage(); +#ifdef HAVE_POPPLER_CAIRO + is_importvia_poppler = dlg->getImportMethod(); + // printf("PDF import via %s.\n", is_importvia_poppler ? "poppler" : "native"); +#endif + } else { + page_num = INKSCAPE.get_pdf_page(); +#ifdef HAVE_POPPLER_CAIRO + is_importvia_poppler = INKSCAPE.get_pdf_poppler(); +#endif + } + + // Create Inkscape document from file + SPDocument *doc = nullptr; + bool saved = false; + if(!is_importvia_poppler) + { + // native importer + + // Check page exists + Catalog *catalog = pdf_doc->getCatalog(); + int const num_pages = catalog->getNumPages(); + sanitize_page_number(page_num, num_pages); + Page *page = catalog->getPage(page_num); + if (!page) { + std::cerr << "PDFInput::open: error opening page " << page_num << std::endl; + return nullptr; + } + + // Create document + doc = SPDocument::createNewDoc(nullptr, true, true); + saved = DocumentUndo::getUndoSensitive(doc); + DocumentUndo::setUndoSensitive(doc, false); // No need to undo in this temporary document + + // Create builder + gchar *docname = g_path_get_basename(uri); + gchar *dot = g_strrstr(docname, "."); + if (dot) { + *dot = 0; + } + SvgBuilder *builder = new SvgBuilder(doc, docname, pdf_doc->getXRef()); + + // Get preferences + Inkscape::XML::Node *prefs = builder->getPreferences(); + if (dlg) + dlg->getImportSettings(prefs); + + // Apply crop settings + _POPPLER_CONST PDFRectangle *clipToBox = nullptr; + double crop_setting = -1.0; + sp_repr_get_double(prefs, "cropTo", &crop_setting); + + if ( crop_setting >= 0.0 ) { // Do page clipping + int crop_choice = (int)crop_setting; + switch (crop_choice) { + case 0: // Media box + clipToBox = page->getMediaBox(); + break; + case 1: // Crop box + clipToBox = page->getCropBox(); + break; + case 2: // Bleed box + clipToBox = page->getBleedBox(); + break; + case 3: // Trim box + clipToBox = page->getTrimBox(); + break; + case 4: // Art box + clipToBox = page->getArtBox(); + break; + default: + break; + } + } + + // Create parser (extension/internal/pdfinput/pdf-parser.h) + PdfParser *pdf_parser = new PdfParser(pdf_doc->getXRef(), builder, page_num-1, page->getRotate(), + page->getResourceDict(), page->getCropBox(), clipToBox); + + // Set up approximation precision for parser. Used for converting Mesh Gradients into tiles. + double color_delta = 2.0; + sp_repr_get_double(prefs, "approximationPrecision", &color_delta); + if ( color_delta <= 0.0 ) { + color_delta = 1.0 / 2.0; + } else { + color_delta = 1.0 / color_delta; + } + for ( int i = 1 ; i <= pdfNumShadingTypes ; i++ ) { + pdf_parser->setApproximationPrecision(i, color_delta, 6); + } + + // Parse the document structure +#if defined(POPPLER_NEW_OBJECT_API) + Object obj = page->getContents(); +#else + Object obj; + page->getContents(&obj); +#endif + if (!obj.isNull()) { + pdf_parser->parse(&obj); + } + + // Cleanup +#if !defined(POPPLER_NEW_OBJECT_API) + obj.free(); +#endif + delete pdf_parser; + delete builder; + g_free(docname); + } + else + { +#ifdef HAVE_POPPLER_CAIRO + // the poppler import + + Glib::ustring full_path = uri; + if (!Glib::path_is_absolute(uri)) { + full_path = Glib::build_filename(Glib::get_current_dir(),uri); + } + Glib::ustring full_uri = Glib::filename_to_uri(full_path); + + GError *error = NULL; + /// @todo handle password + /// @todo check if win32 unicode needs special attention + PopplerDocument* document = poppler_document_new_from_file(full_uri.c_str(), NULL, &error); + PopplerPage* page = nullptr; + + if(error != NULL) { + std::cerr << "PDFInput::open: error opening document: " << full_uri << std::endl; + g_error_free (error); + } + + if (document) { + int const num_pages = poppler_document_get_n_pages(document); + sanitize_page_number(page_num, num_pages); + page = poppler_document_get_page(document, page_num - 1); + } + + if (page) { + double width, height; + poppler_page_get_size(page, &width, &height); + + Glib::ustring output; + cairo_surface_t* surface = cairo_svg_surface_create_for_stream(Inkscape::Extension::Internal::_write_ustring_cb, + &output, width, height); + + // This magical function results in more fine-grain fallbacks. In particular, a mesh + // gradient won't necessarily result in the whole PDF being rasterized. Of course, SVG + // 1.2 never made it as a standard, but hey, we'll take what we can get. This trick was + // found by examining the 'pdftocairo' code. + cairo_svg_surface_restrict_to_version( surface, CAIRO_SVG_VERSION_1_2 ); + + cairo_t* cr = cairo_create(surface); + + poppler_page_render_for_printing(page, cr); + cairo_show_page(cr); + + cairo_destroy(cr); + cairo_surface_destroy(surface); + + doc = SPDocument::createNewDocFromMem(output.c_str(), output.length(), TRUE); + } else if (document) { + std::cerr << "PDFInput::open: error opening page " << page_num << " of document: " << full_uri << std::endl; + } + + // Cleanup + if (document) { + g_object_unref(G_OBJECT(document)); + if (page) { + g_object_unref(G_OBJECT(page)); + } + } + + if (!doc) { + return nullptr; + } + + saved = DocumentUndo::getUndoSensitive(doc); + DocumentUndo::setUndoSensitive(doc, false); // No need to undo in this temporary document +#endif + } + + // Set viewBox if it doesn't exist + if (!doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit()))); + } + + // Restore undo + DocumentUndo::setUndoSensitive(doc, saved); + + return doc; +} + +#include "../clear-n_.h" + +void PdfInput::init() { + /* PDF in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("PDF Input") "</name>\n" + "<id>org.inkscape.input.pdf</id>\n" + "<input>\n" + "<extension>.pdf</extension>\n" + "<mimetype>application/pdf</mimetype>\n" + "<filetypename>" N_("Portable Document Format (*.pdf)") "</filetypename>\n" + "<filetypetooltip>" N_("Portable Document Format") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new PdfInput()); + + /* AI in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("AI Input") "</name>\n" + "<id>org.inkscape.input.ai</id>\n" + "<input>\n" + "<extension>.ai</extension>\n" + "<mimetype>image/x-adobe-illustrator</mimetype>\n" + "<filetypename>" N_("Adobe Illustrator 9.0 and above (*.ai)") "</filetypename>\n" + "<filetypetooltip>" N_("Open files saved in Adobe Illustrator 9.0 and newer versions") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new PdfInput()); +} // init + +} } } /* namespace Inkscape, Extension, Implementation */ + +#endif /* HAVE_POPPLER */ + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/pdfinput/pdf-input.h b/src/extension/internal/pdfinput/pdf-input.h new file mode 100644 index 0000000..66f750d --- /dev/null +++ b/src/extension/internal/pdfinput/pdf-input.h @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_EXTENSION_INTERNAL_PDFINPUT_H +#define SEEN_EXTENSION_INTERNAL_PDFINPUT_H + +/* + * Authors: + * miklos erdelyi + * + * Copyright (C) 2007 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 HAVE_POPPLER +#include "poppler-transition-api.h" + +#include <gtkmm/dialog.h> + +#include "../../implementation/implementation.h" + +#ifdef HAVE_POPPLER_CAIRO +struct _PopplerDocument; +typedef struct _PopplerDocument PopplerDocument; +#endif + +struct _GdkEventExpose; +typedef _GdkEventExpose GdkEventExpose; + +class Page; +class PDFDoc; + +namespace Gtk { + class Button; + class CheckButton; + class ComboBoxText; + class DrawingArea; + class Frame; + class HBox; + class Scale; + class RadioButton; + class VBox; + class Label; +} + +namespace Inkscape { + +namespace UI { +namespace Widget { + class SpinButton; + class Frame; +} +} + +namespace Extension { +namespace Internal { + +/** + * PDF import using libpoppler. + */ +class PdfImportDialog : public Gtk::Dialog +{ +public: + PdfImportDialog(std::shared_ptr<PDFDoc> doc, const gchar *uri); + ~PdfImportDialog() override; + + bool showDialog(); + int getSelectedPage(); + bool getImportMethod(); + void getImportSettings(Inkscape::XML::Node *prefs); + +private: + void _setPreviewPage(int page); + + // Signal handlers + bool _onDraw(const Cairo::RefPtr<Cairo::Context>& cr); + void _onPageNumberChanged(); + void _onToggleCropping(); + void _onPrecisionChanged(); +#ifdef HAVE_POPPLER_CAIRO + void _onToggleImport(); +#endif + + class Gtk::Button * cancelbutton; + class Gtk::Button * okbutton; + class Gtk::Label * _labelSelect; + class Inkscape::UI::Widget::SpinButton * _pageNumberSpin; + class Gtk::Label * _labelTotalPages; + class Gtk::HBox * hbox2; + class Gtk::CheckButton * _cropCheck; + class Gtk::ComboBoxText * _cropTypeCombo; + class Gtk::HBox * hbox3; + class Gtk::VBox * vbox2; + class Inkscape::UI::Widget::Frame * _pageSettingsFrame; + class Gtk::Label * _labelPrecision; + class Gtk::Label * _labelPrecisionWarning; +#ifdef HAVE_POPPLER_CAIRO + class Gtk::RadioButton * _importViaPoppler; // Use poppler_cairo importing + class Gtk::Label * _labelViaPoppler; + class Gtk::RadioButton * _importViaInternal; // Use native (poppler based) importing + class Gtk::Label * _labelViaInternal; +#endif + Gtk::Scale * _fallbackPrecisionSlider; + Glib::RefPtr<Gtk::Adjustment> _fallbackPrecisionSlider_adj; + class Gtk::Label * _labelPrecisionComment; + class Gtk::HBox * hbox6; +#if 0 + class Gtk::Label * _labelText; + class Gtk::ComboBoxText * _textHandlingCombo; + class Gtk::HBox * hbox5; +#endif + class Gtk::CheckButton * _localFontsCheck; + class Gtk::CheckButton * _embedImagesCheck; + class Gtk::VBox * vbox3; + class Inkscape::UI::Widget::Frame * _importSettingsFrame; + class Gtk::VBox * vbox1; + class Gtk::DrawingArea * _previewArea; + class Gtk::HBox * hbox1; + + std::shared_ptr<PDFDoc> _pdf_doc; // Document to be imported + int _current_page; // Current selected page + Page *_previewed_page; // Currently previewed page + unsigned char *_thumb_data; // Thumbnail image data + int _thumb_width, _thumb_height; // Thumbnail size + int _thumb_rowstride; + int _preview_width, _preview_height; // Size of the preview area + bool _render_thumb; // Whether we can/shall render thumbnails +#ifdef HAVE_POPPLER_CAIRO + cairo_surface_t *_cairo_surface; + PopplerDocument *_poppler_doc; +#endif +}; + + +class PdfInput: public Inkscape::Extension::Implementation::Implementation { + PdfInput () = default;; +public: + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ) override; + static void init( ); +}; + +} // namespace Implementation +} // namespace Extension +} // namespace Inkscape + +#endif // HAVE_POPPLER + +#endif // SEEN_EXTENSION_INTERNAL_PDFINPUT_H + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/pdfinput/pdf-parser.cpp b/src/extension/internal/pdfinput/pdf-parser.cpp new file mode 100644 index 0000000..73867e9 --- /dev/null +++ b/src/extension/internal/pdfinput/pdf-parser.cpp @@ -0,0 +1,3398 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * PDF parsing using libpoppler. + *//* + * Authors: + * Derived from poppler's Gfx.cc, which was derived from Xpdf by 1996-2003 Glyph & Cog, LLC + * Jon A. Cruz <jon@joncruz.org> + * + * 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 HAVE_POPPLER + +#ifdef USE_GCC_PRAGMAS +#pragma implementation +#endif + +#include <cstdlib> +#include <cstdio> +#include <cstddef> +#include <cstring> +#include <cmath> + +#include "svg-builder.h" +#include "Gfx.h" +#include "pdf-parser.h" +#include "util/units.h" + +#include "glib/poppler-features.h" +#include "goo/gmem.h" +#include "goo/GooString.h" +#include "GlobalParams.h" +#include "CharTypes.h" +#include "Object.h" +#include "Array.h" +#include "Dict.h" +#include "Stream.h" +#include "Lexer.h" +#include "Parser.h" +#include "GfxFont.h" +#include "GfxState.h" +#include "OutputDev.h" +#include "Page.h" +#include "Annot.h" +#include "Error.h" + +// the MSVC math.h doesn't define this +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +//------------------------------------------------------------------------ +// constants +//------------------------------------------------------------------------ + +// Default max delta allowed in any color component for a shading fill. +#define defaultShadingColorDelta (dblToCol( 1 / 2.0 )) + +// Default max recursive depth for a shading fill. +#define defaultShadingMaxDepth 6 + +// Max number of operators kept in the history list. +#define maxOperatorHistoryDepth 16 + +//------------------------------------------------------------------------ +// Operator table +//------------------------------------------------------------------------ + +PdfOperator PdfParser::opTab[] = { + {"\"", 3, {tchkNum, tchkNum, tchkString}, + &PdfParser::opMoveSetShowText}, + {"'", 1, {tchkString}, + &PdfParser::opMoveShowText}, + {"B", 0, {tchkNone}, + &PdfParser::opFillStroke}, + {"B*", 0, {tchkNone}, + &PdfParser::opEOFillStroke}, + {"BDC", 2, {tchkName, tchkProps}, + &PdfParser::opBeginMarkedContent}, + {"BI", 0, {tchkNone}, + &PdfParser::opBeginImage}, + {"BMC", 1, {tchkName}, + &PdfParser::opBeginMarkedContent}, + {"BT", 0, {tchkNone}, + &PdfParser::opBeginText}, + {"BX", 0, {tchkNone}, + &PdfParser::opBeginIgnoreUndef}, + {"CS", 1, {tchkName}, + &PdfParser::opSetStrokeColorSpace}, + {"DP", 2, {tchkName, tchkProps}, + &PdfParser::opMarkPoint}, + {"Do", 1, {tchkName}, + &PdfParser::opXObject}, + {"EI", 0, {tchkNone}, + &PdfParser::opEndImage}, + {"EMC", 0, {tchkNone}, + &PdfParser::opEndMarkedContent}, + {"ET", 0, {tchkNone}, + &PdfParser::opEndText}, + {"EX", 0, {tchkNone}, + &PdfParser::opEndIgnoreUndef}, + {"F", 0, {tchkNone}, + &PdfParser::opFill}, + {"G", 1, {tchkNum}, + &PdfParser::opSetStrokeGray}, + {"ID", 0, {tchkNone}, + &PdfParser::opImageData}, + {"J", 1, {tchkInt}, + &PdfParser::opSetLineCap}, + {"K", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetStrokeCMYKColor}, + {"M", 1, {tchkNum}, + &PdfParser::opSetMiterLimit}, + {"MP", 1, {tchkName}, + &PdfParser::opMarkPoint}, + {"Q", 0, {tchkNone}, + &PdfParser::opRestore}, + {"RG", 3, {tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetStrokeRGBColor}, + {"S", 0, {tchkNone}, + &PdfParser::opStroke}, + {"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetStrokeColor}, + {"SCN", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN}, + &PdfParser::opSetStrokeColorN}, + {"T*", 0, {tchkNone}, + &PdfParser::opTextNextLine}, + {"TD", 2, {tchkNum, tchkNum}, + &PdfParser::opTextMoveSet}, + {"TJ", 1, {tchkArray}, + &PdfParser::opShowSpaceText}, + {"TL", 1, {tchkNum}, + &PdfParser::opSetTextLeading}, + {"Tc", 1, {tchkNum}, + &PdfParser::opSetCharSpacing}, + {"Td", 2, {tchkNum, tchkNum}, + &PdfParser::opTextMove}, + {"Tf", 2, {tchkName, tchkNum}, + &PdfParser::opSetFont}, + {"Tj", 1, {tchkString}, + &PdfParser::opShowText}, + {"Tm", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opSetTextMatrix}, + {"Tr", 1, {tchkInt}, + &PdfParser::opSetTextRender}, + {"Ts", 1, {tchkNum}, + &PdfParser::opSetTextRise}, + {"Tw", 1, {tchkNum}, + &PdfParser::opSetWordSpacing}, + {"Tz", 1, {tchkNum}, + &PdfParser::opSetHorizScaling}, + {"W", 0, {tchkNone}, + &PdfParser::opClip}, + {"W*", 0, {tchkNone}, + &PdfParser::opEOClip}, + {"b", 0, {tchkNone}, + &PdfParser::opCloseFillStroke}, + {"b*", 0, {tchkNone}, + &PdfParser::opCloseEOFillStroke}, + {"c", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opCurveTo}, + {"cm", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opConcat}, + {"cs", 1, {tchkName}, + &PdfParser::opSetFillColorSpace}, + {"d", 2, {tchkArray, tchkNum}, + &PdfParser::opSetDash}, + {"d0", 2, {tchkNum, tchkNum}, + &PdfParser::opSetCharWidth}, + {"d1", 6, {tchkNum, tchkNum, tchkNum, tchkNum, + tchkNum, tchkNum}, + &PdfParser::opSetCacheDevice}, + {"f", 0, {tchkNone}, + &PdfParser::opFill}, + {"f*", 0, {tchkNone}, + &PdfParser::opEOFill}, + {"g", 1, {tchkNum}, + &PdfParser::opSetFillGray}, + {"gs", 1, {tchkName}, + &PdfParser::opSetExtGState}, + {"h", 0, {tchkNone}, + &PdfParser::opClosePath}, + {"i", 1, {tchkNum}, + &PdfParser::opSetFlat}, + {"j", 1, {tchkInt}, + &PdfParser::opSetLineJoin}, + {"k", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetFillCMYKColor}, + {"l", 2, {tchkNum, tchkNum}, + &PdfParser::opLineTo}, + {"m", 2, {tchkNum, tchkNum}, + &PdfParser::opMoveTo}, + {"n", 0, {tchkNone}, + &PdfParser::opEndPath}, + {"q", 0, {tchkNone}, + &PdfParser::opSave}, + {"re", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opRectangle}, + {"rg", 3, {tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetFillRGBColor}, + {"ri", 1, {tchkName}, + &PdfParser::opSetRenderingIntent}, + {"s", 0, {tchkNone}, + &PdfParser::opCloseStroke}, + {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opSetFillColor}, + {"scn", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN, tchkSCN, tchkSCN, tchkSCN, + tchkSCN}, + &PdfParser::opSetFillColorN}, + {"sh", 1, {tchkName}, + &PdfParser::opShFill}, + {"v", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opCurveTo1}, + {"w", 1, {tchkNum}, + &PdfParser::opSetLineWidth}, + {"y", 4, {tchkNum, tchkNum, tchkNum, tchkNum}, + &PdfParser::opCurveTo2} +}; + +#define numOps (sizeof(opTab) / sizeof(PdfOperator)) + +namespace { + +GfxPatch blankPatch() +{ + GfxPatch patch; + memset(&patch, 0, sizeof(patch)); // quick-n-dirty + return patch; +} + +} // namespace + +//------------------------------------------------------------------------ +// ClipHistoryEntry +//------------------------------------------------------------------------ + +class ClipHistoryEntry { +public: + + ClipHistoryEntry(GfxPath *clipPath = nullptr, GfxClipType clipType = clipNormal); + virtual ~ClipHistoryEntry(); + + // Manipulate clip path stack + ClipHistoryEntry *save(); + ClipHistoryEntry *restore(); + GBool hasSaves() { return saved != nullptr; } + void setClip(_POPPLER_CONST_83 GfxPath *newClipPath, GfxClipType newClipType = clipNormal); + GfxPath *getClipPath() { return clipPath; } + GfxClipType getClipType() { return clipType; } + +private: + + ClipHistoryEntry *saved; // next clip path on stack + + GfxPath *clipPath; // used as the path to be filled for an 'sh' operator + GfxClipType clipType; + + ClipHistoryEntry(ClipHistoryEntry *other); +}; + +//------------------------------------------------------------------------ +// PdfParser +//------------------------------------------------------------------------ + +PdfParser::PdfParser(XRef *xrefA, + Inkscape::Extension::Internal::SvgBuilder *builderA, + int /*pageNum*/, + int rotate, + Dict *resDict, + _POPPLER_CONST PDFRectangle *box, + _POPPLER_CONST PDFRectangle *cropBox) : + xref(xrefA), + builder(builderA), + subPage(gFalse), + printCommands(false), + res(new GfxResources(xref, resDict, nullptr)), // start the resource stack + state(new GfxState(72.0, 72.0, box, rotate, gTrue)), + fontChanged(gFalse), + clip(clipNone), + ignoreUndef(0), + baseMatrix(), + formDepth(0), + parser(nullptr), + colorDeltas(), + maxDepths(), + clipHistory(new ClipHistoryEntry()), + operatorHistory(nullptr) +{ + setDefaultApproximationPrecision(); + builder->setDocumentSize(Inkscape::Util::Quantity::convert(state->getPageWidth(), "pt", "px"), + Inkscape::Util::Quantity::convert(state->getPageHeight(), "pt", "px")); + + const double *ctm = state->getCTM(); + double scaledCTM[6]; + for (int i = 0; i < 6; ++i) { + baseMatrix[i] = ctm[i]; + scaledCTM[i] = Inkscape::Util::Quantity::convert(ctm[i], "pt", "px"); + } + saveState(); + builder->setTransform((double*)&scaledCTM); + formDepth = 0; + + // set crop box + if (cropBox) { + if (printCommands) + printf("cropBox: %f %f %f %f\n", cropBox->x1, cropBox->y1, cropBox->x2, cropBox->y2); + // do not clip if it's not needed + if (cropBox->x1 != 0.0 || cropBox->y1 != 0.0 || + cropBox->x2 != state->getPageWidth() || cropBox->y2 != state->getPageHeight()) { + + state->moveTo(cropBox->x1, cropBox->y1); + state->lineTo(cropBox->x2, cropBox->y1); + state->lineTo(cropBox->x2, cropBox->y2); + state->lineTo(cropBox->x1, cropBox->y2); + state->closePath(); + state->clip(); + clipHistory->setClip(state->getPath(), clipNormal); + builder->setClipPath(state); + state->clearPath(); + } + } + pushOperator("startPage"); +} + +PdfParser::PdfParser(XRef *xrefA, + Inkscape::Extension::Internal::SvgBuilder *builderA, + Dict *resDict, + _POPPLER_CONST PDFRectangle *box) : + xref(xrefA), + builder(builderA), + subPage(gTrue), + printCommands(false), + res(new GfxResources(xref, resDict, nullptr)), // start the resource stack + state(new GfxState(72, 72, box, 0, gFalse)), + fontChanged(gFalse), + clip(clipNone), + ignoreUndef(0), + baseMatrix(), + formDepth(0), + parser(nullptr), + colorDeltas(), + maxDepths(), + clipHistory(new ClipHistoryEntry()), + operatorHistory(nullptr) +{ + setDefaultApproximationPrecision(); + + for (int i = 0; i < 6; ++i) { + baseMatrix[i] = state->getCTM()[i]; + } + formDepth = 0; +} + +PdfParser::~PdfParser() { + while(operatorHistory) { + OpHistoryEntry *tmp = operatorHistory->next; + delete operatorHistory; + operatorHistory = tmp; + } + + while (state && state->hasSaves()) { + restoreState(); + } + + if (!subPage) { + //out->endPage(); + } + + while (res) { + popResources(); + } + + if (state) { + delete state; + state = nullptr; + } + + if (clipHistory) { + delete clipHistory; + clipHistory = nullptr; + } +} + +void PdfParser::parse(Object *obj, GBool topLevel) { + Object obj2; + + if (obj->isArray()) { + for (int i = 0; i < obj->arrayGetLength(); ++i) { + _POPPLER_CALL_ARGS(obj2, obj->arrayGet, i); + if (!obj2.isStream()) { + error(errInternal, -1, "Weird page contents"); + _POPPLER_FREE(obj2); + return; + } + _POPPLER_FREE(obj2); + } + } else if (!obj->isStream()) { + error(errInternal, -1, "Weird page contents"); + return; + } + parser = new _POPPLER_NEW_PARSER(xref, obj); + go(topLevel); + delete parser; + parser = nullptr; +} + +void PdfParser::go(GBool /*topLevel*/) +{ + Object obj; + Object args[maxArgs]; + + // scan a sequence of objects + int numArgs = 0; + _POPPLER_CALL(obj, parser->getObj); + while (!obj.isEOF()) { + + // got a command - execute it + if (obj.isCmd()) { + if (printCommands) { + obj.print(stdout); + for (int i = 0; i < numArgs; ++i) { + printf(" "); + args[i].print(stdout); + } + printf("\n"); + fflush(stdout); + } + + // Run the operation + execOp(&obj, args, numArgs); + +#if !defined(POPPLER_NEW_OBJECT_API) + _POPPLER_FREE(obj); + for (int i = 0; i < numArgs; ++i) + _POPPLER_FREE(args[i]); +#endif + numArgs = 0; + + // got an argument - save it + } else if (numArgs < maxArgs) { + args[numArgs++] = std::move(obj); + + // too many arguments - something is wrong + } else { + error(errSyntaxError, getPos(), "Too many args in content stream"); + if (printCommands) { + printf("throwing away arg: "); + obj.print(stdout); + printf("\n"); + fflush(stdout); + } + _POPPLER_FREE(obj); + } + + // grab the next object + _POPPLER_CALL(obj, parser->getObj); + } + _POPPLER_FREE(obj); + + // args at end with no command + if (numArgs > 0) { + error(errSyntaxError, getPos(), "Leftover args in content stream"); + if (printCommands) { + printf("%d leftovers:", numArgs); + for (int i = 0; i < numArgs; ++i) { + printf(" "); + args[i].print(stdout); + } + printf("\n"); + fflush(stdout); + } +#if !defined(POPPLER_NEW_OBJECT_API) + for (int i = 0; i < numArgs; ++i) + _POPPLER_FREE(args[i]); +#endif + } +} + +void PdfParser::pushOperator(const char *name) +{ + OpHistoryEntry *newEntry = new OpHistoryEntry; + newEntry->name = name; + newEntry->state = nullptr; + newEntry->depth = (operatorHistory != nullptr ? (operatorHistory->depth+1) : 0); + newEntry->next = operatorHistory; + operatorHistory = newEntry; + + // Truncate list if needed + if (operatorHistory->depth > maxOperatorHistoryDepth) { + OpHistoryEntry *curr = operatorHistory; + OpHistoryEntry *prev = nullptr; + while (curr && curr->next != nullptr) { + curr->depth--; + prev = curr; + curr = curr->next; + } + if (prev) { + if (curr->state != nullptr) + delete curr->state; + delete curr; + prev->next = nullptr; + } + } +} + +const char *PdfParser::getPreviousOperator(unsigned int look_back) { + OpHistoryEntry *prev = nullptr; + if (operatorHistory != nullptr && look_back > 0) { + prev = operatorHistory->next; + while (--look_back > 0 && prev != nullptr) { + prev = prev->next; + } + } + if (prev != nullptr) { + return prev->name; + } else { + return ""; + } +} + +void PdfParser::execOp(Object *cmd, Object args[], int numArgs) { + PdfOperator *op; + const char *name; + Object *argPtr; + int i; + + // find operator + name = cmd->getCmd(); + if (!(op = findOp(name))) { + if (ignoreUndef == 0) + error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); + return; + } + + // type check args + argPtr = args; + if (op->numArgs >= 0) { + if (numArgs < op->numArgs) { + error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:d}' operator", numArgs, name); + return; + } + if (numArgs > op->numArgs) { +#if 0 + error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name); +#endif + argPtr += numArgs - op->numArgs; + numArgs = op->numArgs; + } + } else { + if (numArgs > -op->numArgs) { + error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", + numArgs, name); + return; + } + } + for (i = 0; i < numArgs; ++i) { + if (!checkArg(&argPtr[i], op->tchk[i])) { + error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", + i, name, argPtr[i].getTypeName()); + return; + } + } + + // add to history + pushOperator((char*)&op->name); + + // do it + (this->*op->func)(argPtr, numArgs); +} + +PdfOperator* PdfParser::findOp(const char *name) { + int a = -1; + int b = numOps; + int cmp = -1; + // invariant: opTab[a] < name < opTab[b] + while (b - a > 1) { + const int m = (a + b) / 2; + cmp = strcmp(opTab[m].name, name); + if (cmp < 0) + a = m; + else if (cmp > 0) + b = m; + else + a = b = m; + } + if (cmp != 0) + return nullptr; + return &opTab[a]; +} + +GBool PdfParser::checkArg(Object *arg, TchkType type) { + switch (type) { + case tchkBool: return arg->isBool(); + case tchkInt: return arg->isInt(); + case tchkNum: return arg->isNum(); + case tchkString: return arg->isString(); + case tchkName: return arg->isName(); + case tchkArray: return arg->isArray(); + case tchkProps: return arg->isDict() || arg->isName(); + case tchkSCN: return arg->isNum() || arg->isName(); + case tchkNone: return gFalse; + } + return gFalse; +} + +int PdfParser::getPos() { + return parser ? parser->getPos() : -1; +} + +//------------------------------------------------------------------------ +// graphics state operators +//------------------------------------------------------------------------ + +void PdfParser::opSave(Object /*args*/[], int /*numArgs*/) +{ + saveState(); +} + +void PdfParser::opRestore(Object /*args*/[], int /*numArgs*/) +{ + restoreState(); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opConcat(Object args[], int /*numArgs*/) +{ + state->concatCTM(args[0].getNum(), args[1].getNum(), + args[2].getNum(), args[3].getNum(), + args[4].getNum(), args[5].getNum()); + const char *prevOp = getPreviousOperator(); + double a0 = args[0].getNum(); + double a1 = args[1].getNum(); + double a2 = args[2].getNum(); + double a3 = args[3].getNum(); + double a4 = args[4].getNum(); + double a5 = args[5].getNum(); + if (!strcmp(prevOp, "q")) { + builder->setTransform(a0, a1, a2, a3, a4, a5); + } else if (!strcmp(prevOp, "cm") || !strcmp(prevOp, "startPage")) { + // multiply it with the previous transform + double otherMatrix[6]; + if (!builder->getTransform(otherMatrix)) { // invalid transform + // construct identity matrix + otherMatrix[0] = otherMatrix[3] = 1.0; + otherMatrix[1] = otherMatrix[2] = otherMatrix[4] = otherMatrix[5] = 0.0; + } + double c0 = a0*otherMatrix[0] + a1*otherMatrix[2]; + double c1 = a0*otherMatrix[1] + a1*otherMatrix[3]; + double c2 = a2*otherMatrix[0] + a3*otherMatrix[2]; + double c3 = a2*otherMatrix[1] + a3*otherMatrix[3]; + double c4 = a4*otherMatrix[0] + a5*otherMatrix[2] + otherMatrix[4]; + double c5 = a4*otherMatrix[1] + a5*otherMatrix[3] + otherMatrix[5]; + builder->setTransform(c0, c1, c2, c3, c4, c5); + } else { + builder->pushGroup(); + builder->setTransform(a0, a1, a2, a3, a4, a5); + } + fontChanged = gTrue; +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetDash(Object args[], int /*numArgs*/) +{ + double *dash = nullptr; + + Array *a = args[0].getArray(); + int length = a->getLength(); + if (length != 0) { + dash = (double *)gmallocn(length, sizeof(double)); + for (int i = 0; i < length; ++i) { + Object obj; + dash[i] = _POPPLER_CALL_ARGS_DEREF(obj, a->get, i).getNum(); + _POPPLER_FREE(obj); + } + } + state->setLineDash(dash, length, args[1].getNum()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFlat(Object args[], int /*numArgs*/) +{ + state->setFlatness((int)args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetLineJoin(Object args[], int /*numArgs*/) +{ + state->setLineJoin(args[0].getInt()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetLineCap(Object args[], int /*numArgs*/) +{ + state->setLineCap(args[0].getInt()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetMiterLimit(Object args[], int /*numArgs*/) +{ + state->setMiterLimit(args[0].getNum()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetLineWidth(Object args[], int /*numArgs*/) +{ + state->setLineWidth(args[0].getNum()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) +{ + Object obj1, obj2, obj3, obj4, obj5; + Function *funcs[4] = {nullptr, nullptr, nullptr, nullptr}; + GfxColor backdropColor; + GBool haveBackdropColor = gFalse; + GBool alpha = gFalse; + + _POPPLER_CALL_ARGS(obj1, res->lookupGState, args[0].getName()); + if (obj1.isNull()) { + return; + } + if (!obj1.isDict()) { + error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type"), args[0].getName(); + _POPPLER_FREE(obj1); + return; + } + if (printCommands) { + printf(" gfx state dict: "); + obj1.print(); + printf("\n"); + } + + // transparency support: blend mode, fill/stroke opacity + if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "BM").isNull()) { + GfxBlendMode mode = gfxBlendNormal; + if (state->parseBlendMode(&obj2, &mode)) { + state->setBlendMode(mode); + } else { + error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState"); + } + } + _POPPLER_FREE(obj2); + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "ca").isNum()) { + state->setFillOpacity(obj2.getNum()); + } + _POPPLER_FREE(obj2); + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "CA").isNum()) { + state->setStrokeOpacity(obj2.getNum()); + } + _POPPLER_FREE(obj2); + + // fill/stroke overprint + GBool haveFillOP = gFalse; + if ((haveFillOP = _POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "op").isBool())) { + state->setFillOverprint(obj2.getBool()); + } + _POPPLER_FREE(obj2); + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "OP").isBool()) { + state->setStrokeOverprint(obj2.getBool()); + if (!haveFillOP) { + state->setFillOverprint(obj2.getBool()); + } + } + _POPPLER_FREE(obj2); + + // stroke adjust + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SA").isBool()) { + state->setStrokeAdjust(obj2.getBool()); + } + _POPPLER_FREE(obj2); + + // transfer function + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "TR2").isNull()) { + _POPPLER_FREE(obj2); + _POPPLER_CALL_ARGS(obj2, obj1.dictLookup, "TR"); + } + if (obj2.isName(const_cast<char*>("Default")) || + obj2.isName(const_cast<char*>("Identity"))) { + funcs[0] = funcs[1] = funcs[2] = funcs[3] = nullptr; + state->setTransfer(funcs); + } else if (obj2.isArray() && obj2.arrayGetLength() == 4) { + int pos = 4; + for (int i = 0; i < 4; ++i) { + _POPPLER_CALL_ARGS(obj3, obj2.arrayGet, i); + funcs[i] = Function::parse(&obj3); + _POPPLER_FREE(obj3); + if (!funcs[i]) { + pos = i; + break; + } + } + if (pos == 4) { + state->setTransfer(funcs); + } + } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) { + if ((funcs[0] = Function::parse(&obj2))) { + funcs[1] = funcs[2] = funcs[3] = nullptr; + state->setTransfer(funcs); + } + } else if (!obj2.isNull()) { + error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState"); + } + _POPPLER_FREE(obj2); + + // soft mask + if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SMask").isNull()) { + if (obj2.isName(const_cast<char*>("None"))) { + builder->clearSoftMask(state); + } else if (obj2.isDict()) { + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "S").isName("Alpha")) { + alpha = gTrue; + } else { // "Luminosity" + alpha = gFalse; + } + _POPPLER_FREE(obj3); + funcs[0] = nullptr; + if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "TR").isNull()) { + funcs[0] = Function::parse(&obj3); + if (funcs[0]->getInputSize() != 1 || + funcs[0]->getOutputSize() != 1) { + error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState"); + delete funcs[0]; + funcs[0] = nullptr; + } + } + _POPPLER_FREE(obj3); + if ((haveBackdropColor = _POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "BC").isArray())) { + for (int & i : backdropColor.c) { + i = 0; + } + for (int i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) { + _POPPLER_CALL_ARGS(obj4, obj3.arrayGet, i); + if (obj4.isNum()) { + backdropColor.c[i] = dblToCol(obj4.getNum()); + } + _POPPLER_FREE(obj4); + } + } + _POPPLER_FREE(obj3); + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "G").isStream()) { + if (_POPPLER_CALL_ARGS_DEREF(obj4, obj3.streamGetDict()->lookup, "Group").isDict()) { + GfxColorSpace *blendingColorSpace = nullptr; + GBool isolated = gFalse; + GBool knockout = gFalse; + if (!_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "CS").isNull()) { +#if defined(POPPLER_EVEN_NEWER_NEW_COLOR_SPACE_API) + blendingColorSpace = GfxColorSpace::parse(nullptr, &obj5, nullptr, state); +#elif defined(POPPLER_EVEN_NEWER_COLOR_SPACE_API) + blendingColorSpace = GfxColorSpace::parse(&obj5, NULL, NULL); +#else + blendingColorSpace = GfxColorSpace::parse(&obj5, NULL); +#endif + } + _POPPLER_FREE(obj5); + if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "I").isBool()) { + isolated = obj5.getBool(); + } + _POPPLER_FREE(obj5); + if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "K").isBool()) { + knockout = obj5.getBool(); + } + _POPPLER_FREE(obj5); + if (!haveBackdropColor) { + if (blendingColorSpace) { + blendingColorSpace->getDefaultColor(&backdropColor); + } else { + //~ need to get the parent or default color space (?) + for (int & i : backdropColor.c) { + i = 0; + } + } + } + doSoftMask(&obj3, alpha, blendingColorSpace, + isolated, knockout, funcs[0], &backdropColor); + if (funcs[0]) { + delete funcs[0]; + } + } else { + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); + } + _POPPLER_FREE(obj4); + } else { + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); + } + _POPPLER_FREE(obj3); + } else if (!obj2.isNull()) { + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState"); + } + } + _POPPLER_FREE(obj2); + + _POPPLER_FREE(obj1); +} + +void PdfParser::doSoftMask(Object *str, GBool alpha, + GfxColorSpace *blendingColorSpace, + GBool isolated, GBool knockout, + Function *transferFunc, GfxColor *backdropColor) { + Dict *dict, *resDict; + double m[6], bbox[4]; + Object obj1, obj2; + int i; + + // check for excessive recursion + if (formDepth > 20) { + return; + } + + // get stream dict + dict = str->streamGetDict(); + + // check form type + _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType"); + if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { + error(errSyntaxError, getPos(), "Unknown form type"); + } + _POPPLER_FREE(obj1); + + // get bounding box + _POPPLER_CALL_ARGS(obj1, dict->lookup, "BBox"); + if (!obj1.isArray()) { + _POPPLER_FREE(obj1); + error(errSyntaxError, getPos(), "Bad form bounding box"); + return; + } + for (i = 0; i < 4; ++i) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i); + bbox[i] = obj2.getNum(); + _POPPLER_FREE(obj2); + } + _POPPLER_FREE(obj1); + + // get matrix + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Matrix"); + if (obj1.isArray()) { + for (i = 0; i < 6; ++i) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i); + m[i] = obj2.getNum(); + _POPPLER_FREE(obj2); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + _POPPLER_FREE(obj1); + + // get resources + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Resources"); + resDict = obj1.isDict() ? obj1.getDict() : (Dict *)nullptr; + + // draw it + ++formDepth; + doForm1(str, resDict, m, bbox, gTrue, gTrue, + blendingColorSpace, isolated, knockout, + alpha, transferFunc, backdropColor); + --formDepth; + + if (blendingColorSpace) { + delete blendingColorSpace; + } + _POPPLER_FREE(obj1); +} + +void PdfParser::opSetRenderingIntent(Object /*args*/[], int /*numArgs*/) +{ +} + +//------------------------------------------------------------------------ +// color operators +//------------------------------------------------------------------------ + +/** + * Get a newly allocated color space instance by CS operation argument. + * + * Maintains a cache for named color spaces to avoid expensive re-parsing. + */ +GfxColorSpace *PdfParser::lookupColorSpaceCopy(Object &arg) +{ + assert(!arg.isNull()); + + char const *name = arg.isName() ? arg.getName() : nullptr; + GfxColorSpace *colorSpace = nullptr; + + if (name && (colorSpace = colorSpacesCache[name].get())) { + return colorSpace->copy(); + } + + Object *argPtr = &arg; + Object obj; + + if (name) { + _POPPLER_CALL_ARGS(obj, res->lookupColorSpace, name); + if (!obj.isNull()) { + argPtr = &obj; + } + } + +#if defined(POPPLER_EVEN_NEWER_NEW_COLOR_SPACE_API) + colorSpace = GfxColorSpace::parse(res, argPtr, nullptr, state); +#elif defined(POPPLER_EVEN_NEWER_COLOR_SPACE_API) + colorSpace = GfxColorSpace::parse(argPtr, nullptr, nullptr); +#else + colorSpace = GfxColorSpace::parse(argPtr, nullptr); +#endif + + _POPPLER_FREE(obj); + + if (name && colorSpace) { + colorSpacesCache[name].reset(colorSpace->copy()); + } + + return colorSpace; +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillGray(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setFillPattern(nullptr); + state->setFillColorSpace(new GfxDeviceGrayColorSpace()); + color.c[0] = dblToCol(args[0].getNum()); + state->setFillColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeGray(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setStrokePattern(nullptr); + state->setStrokeColorSpace(new GfxDeviceGrayColorSpace()); + color.c[0] = dblToCol(args[0].getNum()); + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillCMYKColor(Object args[], int /*numArgs*/) +{ + GfxColor color; + int i; + + state->setFillPattern(nullptr); + state->setFillColorSpace(new GfxDeviceCMYKColorSpace()); + for (i = 0; i < 4; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setFillColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeCMYKColor(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setStrokePattern(nullptr); + state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace()); + for (int i = 0; i < 4; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillRGBColor(Object args[], int /*numArgs*/) +{ + GfxColor color; + + state->setFillPattern(nullptr); + state->setFillColorSpace(new GfxDeviceRGBColorSpace()); + for (int i = 0; i < 3; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setFillColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeRGBColor(Object args[], int /*numArgs*/) { + GfxColor color; + + state->setStrokePattern(nullptr); + state->setStrokeColorSpace(new GfxDeviceRGBColorSpace()); + for (int i = 0; i < 3; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFillColorSpace(Object args[], int numArgs) +{ + assert(numArgs >= 1); + GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]); + + state->setFillPattern(nullptr); + + if (colorSpace) { + GfxColor color; + state->setFillColorSpace(colorSpace); + colorSpace->getDefaultColor(&color); + state->setFillColor(&color); + builder->updateStyle(state); + } else { + error(errSyntaxError, getPos(), "Bad color space (fill)"); + } +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetStrokeColorSpace(Object args[], int numArgs) +{ + assert(numArgs >= 1); + GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]); + + state->setStrokePattern(nullptr); + + if (colorSpace) { + GfxColor color; + state->setStrokeColorSpace(colorSpace); + colorSpace->getDefaultColor(&color); + state->setStrokeColor(&color); + builder->updateStyle(state); + } else { + error(errSyntaxError, getPos(), "Bad color space (stroke)"); + } +} + +void PdfParser::opSetFillColor(Object args[], int numArgs) { + GfxColor color; + int i; + + if (numArgs != state->getFillColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command"); + return; + } + state->setFillPattern(nullptr); + for (i = 0; i < numArgs; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setFillColor(&color); + builder->updateStyle(state); +} + +void PdfParser::opSetStrokeColor(Object args[], int numArgs) { + GfxColor color; + int i; + + if (numArgs != state->getStrokeColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command"); + return; + } + state->setStrokePattern(nullptr); + for (i = 0; i < numArgs; ++i) { + color.c[i] = dblToCol(args[i].getNum()); + } + state->setStrokeColor(&color); + builder->updateStyle(state); +} + +void PdfParser::opSetFillColorN(Object args[], int numArgs) { + GfxColor color; + int i; + + if (state->getFillColorSpace()->getMode() == csPattern) { + if (numArgs > 1) { + if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() || + numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace()) + ->getUnder()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command"); + return; + } + for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + state->setFillColor(&color); + builder->updateStyle(state); + } + GfxPattern *pattern; +#if defined(POPPLER_EVEN_NEWER_COLOR_SPACE_API) + if (args[numArgs-1].isName() && + (pattern = res->lookupPattern(args[numArgs-1].getName(), nullptr, state))) { + state->setFillPattern(pattern); + builder->updateStyle(state); + } +#else + if (args[numArgs-1].isName() && + (pattern = res->lookupPattern(args[numArgs-1].getName(), NULL))) { + state->setFillPattern(pattern); + builder->updateStyle(state); + } +#endif + + } else { + if (numArgs != state->getFillColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command"); + return; + } + state->setFillPattern(nullptr); + for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + state->setFillColor(&color); + builder->updateStyle(state); + } +} + +void PdfParser::opSetStrokeColorN(Object args[], int numArgs) { + GfxColor color; + int i; + + if (state->getStrokeColorSpace()->getMode() == csPattern) { + if (numArgs > 1) { + if (!((GfxPatternColorSpace *)state->getStrokeColorSpace()) + ->getUnder() || + numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace()) + ->getUnder()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); + return; + } + for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + state->setStrokeColor(&color); + builder->updateStyle(state); + } + GfxPattern *pattern; +#if defined(POPPLER_EVEN_NEWER_COLOR_SPACE_API) + if (args[numArgs-1].isName() && + (pattern = res->lookupPattern(args[numArgs-1].getName(), nullptr, state))) { + state->setStrokePattern(pattern); + builder->updateStyle(state); + } +#else + if (args[numArgs-1].isName() && + (pattern = res->lookupPattern(args[numArgs-1].getName(), NULL))) { + state->setStrokePattern(pattern); + builder->updateStyle(state); + } +#endif + + } else { + if (numArgs != state->getStrokeColorSpace()->getNComps()) { + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); + return; + } + state->setStrokePattern(nullptr); + for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) { + if (args[i].isNum()) { + color.c[i] = dblToCol(args[i].getNum()); + } + } + state->setStrokeColor(&color); + builder->updateStyle(state); + } +} + +//------------------------------------------------------------------------ +// path segment operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opMoveTo(Object args[], int /*numArgs*/) +{ + state->moveTo(args[0].getNum(), args[1].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opLineTo(Object args[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in lineto"); + return; + } + state->lineTo(args[0].getNum(), args[1].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opCurveTo(Object args[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in curveto"); + return; + } + double x1 = args[0].getNum(); + double y1 = args[1].getNum(); + double x2 = args[2].getNum(); + double y2 = args[3].getNum(); + double x3 = args[4].getNum(); + double y3 = args[5].getNum(); + state->curveTo(x1, y1, x2, y2, x3, y3); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opCurveTo1(Object args[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in curveto1"); + return; + } + double x1 = state->getCurX(); + double y1 = state->getCurY(); + double x2 = args[0].getNum(); + double y2 = args[1].getNum(); + double x3 = args[2].getNum(); + double y3 = args[3].getNum(); + state->curveTo(x1, y1, x2, y2, x3, y3); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opCurveTo2(Object args[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in curveto2"); + return; + } + double x1 = args[0].getNum(); + double y1 = args[1].getNum(); + double x2 = args[2].getNum(); + double y2 = args[3].getNum(); + double x3 = x2; + double y3 = y2; + state->curveTo(x1, y1, x2, y2, x3, y3); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opRectangle(Object args[], int /*numArgs*/) +{ + double x = args[0].getNum(); + double y = args[1].getNum(); + double w = args[2].getNum(); + double h = args[3].getNum(); + state->moveTo(x, y); + state->lineTo(x + w, y); + state->lineTo(x + w, y + h); + state->lineTo(x, y + h); + state->closePath(); +} + +void PdfParser::opClosePath(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + error(errSyntaxError, getPos(), "No current point in closepath"); + return; + } + state->closePath(); +} + +//------------------------------------------------------------------------ +// path painting operators +//------------------------------------------------------------------------ + +void PdfParser::opEndPath(Object /*args*/[], int /*numArgs*/) +{ + doEndPath(); +} + +void PdfParser::opStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in stroke")); + return; + } + if (state->isPath()) { + if (state->getStrokeColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getStrokePattern())) { + doPatternStrokeFallback(); + } else { + builder->addPath(state, false, true); + } + } + doEndPath(); +} + +void PdfParser::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) { + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in closepath/stroke")); + return; + } + state->closePath(); + if (state->isPath()) { + if (state->getStrokeColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getStrokePattern())) { + doPatternStrokeFallback(); + } else { + builder->addPath(state, false, true); + } + } + doEndPath(); +} + +void PdfParser::opFill(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in fill")); + return; + } + if (state->isPath()) { + if (state->getFillColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getFillPattern())) { + doPatternFillFallback(gFalse); + } else { + builder->addPath(state, true, false); + } + } + doEndPath(); +} + +void PdfParser::opEOFill(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in eofill")); + return; + } + if (state->isPath()) { + if (state->getFillColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getFillPattern())) { + doPatternFillFallback(gTrue); + } else { + builder->addPath(state, true, false, true); + } + } + doEndPath(); +} + +void PdfParser::opFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in fill/stroke")); + return; + } + if (state->isPath()) { + doFillAndStroke(gFalse); + } else { + builder->addPath(state, true, true); + } + doEndPath(); +} + +void PdfParser::opCloseFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in closepath/fill/stroke")); + return; + } + if (state->isPath()) { + state->closePath(); + doFillAndStroke(gFalse); + } + doEndPath(); +} + +void PdfParser::opEOFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in eofill/stroke")); + return; + } + if (state->isPath()) { + doFillAndStroke(gTrue); + } + doEndPath(); +} + +void PdfParser::opCloseEOFillStroke(Object /*args*/[], int /*numArgs*/) +{ + if (!state->isCurPt()) { + //error(getPos(), const_cast<char*>("No path in closepath/eofill/stroke")); + return; + } + if (state->isPath()) { + state->closePath(); + doFillAndStroke(gTrue); + } + doEndPath(); +} + +void PdfParser::doFillAndStroke(GBool eoFill) { + GBool fillOk = gTrue, strokeOk = gTrue; + if (state->getFillColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getFillPattern())) { + fillOk = gFalse; + } + if (state->getStrokeColorSpace()->getMode() == csPattern && + !builder->isPatternTypeSupported(state->getStrokePattern())) { + strokeOk = gFalse; + } + if (fillOk && strokeOk) { + builder->addPath(state, true, true, eoFill); + } else { + doPatternFillFallback(eoFill); + doPatternStrokeFallback(); + } +} + +void PdfParser::doPatternFillFallback(GBool eoFill) { + GfxPattern *pattern; + + if (!(pattern = state->getFillPattern())) { + return; + } + switch (pattern->getType()) { + case 1: + break; + case 2: + doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gFalse, eoFill); + break; + default: + error(errUnimplemented, getPos(), "Unimplemented pattern type (%d) in fill", + pattern->getType()); + break; + } +} + +void PdfParser::doPatternStrokeFallback() { + GfxPattern *pattern; + + if (!(pattern = state->getStrokePattern())) { + return; + } + switch (pattern->getType()) { + case 1: + break; + case 2: + doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gTrue, gFalse); + break; + default: + error(errUnimplemented, getPos(), "Unimplemented pattern type ({0:d}) in stroke", + pattern->getType()); + break; + } +} + +void PdfParser::doShadingPatternFillFallback(GfxShadingPattern *sPat, + GBool stroke, GBool eoFill) { + GfxShading *shading; + GfxPath *savedPath; + const double *ctm, *btm, *ptm; + double m[6], ictm[6], m1[6]; + double xMin, yMin, xMax, yMax; + double det; + + shading = sPat->getShading(); + + // save current graphics state + savedPath = state->getPath()->copy(); + saveState(); + + // clip to bbox + if (false ){//shading->getHasBBox()) { + shading->getBBox(&xMin, &yMin, &xMax, &yMax); + state->moveTo(xMin, yMin); + state->lineTo(xMax, yMin); + state->lineTo(xMax, yMax); + state->lineTo(xMin, yMax); + state->closePath(); + state->clip(); + //builder->clip(state); + state->setPath(savedPath->copy()); + } + + // clip to current path + if (stroke) { + state->clipToStrokePath(); + //out->clipToStrokePath(state); + } else { + state->clip(); + if (eoFill) { + builder->setClipPath(state, true); + } else { + builder->setClipPath(state); + } + } + + // set the color space + state->setFillColorSpace(shading->getColorSpace()->copy()); + + // background color fill + if (shading->getHasBackground()) { + state->setFillColor(shading->getBackground()); + builder->addPath(state, true, false); + } + state->clearPath(); + + // construct a (pattern space) -> (current space) transform matrix + ctm = state->getCTM(); + btm = baseMatrix; + ptm = sPat->getMatrix(); + // iCTM = invert CTM + det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]); + ictm[0] = ctm[3] * det; + ictm[1] = -ctm[1] * det; + ictm[2] = -ctm[2] * det; + ictm[3] = ctm[0] * det; + ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det; + ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det; + // m1 = PTM * BTM = PTM * base transform matrix + m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2]; + m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3]; + m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2]; + m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3]; + m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4]; + m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5]; + // m = m1 * iCTM = (PTM * BTM) * (iCTM) + m[0] = m1[0] * ictm[0] + m1[1] * ictm[2]; + m[1] = m1[0] * ictm[1] + m1[1] * ictm[3]; + m[2] = m1[2] * ictm[0] + m1[3] * ictm[2]; + m[3] = m1[2] * ictm[1] + m1[3] * ictm[3]; + m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4]; + m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5]; + + // set the new matrix + state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]); + builder->setTransform(m[0], m[1], m[2], m[3], m[4], m[5]); + + // do shading type-specific operations + switch (shading->getType()) { + case 1: + doFunctionShFill(static_cast<GfxFunctionShading *>(shading)); + break; + case 2: + case 3: + // no need to implement these + break; + case 4: + case 5: + doGouraudTriangleShFill(static_cast<GfxGouraudTriangleShading *>(shading)); + break; + case 6: + case 7: + doPatchMeshShFill(static_cast<GfxPatchMeshShading *>(shading)); + break; + } + + // restore graphics state + restoreState(); + state->setPath(savedPath); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opShFill(Object args[], int /*numArgs*/) +{ + GfxShading *shading = nullptr; + GfxPath *savedPath = nullptr; + double xMin, yMin, xMax, yMax; + double xTemp, yTemp; + double gradientTransform[6]; + double *matrix = nullptr; + GBool savedState = gFalse; + +#if defined(POPPLER_EVEN_NEWER_COLOR_SPACE_API) + if (!(shading = res->lookupShading(args[0].getName(), nullptr, state))) { + return; + } +#else + if (!(shading = res->lookupShading(args[0].getName(), NULL))) { + return; + } +#endif + + // save current graphics state + if (shading->getType() != 2 && shading->getType() != 3) { + savedPath = state->getPath()->copy(); + saveState(); + savedState = gTrue; + } else { // get gradient transform if possible + // check proper operator sequence + // first there should be one W(*) and then one 'cm' somewhere before 'sh' + GBool seenClip, seenConcat; + seenClip = (clipHistory->getClipPath() != nullptr); + seenConcat = gFalse; + int i = 1; + while (i <= maxOperatorHistoryDepth) { + const char *opName = getPreviousOperator(i); + if (!strcmp(opName, "cm")) { + if (seenConcat) { // more than one 'cm' + break; + } else { + seenConcat = gTrue; + } + } + i++; + } + + if (seenConcat && seenClip) { + if (builder->getTransform(gradientTransform)) { + matrix = (double*)&gradientTransform; + builder->setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); // remove transform + } + } + } + + // clip to bbox + if (shading->getHasBBox()) { + shading->getBBox(&xMin, &yMin, &xMax, &yMax); + if (matrix != nullptr) { + xTemp = matrix[0]*xMin + matrix[2]*yMin + matrix[4]; + yTemp = matrix[1]*xMin + matrix[3]*yMin + matrix[5]; + state->moveTo(xTemp, yTemp); + xTemp = matrix[0]*xMax + matrix[2]*yMin + matrix[4]; + yTemp = matrix[1]*xMax + matrix[3]*yMin + matrix[5]; + state->lineTo(xTemp, yTemp); + xTemp = matrix[0]*xMax + matrix[2]*yMax + matrix[4]; + yTemp = matrix[1]*xMax + matrix[3]*yMax + matrix[5]; + state->lineTo(xTemp, yTemp); + xTemp = matrix[0]*xMin + matrix[2]*yMax + matrix[4]; + yTemp = matrix[1]*xMin + matrix[3]*yMax + matrix[5]; + state->lineTo(xTemp, yTemp); + } + else { + state->moveTo(xMin, yMin); + state->lineTo(xMax, yMin); + state->lineTo(xMax, yMax); + state->lineTo(xMin, yMax); + } + state->closePath(); + state->clip(); + if (savedState) + builder->setClipPath(state); + else + builder->clip(state); + state->clearPath(); + } + + // set the color space + if (savedState) + state->setFillColorSpace(shading->getColorSpace()->copy()); + + // do shading type-specific operations + switch (shading->getType()) { + case 1: + doFunctionShFill(static_cast<GfxFunctionShading *>(shading)); + break; + case 2: + case 3: + if (clipHistory->getClipPath()) { + builder->addShadedFill(shading, matrix, clipHistory->getClipPath(), + clipHistory->getClipType() == clipEO ? true : false); + } + break; + case 4: + case 5: + doGouraudTriangleShFill(static_cast<GfxGouraudTriangleShading *>(shading)); + break; + case 6: + case 7: + doPatchMeshShFill(static_cast<GfxPatchMeshShading *>(shading)); + break; + } + + // restore graphics state + if (savedState) { + restoreState(); + state->setPath(savedPath); + } + + delete shading; +} + +void PdfParser::doFunctionShFill(GfxFunctionShading *shading) { + double x0, y0, x1, y1; + GfxColor colors[4]; + + shading->getDomain(&x0, &y0, &x1, &y1); + shading->getColor(x0, y0, &colors[0]); + shading->getColor(x0, y1, &colors[1]); + shading->getColor(x1, y0, &colors[2]); + shading->getColor(x1, y1, &colors[3]); + doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0); +} + +void PdfParser::doFunctionShFill1(GfxFunctionShading *shading, + double x0, double y0, + double x1, double y1, + GfxColor *colors, int depth) { + GfxColor fillColor; + GfxColor color0M, color1M, colorM0, colorM1, colorMM; + GfxColor colors2[4]; + double functionColorDelta = colorDeltas[pdfFunctionShading-1]; + const double *matrix; + double xM, yM; + int nComps, i, j; + + nComps = shading->getColorSpace()->getNComps(); + matrix = shading->getMatrix(); + + // compare the four corner colors + for (i = 0; i < 4; ++i) { + for (j = 0; j < nComps; ++j) { + if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) { + break; + } + } + if (j < nComps) { + break; + } + } + + // center of the rectangle + xM = 0.5 * (x0 + x1); + yM = 0.5 * (y0 + y1); + + // the four corner colors are close (or we hit the recursive limit) + // -- fill the rectangle; but require at least one subdivision + // (depth==0) to avoid problems when the four outer corners of the + // shaded region are the same color + if ((i == 4 && depth > 0) || depth == maxDepths[pdfFunctionShading-1]) { + + // use the center color + shading->getColor(xM, yM, &fillColor); + state->setFillColor(&fillColor); + + // fill the rectangle + state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4], + x0 * matrix[1] + y0 * matrix[3] + matrix[5]); + state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4], + x1 * matrix[1] + y0 * matrix[3] + matrix[5]); + state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4], + x1 * matrix[1] + y1 * matrix[3] + matrix[5]); + state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4], + x0 * matrix[1] + y1 * matrix[3] + matrix[5]); + state->closePath(); + builder->addPath(state, true, false); + state->clearPath(); + + // the four corner colors are not close enough -- subdivide the + // rectangle + } else { + + // colors[0] colorM0 colors[2] + // (x0,y0) (xM,y0) (x1,y0) + // +----------+----------+ + // | | | + // | UL | UR | + // color0M | colorMM | color1M + // (x0,yM) +----------+----------+ (x1,yM) + // | (xM,yM) | + // | LL | LR | + // | | | + // +----------+----------+ + // colors[1] colorM1 colors[3] + // (x0,y1) (xM,y1) (x1,y1) + + shading->getColor(x0, yM, &color0M); + shading->getColor(x1, yM, &color1M); + shading->getColor(xM, y0, &colorM0); + shading->getColor(xM, y1, &colorM1); + shading->getColor(xM, yM, &colorMM); + + // upper-left sub-rectangle + colors2[0] = colors[0]; + colors2[1] = color0M; + colors2[2] = colorM0; + colors2[3] = colorMM; + doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1); + + // lower-left sub-rectangle + colors2[0] = color0M; + colors2[1] = colors[1]; + colors2[2] = colorMM; + colors2[3] = colorM1; + doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1); + + // upper-right sub-rectangle + colors2[0] = colorM0; + colors2[1] = colorMM; + colors2[2] = colors[2]; + colors2[3] = color1M; + doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1); + + // lower-right sub-rectangle + colors2[0] = colorMM; + colors2[1] = colorM1; + colors2[2] = color1M; + colors2[3] = colors[3]; + doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1); + } +} + +void PdfParser::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) { + double x0, y0, x1, y1, x2, y2; + GfxColor color0, color1, color2; + int i; + + for (i = 0; i < shading->getNTriangles(); ++i) { + shading->getTriangle(i, &x0, &y0, &color0, + &x1, &y1, &color1, + &x2, &y2, &color2); + gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2, + shading->getColorSpace()->getNComps(), 0); + } +} + +void PdfParser::gouraudFillTriangle(double x0, double y0, GfxColor *color0, + double x1, double y1, GfxColor *color1, + double x2, double y2, GfxColor *color2, + int nComps, int depth) { + double x01, y01, x12, y12, x20, y20; + double gouraudColorDelta = colorDeltas[pdfGouraudTriangleShading-1]; + GfxColor color01, color12, color20; + int i; + + for (i = 0; i < nComps; ++i) { + if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta || + abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) { + break; + } + } + if (i == nComps || depth == maxDepths[pdfGouraudTriangleShading-1]) { + state->setFillColor(color0); + state->moveTo(x0, y0); + state->lineTo(x1, y1); + state->lineTo(x2, y2); + state->closePath(); + builder->addPath(state, true, false); + state->clearPath(); + } else { + x01 = 0.5 * (x0 + x1); + y01 = 0.5 * (y0 + y1); + x12 = 0.5 * (x1 + x2); + y12 = 0.5 * (y1 + y2); + x20 = 0.5 * (x2 + x0); + y20 = 0.5 * (y2 + y0); + //~ if the shading has a Function, this should interpolate on the + //~ function parameter, not on the color components + for (i = 0; i < nComps; ++i) { + color01.c[i] = (color0->c[i] + color1->c[i]) / 2; + color12.c[i] = (color1->c[i] + color2->c[i]) / 2; + color20.c[i] = (color2->c[i] + color0->c[i]) / 2; + } + gouraudFillTriangle(x0, y0, color0, x01, y01, &color01, + x20, y20, &color20, nComps, depth + 1); + gouraudFillTriangle(x01, y01, &color01, x1, y1, color1, + x12, y12, &color12, nComps, depth + 1); + gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12, + x20, y20, &color20, nComps, depth + 1); + gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12, + x2, y2, color2, nComps, depth + 1); + } +} + +void PdfParser::doPatchMeshShFill(GfxPatchMeshShading *shading) { + int start, i; + + if (shading->getNPatches() > 128) { + start = 3; + } else if (shading->getNPatches() > 64) { + start = 2; + } else if (shading->getNPatches() > 16) { + start = 1; + } else { + start = 0; + } + for (i = 0; i < shading->getNPatches(); ++i) { + fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(), + start); + } +} + +void PdfParser::fillPatch(_POPPLER_CONST GfxPatch *patch, int nComps, int depth) { + GfxPatch patch00 = blankPatch(); + GfxPatch patch01 = blankPatch(); + GfxPatch patch10 = blankPatch(); + GfxPatch patch11 = blankPatch(); + GfxColor color = {{0}}; + double xx[4][8]; + double yy[4][8]; + double xxm; + double yym; + double patchColorDelta = colorDeltas[pdfPatchMeshShading - 1]; + + int i; + + for (i = 0; i < nComps; ++i) { + if (std::abs(patch->color[0][0].c[i] - patch->color[0][1].c[i]) + > patchColorDelta || + std::abs(patch->color[0][1].c[i] - patch->color[1][1].c[i]) + > patchColorDelta || + std::abs(patch->color[1][1].c[i] - patch->color[1][0].c[i]) + > patchColorDelta || + std::abs(patch->color[1][0].c[i] - patch->color[0][0].c[i]) + > patchColorDelta) { + break; + } + color.c[i] = GfxColorComp(patch->color[0][0].c[i]); + } + if (i == nComps || depth == maxDepths[pdfPatchMeshShading-1]) { + state->setFillColor(&color); + state->moveTo(patch->x[0][0], patch->y[0][0]); + state->curveTo(patch->x[0][1], patch->y[0][1], + patch->x[0][2], patch->y[0][2], + patch->x[0][3], patch->y[0][3]); + state->curveTo(patch->x[1][3], patch->y[1][3], + patch->x[2][3], patch->y[2][3], + patch->x[3][3], patch->y[3][3]); + state->curveTo(patch->x[3][2], patch->y[3][2], + patch->x[3][1], patch->y[3][1], + patch->x[3][0], patch->y[3][0]); + state->curveTo(patch->x[2][0], patch->y[2][0], + patch->x[1][0], patch->y[1][0], + patch->x[0][0], patch->y[0][0]); + state->closePath(); + builder->addPath(state, true, false); + state->clearPath(); + } else { + for (i = 0; i < 4; ++i) { + xx[i][0] = patch->x[i][0]; + yy[i][0] = patch->y[i][0]; + xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]); + yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]); + xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]); + yym = 0.5 * (patch->y[i][1] + patch->y[i][2]); + xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]); + yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]); + xx[i][2] = 0.5 * (xx[i][1] + xxm); + yy[i][2] = 0.5 * (yy[i][1] + yym); + xx[i][5] = 0.5 * (xxm + xx[i][6]); + yy[i][5] = 0.5 * (yym + yy[i][6]); + xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]); + yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]); + xx[i][7] = patch->x[i][3]; + yy[i][7] = patch->y[i][3]; + } + for (i = 0; i < 4; ++i) { + patch00.x[0][i] = xx[0][i]; + patch00.y[0][i] = yy[0][i]; + patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]); + patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]); + xxm = 0.5 * (xx[1][i] + xx[2][i]); + yym = 0.5 * (yy[1][i] + yy[2][i]); + patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]); + patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]); + patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm); + patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym); + patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]); + patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]); + patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]); + patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]); + patch10.x[0][i] = patch00.x[3][i]; + patch10.y[0][i] = patch00.y[3][i]; + patch10.x[3][i] = xx[3][i]; + patch10.y[3][i] = yy[3][i]; + } + for (i = 4; i < 8; ++i) { + patch01.x[0][i-4] = xx[0][i]; + patch01.y[0][i-4] = yy[0][i]; + patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]); + patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]); + xxm = 0.5 * (xx[1][i] + xx[2][i]); + yym = 0.5 * (yy[1][i] + yy[2][i]); + patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]); + patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]); + patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm); + patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym); + patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]); + patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]); + patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]); + patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]); + patch11.x[0][i-4] = patch01.x[3][i-4]; + patch11.y[0][i-4] = patch01.y[3][i-4]; + patch11.x[3][i-4] = xx[3][i]; + patch11.y[3][i-4] = yy[3][i]; + } + //~ if the shading has a Function, this should interpolate on the + //~ function parameter, not on the color components + for (i = 0; i < nComps; ++i) { + patch00.color[0][0].c[i] = patch->color[0][0].c[i]; + patch00.color[0][1].c[i] = (patch->color[0][0].c[i] + + patch->color[0][1].c[i]) / 2; + patch01.color[0][0].c[i] = patch00.color[0][1].c[i]; + patch01.color[0][1].c[i] = patch->color[0][1].c[i]; + patch01.color[1][1].c[i] = (patch->color[0][1].c[i] + + patch->color[1][1].c[i]) / 2; + patch11.color[0][1].c[i] = patch01.color[1][1].c[i]; + patch11.color[1][1].c[i] = patch->color[1][1].c[i]; + patch11.color[1][0].c[i] = (patch->color[1][1].c[i] + + patch->color[1][0].c[i]) / 2; + patch10.color[1][1].c[i] = patch11.color[1][0].c[i]; + patch10.color[1][0].c[i] = patch->color[1][0].c[i]; + patch10.color[0][0].c[i] = (patch->color[1][0].c[i] + + patch->color[0][0].c[i]) / 2; + patch00.color[1][0].c[i] = patch10.color[0][0].c[i]; + patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] + + patch01.color[1][1].c[i]) / 2; + patch01.color[1][0].c[i] = patch00.color[1][1].c[i]; + patch11.color[0][0].c[i] = patch00.color[1][1].c[i]; + patch10.color[0][1].c[i] = patch00.color[1][1].c[i]; + } + fillPatch(&patch00, nComps, depth + 1); + fillPatch(&patch10, nComps, depth + 1); + fillPatch(&patch01, nComps, depth + 1); + fillPatch(&patch11, nComps, depth + 1); + } +} + +void PdfParser::doEndPath() { + if (state->isCurPt() && clip != clipNone) { + state->clip(); + if (clip == clipNormal) { + clipHistory->setClip(state->getPath(), clipNormal); + builder->clip(state); + } else { + clipHistory->setClip(state->getPath(), clipEO); + builder->clip(state, true); + } + } + clip = clipNone; + state->clearPath(); +} + +//------------------------------------------------------------------------ +// path clipping operators +//------------------------------------------------------------------------ + +void PdfParser::opClip(Object /*args*/[], int /*numArgs*/) +{ + clip = clipNormal; +} + +void PdfParser::opEOClip(Object /*args*/[], int /*numArgs*/) +{ + clip = clipEO; +} + +//------------------------------------------------------------------------ +// text object operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginText(Object /*args*/[], int /*numArgs*/) +{ + state->setTextMat(1, 0, 0, 1, 0, 0); + state->textMoveTo(0, 0); + builder->updateTextPosition(0.0, 0.0); + fontChanged = gTrue; + builder->beginTextObject(state); +} + +void PdfParser::opEndText(Object /*args*/[], int /*numArgs*/) +{ + builder->endTextObject(state); +} + +//------------------------------------------------------------------------ +// text state operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetCharSpacing(Object args[], int /*numArgs*/) +{ + state->setCharSpace(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetFont(Object args[], int /*numArgs*/) +{ + GfxFont *font = res->lookupFont(args[0].getName()); + + if (!font) { + // unsetting the font (drawing no text) is better than using the + // previous one and drawing random glyphs from it + state->setFont(nullptr, args[1].getNum()); + fontChanged = gTrue; + return; + } + if (printCommands) { + printf(" font: tag=%s name='%s' %g\n", + font->getTag()->getCString(), + font->getName() ? font->getName()->getCString() : "???", + args[1].getNum()); + fflush(stdout); + } + + font->incRefCnt(); + state->setFont(font, args[1].getNum()); + fontChanged = gTrue; +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetTextLeading(Object args[], int /*numArgs*/) +{ + state->setLeading(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetTextRender(Object args[], int /*numArgs*/) +{ + state->setRender(args[0].getInt()); + builder->updateStyle(state); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetTextRise(Object args[], int /*numArgs*/) +{ + state->setRise(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetWordSpacing(Object args[], int /*numArgs*/) +{ + state->setWordSpace(args[0].getNum()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetHorizScaling(Object args[], int /*numArgs*/) +{ + state->setHorizScaling(args[0].getNum()); + builder->updateTextMatrix(state); + fontChanged = gTrue; +} + +//------------------------------------------------------------------------ +// text positioning operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opTextMove(Object args[], int /*numArgs*/) +{ + double tx, ty; + + tx = state->getLineX() + args[0].getNum(); + ty = state->getLineY() + args[1].getNum(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opTextMoveSet(Object args[], int /*numArgs*/) +{ + double tx, ty; + + tx = state->getLineX() + args[0].getNum(); + ty = args[1].getNum(); + state->setLeading(-ty); + ty += state->getLineY(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opSetTextMatrix(Object args[], int /*numArgs*/) +{ + state->setTextMat(args[0].getNum(), args[1].getNum(), + args[2].getNum(), args[3].getNum(), + args[4].getNum(), args[5].getNum()); + state->textMoveTo(0, 0); + builder->updateTextMatrix(state); + builder->updateTextPosition(0.0, 0.0); + fontChanged = gTrue; +} + +void PdfParser::opTextNextLine(Object /*args*/[], int /*numArgs*/) +{ + double tx, ty; + + tx = state->getLineX(); + ty = state->getLineY() - state->getLeading(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); +} + +//------------------------------------------------------------------------ +// text string operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opShowText(Object args[], int /*numArgs*/) +{ + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in show"); + return; + } + if (fontChanged) { + builder->updateFont(state); + fontChanged = gFalse; + } + doShowText(args[0].getString()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opMoveShowText(Object args[], int /*numArgs*/) +{ + double tx = 0; + double ty = 0; + + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in move/show"); + return; + } + if (fontChanged) { + builder->updateFont(state); + fontChanged = gFalse; + } + tx = state->getLineX(); + ty = state->getLineY() - state->getLeading(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); + doShowText(args[0].getString()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opMoveSetShowText(Object args[], int /*numArgs*/) +{ + double tx = 0; + double ty = 0; + + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in move/set/show"); + return; + } + if (fontChanged) { + builder->updateFont(state); + fontChanged = gFalse; + } + state->setWordSpace(args[0].getNum()); + state->setCharSpace(args[1].getNum()); + tx = state->getLineX(); + ty = state->getLineY() - state->getLeading(); + state->textMoveTo(tx, ty); + builder->updateTextPosition(tx, ty); + doShowText(args[2].getString()); +} + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opShowSpaceText(Object args[], int /*numArgs*/) +{ + Array *a = nullptr; + Object obj; + int wMode = 0; + + if (!state->getFont()) { + error(errSyntaxError, getPos(), "No font in show/space"); + return; + } + if (fontChanged) { + builder->updateFont(state); + fontChanged = gFalse; + } + wMode = state->getFont()->getWMode(); + a = args[0].getArray(); + for (int i = 0; i < a->getLength(); ++i) { + _POPPLER_CALL_ARGS(obj, a->get, i); + if (obj.isNum()) { + // this uses the absolute value of the font size to match + // Acrobat's behavior + if (wMode) { + state->textShift(0, -obj.getNum() * 0.001 * + fabs(state->getFontSize())); + } else { + state->textShift(-obj.getNum() * 0.001 * + fabs(state->getFontSize()), 0); + } + builder->updateTextShift(state, obj.getNum()); + } else if (obj.isString()) { + doShowText(obj.getString()); + } else { + error(errSyntaxError, getPos(), "Element of show/space array must be number or string"); + } + _POPPLER_FREE(obj); + } +} + +#if POPPLER_CHECK_VERSION(0,64,0) +void PdfParser::doShowText(const GooString *s) { +#else +void PdfParser::doShowText(GooString *s) { +#endif + GfxFont *font; + int wMode; + double riseX, riseY; + CharCode code; + Unicode _POPPLER_CONST_82 *u = nullptr; + double x, y, dx, dy, tdx, tdy; + double originX, originY, tOriginX, tOriginY; + double oldCTM[6], newCTM[6]; + const double *mat; + Object charProc; + Dict *resDict; + Parser *oldParser; +#if POPPLER_CHECK_VERSION(0,64,0) + const char *p; +#else + char *p; +#endif + int len, n, uLen; + + font = state->getFont(); + wMode = font->getWMode(); + + builder->beginString(state); + + // handle a Type 3 char + if (font->getType() == fontType3 && false) {//out->interpretType3Chars()) { + mat = state->getCTM(); + for (int i = 0; i < 6; ++i) { + oldCTM[i] = mat[i]; + } + mat = state->getTextMat(); + newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2]; + newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3]; + newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2]; + newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3]; + mat = font->getFontMatrix(); + newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2]; + newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3]; + newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2]; + newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3]; + newCTM[0] *= state->getFontSize(); + newCTM[1] *= state->getFontSize(); + newCTM[2] *= state->getFontSize(); + newCTM[3] *= state->getFontSize(); + newCTM[0] *= state->getHorizScaling(); + newCTM[2] *= state->getHorizScaling(); + state->textTransformDelta(0, state->getRise(), &riseX, &riseY); + double curX = state->getCurX(); + double curY = state->getCurY(); + double lineX = state->getLineX(); + double lineY = state->getLineY(); + oldParser = parser; + p = s->getCString(); + len = s->getLength(); + while (len > 0) { + n = font->getNextChar(p, len, &code, + &u, &uLen, /* TODO: This looks like a memory leak for u. */ + &dx, &dy, &originX, &originY); + dx = dx * state->getFontSize() + state->getCharSpace(); + if (n == 1 && *p == ' ') { + dx += state->getWordSpace(); + } + dx *= state->getHorizScaling(); + dy *= state->getFontSize(); + state->textTransformDelta(dx, dy, &tdx, &tdy); + state->transform(curX + riseX, curY + riseY, &x, &y); + saveState(); + state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y); + //~ the CTM concat values here are wrong (but never used) + //out->updateCTM(state, 1, 0, 0, 1, 0, 0); + if (false){ /*!out->beginType3Char(state, curX + riseX, curY + riseY, tdx, tdy, + code, u, uLen)) {*/ + _POPPLER_CALL_ARGS(charProc, ((Gfx8BitFont *)font)->getCharProc, code); + if ((resDict = ((Gfx8BitFont *)font)->getResources())) { + pushResources(resDict); + } + if (charProc.isStream()) { + //parse(&charProc, gFalse); // TODO: parse into SVG font + } else { + error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry"); + } + //out->endType3Char(state); + if (resDict) { + popResources(); + } + _POPPLER_FREE(charProc); + } + restoreState(); + // GfxState::restore() does *not* restore the current position, + // so we deal with it here using (curX, curY) and (lineX, lineY) + curX += tdx; + curY += tdy; + state->moveTo(curX, curY); + state->textSetPos(lineX, lineY); + p += n; + len -= n; + } + parser = oldParser; + + } else { + state->textTransformDelta(0, state->getRise(), &riseX, &riseY); + p = s->getCString(); + len = s->getLength(); + while (len > 0) { + n = font->getNextChar(p, len, &code, + &u, &uLen, /* TODO: This looks like a memory leak for u. */ + &dx, &dy, &originX, &originY); + + if (wMode) { + dx *= state->getFontSize(); + dy = dy * state->getFontSize() + state->getCharSpace(); + if (n == 1 && *p == ' ') { + dy += state->getWordSpace(); + } + } else { + dx = dx * state->getFontSize() + state->getCharSpace(); + if (n == 1 && *p == ' ') { + dx += state->getWordSpace(); + } + dx *= state->getHorizScaling(); + dy *= state->getFontSize(); + } + state->textTransformDelta(dx, dy, &tdx, &tdy); + originX *= state->getFontSize(); + originY *= state->getFontSize(); + state->textTransformDelta(originX, originY, &tOriginX, &tOriginY); + builder->addChar(state, state->getCurX() + riseX, state->getCurY() + riseY, + dx, dy, tOriginX, tOriginY, code, n, u, uLen); + state->shift(tdx, tdy); + p += n; + len -= n; + } + } + + builder->endString(state); +} + + +//------------------------------------------------------------------------ +// XObject operators +//------------------------------------------------------------------------ + +// TODO not good that numArgs is ignored but args[] is used: +void PdfParser::opXObject(Object args[], int /*numArgs*/) +{ + Object obj1, obj2, obj3, refObj; + +#if POPPLER_CHECK_VERSION(0,64,0) + const char *name = args[0].getName(); +#else + char *name = args[0].getName(); +#endif + _POPPLER_CALL_ARGS(obj1, res->lookupXObject, name); + if (obj1.isNull()) { + return; + } + if (!obj1.isStream()) { + error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name); + _POPPLER_FREE(obj1); + return; + } + _POPPLER_CALL_ARGS(obj2, obj1.streamGetDict()->lookup, "Subtype"); + if (obj2.isName(const_cast<char*>("Image"))) { + _POPPLER_CALL_ARGS(refObj, res->lookupXObjectNF, name); + doImage(&refObj, obj1.getStream(), gFalse); + _POPPLER_FREE(refObj); + } else if (obj2.isName(const_cast<char*>("Form"))) { + doForm(&obj1); + } else if (obj2.isName(const_cast<char*>("PS"))) { + _POPPLER_CALL_ARGS(obj3, obj1.streamGetDict()->lookup, "Level1"); +/* out->psXObject(obj1.getStream(), + obj3.isStream() ? obj3.getStream() : (Stream *)NULL);*/ + } else if (obj2.isName()) { + error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName()); + } else { + error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type"); + } + _POPPLER_FREE(obj2); + _POPPLER_FREE(obj1); +} + +void PdfParser::doImage(Object * /*ref*/, Stream *str, GBool inlineImg) +{ + Dict *dict; + int width, height; + int bits; + GBool interpolate; + StreamColorSpaceMode csMode; + GBool mask; + GBool invert; + Object maskObj, smaskObj; + GBool haveColorKeyMask, haveExplicitMask, haveSoftMask; + GBool maskInvert; + GBool maskInterpolate; + Object obj1, obj2; + + // get info from the stream + bits = 0; + csMode = streamCSNone; + str->getImageParams(&bits, &csMode); + + // get stream dict + dict = str->getDict(); + + // get size + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Width"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "W"); + } + if (obj1.isInt()){ + width = obj1.getInt(); + } + else if (obj1.isReal()) { + width = (int)obj1.getReal(); + } + else { + goto err2; + } + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Height"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "H"); + } + if (obj1.isInt()) { + height = obj1.getInt(); + } + else if (obj1.isReal()){ + height = static_cast<int>(obj1.getReal()); + } + else { + goto err2; + } + _POPPLER_FREE(obj1); + + // image interpolation + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Interpolate"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "I"); + } + if (obj1.isBool()) + interpolate = obj1.getBool(); + else + interpolate = gFalse; + _POPPLER_FREE(obj1); + maskInterpolate = gFalse; + + // image or mask? + _POPPLER_CALL_ARGS(obj1, dict->lookup, "ImageMask"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "IM"); + } + mask = gFalse; + if (obj1.isBool()) { + mask = obj1.getBool(); + } + else if (!obj1.isNull()) { + goto err2; + } + _POPPLER_FREE(obj1); + + // bit depth + if (bits == 0) { + _POPPLER_CALL_ARGS(obj1, dict->lookup, "BitsPerComponent"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "BPC"); + } + if (obj1.isInt()) { + bits = obj1.getInt(); + } else if (mask) { + bits = 1; + } else { + goto err2; + } + _POPPLER_FREE(obj1); + } + + // display a mask + if (mask) { + // check for inverted mask + if (bits != 1) { + goto err1; + } + invert = gFalse; + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "D"); + } + if (obj1.isArray()) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0); + if (obj2.isInt() && obj2.getInt() == 1) { + invert = gTrue; + } + _POPPLER_FREE(obj2); + } else if (!obj1.isNull()) { + goto err2; + } + _POPPLER_FREE(obj1); + + // draw it + builder->addImageMask(state, str, width, height, invert, interpolate); + + } else { + // get color space and color map + GfxColorSpace *colorSpace; + _POPPLER_CALL_ARGS(obj1, dict->lookup, "ColorSpace"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "CS"); + } + if (!obj1.isNull()) { + colorSpace = lookupColorSpaceCopy(obj1); + } else if (csMode == streamCSDeviceGray) { + colorSpace = new GfxDeviceGrayColorSpace(); + } else if (csMode == streamCSDeviceRGB) { + colorSpace = new GfxDeviceRGBColorSpace(); + } else if (csMode == streamCSDeviceCMYK) { + colorSpace = new GfxDeviceCMYKColorSpace(); + } else { + colorSpace = nullptr; + } + _POPPLER_FREE(obj1); + if (!colorSpace) { + goto err1; + } + _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, dict->lookup, "D"); + } + GfxImageColorMap *colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); + _POPPLER_FREE(obj1); + if (!colorMap->isOk()) { + delete colorMap; + goto err1; + } + + // get the mask + int maskColors[2*gfxColorMaxComps]; + haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse; + Stream *maskStr = nullptr; + int maskWidth = 0; + int maskHeight = 0; + maskInvert = gFalse; + GfxImageColorMap *maskColorMap = nullptr; + _POPPLER_CALL_ARGS(maskObj, dict->lookup, "Mask"); + _POPPLER_CALL_ARGS(smaskObj, dict->lookup, "SMask"); + Dict* maskDict; + if (smaskObj.isStream()) { + // soft mask + if (inlineImg) { + goto err1; + } + maskStr = smaskObj.getStream(); + maskDict = smaskObj.streamGetDict(); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W"); + } + if (!obj1.isInt()) { + goto err2; + } + maskWidth = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H"); + } + if (!obj1.isInt()) { + goto err2; + } + maskHeight = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BitsPerComponent"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BPC"); + } + if (!obj1.isInt()) { + goto err2; + } + int maskBits = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I"); + } + if (obj1.isBool()) + maskInterpolate = obj1.getBool(); + else + maskInterpolate = gFalse; + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ColorSpace"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "CS"); + } + GfxColorSpace *maskColorSpace = lookupColorSpaceCopy(obj1); + _POPPLER_FREE(obj1); + if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) { + goto err1; + } + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D"); + } + maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace); + _POPPLER_FREE(obj1); + if (!maskColorMap->isOk()) { + delete maskColorMap; + goto err1; + } + //~ handle the Matte entry + haveSoftMask = gTrue; + } else if (maskObj.isArray()) { + // color key mask + int i; + for (i = 0; i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps; ++i) { + _POPPLER_CALL_ARGS(obj1, maskObj.arrayGet, i); + maskColors[i] = obj1.getInt(); + _POPPLER_FREE(obj1); + } + haveColorKeyMask = gTrue; + } else if (maskObj.isStream()) { + // explicit mask + if (inlineImg) { + goto err1; + } + maskStr = maskObj.getStream(); + maskDict = maskObj.streamGetDict(); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W"); + } + if (!obj1.isInt()) { + goto err2; + } + maskWidth = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H"); + } + if (!obj1.isInt()) { + goto err2; + } + maskHeight = obj1.getInt(); + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ImageMask"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "IM"); + } + if (!obj1.isBool() || !obj1.getBool()) { + goto err2; + } + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I"); + } + if (obj1.isBool()) + maskInterpolate = obj1.getBool(); + else + maskInterpolate = gFalse; + _POPPLER_FREE(obj1); + maskInvert = gFalse; + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode"); + if (obj1.isNull()) { + _POPPLER_FREE(obj1); + _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D"); + } + if (obj1.isArray()) { + _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0); + if (obj2.isInt() && obj2.getInt() == 1) { + maskInvert = gTrue; + } + _POPPLER_FREE(obj2); + } else if (!obj1.isNull()) { + goto err2; + } + _POPPLER_FREE(obj1); + haveExplicitMask = gTrue; + } + + // draw it + if (haveSoftMask) { + builder->addSoftMaskedImage(state, str, width, height, colorMap, interpolate, + maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate); + delete maskColorMap; + } else if (haveExplicitMask) { + builder->addMaskedImage(state, str, width, height, colorMap, interpolate, + maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate); + } else { + builder->addImage(state, str, width, height, colorMap, interpolate, + haveColorKeyMask ? maskColors : static_cast<int *>(nullptr)); + } + delete colorMap; + + _POPPLER_FREE(maskObj); + _POPPLER_FREE(smaskObj); + } + + return; + + err2: + _POPPLER_FREE(obj1); + err1: + error(errSyntaxError, getPos(), "Bad image parameters"); +} + +void PdfParser::doForm(Object *str) { + Dict *dict; + GBool transpGroup, isolated, knockout; + GfxColorSpace *blendingColorSpace; + Object matrixObj, bboxObj; + double m[6], bbox[4]; + Object resObj; + Dict *resDict; + Object obj1, obj2, obj3; + int i; + + // check for excessive recursion + if (formDepth > 20) { + return; + } + + // get stream dict + dict = str->streamGetDict(); + + // check form type + _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType"); + if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { + error(errSyntaxError, getPos(), "Unknown form type"); + } + _POPPLER_FREE(obj1); + + // get bounding box + _POPPLER_CALL_ARGS(bboxObj, dict->lookup, "BBox"); + if (!bboxObj.isArray()) { + _POPPLER_FREE(bboxObj); + error(errSyntaxError, getPos(), "Bad form bounding box"); + return; + } + for (i = 0; i < 4; ++i) { + _POPPLER_CALL_ARGS(obj1, bboxObj.arrayGet, i); + bbox[i] = obj1.getNum(); + _POPPLER_FREE(obj1); + } + _POPPLER_FREE(bboxObj); + + // get matrix + _POPPLER_CALL_ARGS(matrixObj, dict->lookup, "Matrix"); + if (matrixObj.isArray()) { + for (i = 0; i < 6; ++i) { + _POPPLER_CALL_ARGS(obj1, matrixObj.arrayGet, i); + m[i] = obj1.getNum(); + _POPPLER_FREE(obj1); + } + } else { + m[0] = 1; m[1] = 0; + m[2] = 0; m[3] = 1; + m[4] = 0; m[5] = 0; + } + _POPPLER_FREE(matrixObj); + + // get resources + _POPPLER_CALL_ARGS(resObj, dict->lookup, "Resources"); + resDict = resObj.isDict() ? resObj.getDict() : (Dict *)nullptr; + + // check for a transparency group + transpGroup = isolated = knockout = gFalse; + blendingColorSpace = nullptr; + if (_POPPLER_CALL_ARGS_DEREF(obj1, dict->lookup, "Group").isDict()) { + if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "S").isName("Transparency")) { + transpGroup = gTrue; + if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "CS").isNull()) { +#if defined(POPPLER_EVEN_NEWER_NEW_COLOR_SPACE_API) + blendingColorSpace = GfxColorSpace::parse(nullptr, &obj3, nullptr, state); +#elif defined(POPPLER_EVEN_NEWER_COLOR_SPACE_API) + blendingColorSpace = GfxColorSpace::parse(&obj3, NULL, NULL); +#else + blendingColorSpace = GfxColorSpace::parse(&obj3, NULL); +#endif + } + _POPPLER_FREE(obj3); + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "I").isBool()) { + isolated = obj3.getBool(); + } + _POPPLER_FREE(obj3); + if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "K").isBool()) { + knockout = obj3.getBool(); + } + _POPPLER_FREE(obj3); + } + _POPPLER_FREE(obj2); + } + _POPPLER_FREE(obj1); + + // draw it + ++formDepth; + doForm1(str, resDict, m, bbox, + transpGroup, gFalse, blendingColorSpace, isolated, knockout); + --formDepth; + + if (blendingColorSpace) { + delete blendingColorSpace; + } + _POPPLER_FREE(resObj); +} + +void PdfParser::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox, + GBool transpGroup, GBool softMask, + GfxColorSpace *blendingColorSpace, + GBool isolated, GBool knockout, + GBool alpha, Function *transferFunc, + GfxColor *backdropColor) { + Parser *oldParser; + double oldBaseMatrix[6]; + int i; + + // push new resources on stack + pushResources(resDict); + + // save current graphics state + saveState(); + + // kill any pre-existing path + state->clearPath(); + + if (softMask || transpGroup) { + builder->clearSoftMask(state); + builder->pushTransparencyGroup(state, bbox, blendingColorSpace, + isolated, knockout, softMask); + } + + // save current parser + oldParser = parser; + + // set form transformation matrix + state->concatCTM(matrix[0], matrix[1], matrix[2], + matrix[3], matrix[4], matrix[5]); + builder->setTransform(matrix[0], matrix[1], matrix[2], + matrix[3], matrix[4], matrix[5]); + + // set form bounding box + state->moveTo(bbox[0], bbox[1]); + state->lineTo(bbox[2], bbox[1]); + state->lineTo(bbox[2], bbox[3]); + state->lineTo(bbox[0], bbox[3]); + state->closePath(); + state->clip(); + clipHistory->setClip(state->getPath()); + builder->clip(state); + state->clearPath(); + + if (softMask || transpGroup) { + if (state->getBlendMode() != gfxBlendNormal) { + state->setBlendMode(gfxBlendNormal); + } + if (state->getFillOpacity() != 1) { + builder->setGroupOpacity(state->getFillOpacity()); + state->setFillOpacity(1); + } + if (state->getStrokeOpacity() != 1) { + state->setStrokeOpacity(1); + } + } + + // set new base matrix + for (i = 0; i < 6; ++i) { + oldBaseMatrix[i] = baseMatrix[i]; + baseMatrix[i] = state->getCTM()[i]; + } + + // draw the form + parse(str, gFalse); + + // restore base matrix + for (i = 0; i < 6; ++i) { + baseMatrix[i] = oldBaseMatrix[i]; + } + + // restore parser + parser = oldParser; + + if (softMask || transpGroup) { + builder->popTransparencyGroup(state); + } + + // restore graphics state + restoreState(); + + // pop resource stack + popResources(); + + if (softMask) { + builder->setSoftMask(state, bbox, alpha, transferFunc, backdropColor); + } else if (transpGroup) { + builder->paintTransparencyGroup(state, bbox); + } + + return; +} + +//------------------------------------------------------------------------ +// in-line image operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginImage(Object /*args*/[], int /*numArgs*/) +{ + // build dict/stream + Stream *str = buildImageStream(); + + // display the image + if (str) { + doImage(nullptr, str, gTrue); + + // skip 'EI' tag + int c1 = str->getUndecodedStream()->getChar(); + int c2 = str->getUndecodedStream()->getChar(); + while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) { + c1 = c2; + c2 = str->getUndecodedStream()->getChar(); + } + delete str; + } +} + +Stream *PdfParser::buildImageStream() { + Object dict; + Object obj; + Stream *str; + + // build dictionary +#if defined(POPPLER_NEW_OBJECT_API) + dict = Object(new Dict(xref)); +#else + dict.initDict(xref); +#endif + _POPPLER_CALL(obj, parser->getObj); + while (!obj.isCmd(const_cast<char*>("ID")) && !obj.isEOF()) { + if (!obj.isName()) { + error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object"); + _POPPLER_FREE(obj); + } else { + Object obj2; + _POPPLER_CALL(obj2, parser->getObj); + if (obj2.isEOF() || obj2.isError()) { + _POPPLER_FREE(obj); + break; + } + _POPPLER_DICTADD(dict, obj.getName(), obj2); + _POPPLER_FREE(obj); + _POPPLER_FREE(obj2); + } + _POPPLER_CALL(obj, parser->getObj); + } + if (obj.isEOF()) { + error(errSyntaxError, getPos(), "End of file in inline image"); + _POPPLER_FREE(obj); + _POPPLER_FREE(dict); + return nullptr; + } + _POPPLER_FREE(obj); + + // make stream +#if defined(POPPLER_NEW_OBJECT_API) + str = new EmbedStream(parser->getStream(), dict.copy(), gFalse, 0); + str = str->addFilters(dict.getDict()); +#else + str = new EmbedStream(parser->getStream(), &dict, gFalse, 0); + str = str->addFilters(&dict); +#endif + + return str; +} + +void PdfParser::opImageData(Object /*args*/[], int /*numArgs*/) +{ + error(errInternal, getPos(), "Internal: got 'ID' operator"); +} + +void PdfParser::opEndImage(Object /*args*/[], int /*numArgs*/) +{ + error(errInternal, getPos(), "Internal: got 'EI' operator"); +} + +//------------------------------------------------------------------------ +// type 3 font operators +//------------------------------------------------------------------------ + +void PdfParser::opSetCharWidth(Object /*args*/[], int /*numArgs*/) +{ +} + +void PdfParser::opSetCacheDevice(Object /*args*/[], int /*numArgs*/) +{ +} + +//------------------------------------------------------------------------ +// compatibility operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginIgnoreUndef(Object /*args*/[], int /*numArgs*/) +{ + ++ignoreUndef; +} + +void PdfParser::opEndIgnoreUndef(Object /*args*/[], int /*numArgs*/) +{ + if (ignoreUndef > 0) + --ignoreUndef; +} + +//------------------------------------------------------------------------ +// marked content operators +//------------------------------------------------------------------------ + +void PdfParser::opBeginMarkedContent(Object args[], int numArgs) { + if (printCommands) { + printf(" marked content: %s ", args[0].getName()); + if (numArgs == 2) + args[2].print(stdout); + printf("\n"); + fflush(stdout); + } + + if(numArgs == 2) { + //out->beginMarkedContent(args[0].getName(),args[1].getDict()); + } else { + //out->beginMarkedContent(args[0].getName()); + } +} + +void PdfParser::opEndMarkedContent(Object /*args*/[], int /*numArgs*/) +{ + //out->endMarkedContent(); +} + +void PdfParser::opMarkPoint(Object args[], int numArgs) { + if (printCommands) { + printf(" mark point: %s ", args[0].getName()); + if (numArgs == 2) + args[2].print(stdout); + printf("\n"); + fflush(stdout); + } + + if(numArgs == 2) { + //out->markPoint(args[0].getName(),args[1].getDict()); + } else { + //out->markPoint(args[0].getName()); + } + +} + +//------------------------------------------------------------------------ +// misc +//------------------------------------------------------------------------ + +void PdfParser::saveState() { + bool is_radial = false; + + GfxPattern *pattern = state->getFillPattern(); + if (pattern != nullptr) + if (pattern->getType() == 2 ) { + GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern); + GfxShading *shading = shading_pattern->getShading(); + if (shading->getType() == 3) + is_radial = true; + } + + builder->saveState(); + if (is_radial) + state->save(); // nasty hack to prevent GfxRadialShading from getting corrupted during copy operation + else + state = state->save(); // see LP Bug 919176 comment 8 + clipHistory = clipHistory->save(); +} + +void PdfParser::restoreState() { + clipHistory = clipHistory->restore(); + state = state->restore(); + builder->restoreState(); +} + +void PdfParser::pushResources(Dict *resDict) { + res = new GfxResources(xref, resDict, res); +} + +void PdfParser::popResources() { + GfxResources *resPtr; + + resPtr = res->getNext(); + delete res; + res = resPtr; +} + +void PdfParser::setDefaultApproximationPrecision() { + for (int i = 1; i <= pdfNumShadingTypes; ++i) { + setApproximationPrecision(i, defaultShadingColorDelta, defaultShadingMaxDepth); + } +} + +void PdfParser::setApproximationPrecision(int shadingType, double colorDelta, + int maxDepth) { + + if (shadingType > pdfNumShadingTypes || shadingType < 1) { + return; + } + colorDeltas[shadingType-1] = dblToCol(colorDelta); + maxDepths[shadingType-1] = maxDepth; +} + +//------------------------------------------------------------------------ +// ClipHistoryEntry +//------------------------------------------------------------------------ + +ClipHistoryEntry::ClipHistoryEntry(GfxPath *clipPathA, GfxClipType clipTypeA) : + saved(nullptr), + clipPath((clipPathA) ? clipPathA->copy() : nullptr), + clipType(clipTypeA) +{ +} + +ClipHistoryEntry::~ClipHistoryEntry() +{ + if (clipPath) { + delete clipPath; + clipPath = nullptr; + } +} + +void ClipHistoryEntry::setClip(_POPPLER_CONST_83 GfxPath *clipPathA, GfxClipType clipTypeA) { + // Free previous clip path + if (clipPath) { + delete clipPath; + } + if (clipPathA) { + clipPath = clipPathA->copy(); + clipType = clipTypeA; + } else { + clipPath = nullptr; + clipType = clipNormal; + } +} + +ClipHistoryEntry *ClipHistoryEntry::save() { + ClipHistoryEntry *newEntry = new ClipHistoryEntry(this); + newEntry->saved = this; + + return newEntry; +} + +ClipHistoryEntry *ClipHistoryEntry::restore() { + ClipHistoryEntry *oldEntry; + + if (saved) { + oldEntry = saved; + saved = nullptr; + delete this; // TODO really should avoid deleting from inside. + } else { + oldEntry = this; + } + + return oldEntry; +} + +ClipHistoryEntry::ClipHistoryEntry(ClipHistoryEntry *other) { + if (other->clipPath) { + this->clipPath = other->clipPath->copy(); + this->clipType = other->clipType; + } else { + this->clipPath = nullptr; + this->clipType = clipNormal; + } + saved = nullptr; +} + +#endif /* HAVE_POPPLER */ diff --git a/src/extension/internal/pdfinput/pdf-parser.h b/src/extension/internal/pdfinput/pdf-parser.h new file mode 100644 index 0000000..b92c415 --- /dev/null +++ b/src/extension/internal/pdfinput/pdf-parser.h @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * PDF parsing using libpoppler. + *//* + * Authors: + * see git history + * + * Derived from Gfx.h from poppler (?) which derives from Xpdf, Copyright 1996-2003 Glyph & Cog, LLC, which is under GPL2+. + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef PDF_PARSER_H +#define PDF_PARSER_H + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#ifdef HAVE_POPPLER +#include "poppler-transition-api.h" + +#ifdef USE_GCC_PRAGMAS +#pragma interface +#endif + +namespace Inkscape { + namespace Extension { + namespace Internal { + class SvgBuilder; + } + } +} + +// TODO clean up and remove using: +using Inkscape::Extension::Internal::SvgBuilder; + +#include "glib/poppler-features.h" +#include "Object.h" + +#include <map> +#include <memory> +#include <string> + +class GooString; +class XRef; +class Array; +class Stream; +class Parser; +class Dict; +class Function; +class OutputDev; +class GfxFontDict; +class GfxFont; +class GfxPattern; +class GfxTilingPattern; +class GfxShadingPattern; +class GfxShading; +class GfxFunctionShading; +class GfxAxialShading; +class GfxRadialShading; +class GfxGouraudTriangleShading; +class GfxPatchMeshShading; +struct GfxPatch; +class GfxState; +struct GfxColor; +class GfxColorSpace; +class Gfx; +class GfxResources; +class PDFRectangle; +class AnnotBorderStyle; + +class PdfParser; + +class ClipHistoryEntry; + +//------------------------------------------------------------------------ + +#ifndef GFX_H +enum GfxClipType { + clipNone, + clipNormal, + clipEO +}; + +enum TchkType { + tchkBool, // boolean + tchkInt, // integer + tchkNum, // number (integer or real) + tchkString, // string + tchkName, // name + tchkArray, // array + tchkProps, // properties (dictionary or name) + tchkSCN, // scn/SCN args (number of name) + tchkNone // used to avoid empty initializer lists +}; +#endif /* GFX_H */ + +#define maxOperatorArgs 33 + +struct PdfOperator { + char name[4]; + int numArgs; + TchkType tchk[maxOperatorArgs]; + void (PdfParser::*func)(Object args[], int numArgs); +}; + +#undef maxOperatorArgs + +struct OpHistoryEntry { + const char *name; // operator's name + GfxState *state; // saved state, NULL if none + GBool executed; // whether the operator has been executed + + OpHistoryEntry *next; // next entry on stack + unsigned depth; // total number of entries descending from this +}; + +//------------------------------------------------------------------------ +// PdfParser +//------------------------------------------------------------------------ + +//------------------------------------------------------------------------ +// constants +//------------------------------------------------------------------------ + +#define pdfFunctionShading 1 +#define pdfAxialShading 2 +#define pdfRadialShading 3 +#define pdfGouraudTriangleShading 4 +#define pdfPatchMeshShading 5 +#define pdfNumShadingTypes 5 + + + +/** + * PDF parsing module using libpoppler's facilities. + */ +class PdfParser { +public: + + // Constructor for regular output. + PdfParser(XRef *xrefA, SvgBuilder *builderA, int pageNum, int rotate, + Dict *resDict, + _POPPLER_CONST PDFRectangle *box, + _POPPLER_CONST PDFRectangle *cropBox); + + // Constructor for a sub-page object. + PdfParser(XRef *xrefA, Inkscape::Extension::Internal::SvgBuilder *builderA, + Dict *resDict, + _POPPLER_CONST PDFRectangle *box); + + virtual ~PdfParser(); + + // Interpret a stream or array of streams. + void parse(Object *obj, GBool topLevel = gTrue); + + // Save graphics state. + void saveState(); + + // Restore graphics state. + void restoreState(); + + // Get the current graphics state object. + GfxState *getState() { return state; } + + // Set the precision of approximation for specific shading fills. + void setApproximationPrecision(int shadingType, double colorDelta, int maxDepth); + +private: + + XRef *xref; // the xref table for this PDF file + SvgBuilder *builder; // SVG generator + GBool subPage; // is this a sub-page object? + GBool printCommands; // print the drawing commands (for debugging) + GfxResources *res; // resource stack + + GfxState *state; // current graphics state + GBool fontChanged; // set if font or text matrix has changed + GfxClipType clip; // do a clip? + int ignoreUndef; // current BX/EX nesting level + double baseMatrix[6]; // default matrix for most recent + // page/form/pattern + int formDepth; + + Parser *parser; // parser for page content stream(s) + + static PdfOperator opTab[]; // table of operators + + int colorDeltas[pdfNumShadingTypes]; + // max deltas allowed in any color component + // for the approximation of shading fills + int maxDepths[pdfNumShadingTypes]; // max recursive depths + + ClipHistoryEntry *clipHistory; // clip path stack + OpHistoryEntry *operatorHistory; // list containing the last N operators + + //! Caches color spaces by name + std::map<std::string, std::unique_ptr<GfxColorSpace>> colorSpacesCache; + + GfxColorSpace *lookupColorSpaceCopy(Object &); + + void setDefaultApproximationPrecision(); // init color deltas + void pushOperator(const char *name); + OpHistoryEntry *popOperator(); + const char *getPreviousOperator(unsigned int look_back=1); // returns the nth previous operator's name + + void go(GBool topLevel); + void execOp(Object *cmd, Object args[], int numArgs); + PdfOperator *findOp(const char *name); + GBool checkArg(Object *arg, TchkType type); + int getPos(); + + // graphics state operators + void opSave(Object args[], int numArgs); + void opRestore(Object args[], int numArgs); + void opConcat(Object args[], int numArgs); + void opSetDash(Object args[], int numArgs); + void opSetFlat(Object args[], int numArgs); + void opSetLineJoin(Object args[], int numArgs); + void opSetLineCap(Object args[], int numArgs); + void opSetMiterLimit(Object args[], int numArgs); + void opSetLineWidth(Object args[], int numArgs); + void opSetExtGState(Object args[], int numArgs); + void doSoftMask(Object *str, GBool alpha, + GfxColorSpace *blendingColorSpace, + GBool isolated, GBool knockout, + Function *transferFunc, GfxColor *backdropColor); + void opSetRenderingIntent(Object args[], int numArgs); + + // color operators + void opSetFillGray(Object args[], int numArgs); + void opSetStrokeGray(Object args[], int numArgs); + void opSetFillCMYKColor(Object args[], int numArgs); + void opSetStrokeCMYKColor(Object args[], int numArgs); + void opSetFillRGBColor(Object args[], int numArgs); + void opSetStrokeRGBColor(Object args[], int numArgs); + void opSetFillColorSpace(Object args[], int numArgs); + void opSetStrokeColorSpace(Object args[], int numArgs); + void opSetFillColor(Object args[], int numArgs); + void opSetStrokeColor(Object args[], int numArgs); + void opSetFillColorN(Object args[], int numArgs); + void opSetStrokeColorN(Object args[], int numArgs); + + // path segment operators + void opMoveTo(Object args[], int numArgs); + void opLineTo(Object args[], int numArgs); + void opCurveTo(Object args[], int numArgs); + void opCurveTo1(Object args[], int numArgs); + void opCurveTo2(Object args[], int numArgs); + void opRectangle(Object args[], int numArgs); + void opClosePath(Object args[], int numArgs); + + // path painting operators + void opEndPath(Object args[], int numArgs); + void opStroke(Object args[], int numArgs); + void opCloseStroke(Object args[], int numArgs); + void opFill(Object args[], int numArgs); + void opEOFill(Object args[], int numArgs); + void opFillStroke(Object args[], int numArgs); + void opCloseFillStroke(Object args[], int numArgs); + void opEOFillStroke(Object args[], int numArgs); + void opCloseEOFillStroke(Object args[], int numArgs); + void doFillAndStroke(GBool eoFill); + void doPatternFillFallback(GBool eoFill); + void doPatternStrokeFallback(); + void doShadingPatternFillFallback(GfxShadingPattern *sPat, + GBool stroke, GBool eoFill); + void opShFill(Object args[], int numArgs); + void doFunctionShFill(GfxFunctionShading *shading); + void doFunctionShFill1(GfxFunctionShading *shading, + double x0, double y0, + double x1, double y1, + GfxColor *colors, int depth); + void doGouraudTriangleShFill(GfxGouraudTriangleShading *shading); + void gouraudFillTriangle(double x0, double y0, GfxColor *color0, + double x1, double y1, GfxColor *color1, + double x2, double y2, GfxColor *color2, + int nComps, int depth); + void doPatchMeshShFill(GfxPatchMeshShading *shading); + void fillPatch(_POPPLER_CONST GfxPatch *patch, int nComps, int depth); + void doEndPath(); + + // path clipping operators + void opClip(Object args[], int numArgs); + void opEOClip(Object args[], int numArgs); + + // text object operators + void opBeginText(Object args[], int numArgs); + void opEndText(Object args[], int numArgs); + + // text state operators + void opSetCharSpacing(Object args[], int numArgs); + void opSetFont(Object args[], int numArgs); + void opSetTextLeading(Object args[], int numArgs); + void opSetTextRender(Object args[], int numArgs); + void opSetTextRise(Object args[], int numArgs); + void opSetWordSpacing(Object args[], int numArgs); + void opSetHorizScaling(Object args[], int numArgs); + + // text positioning operators + void opTextMove(Object args[], int numArgs); + void opTextMoveSet(Object args[], int numArgs); + void opSetTextMatrix(Object args[], int numArgs); + void opTextNextLine(Object args[], int numArgs); + + // text string operators + void opShowText(Object args[], int numArgs); + void opMoveShowText(Object args[], int numArgs); + void opMoveSetShowText(Object args[], int numArgs); + void opShowSpaceText(Object args[], int numArgs); +#if POPPLER_CHECK_VERSION(0,64,0) + void doShowText(const GooString *s); +#else + void doShowText(GooString *s); +#endif + + + // XObject operators + void opXObject(Object args[], int numArgs); + void doImage(Object *ref, Stream *str, GBool inlineImg); + void doForm(Object *str); + void doForm1(Object *str, Dict *resDict, double *matrix, double *bbox, + GBool transpGroup = gFalse, GBool softMask = gFalse, + GfxColorSpace *blendingColorSpace = nullptr, + GBool isolated = gFalse, GBool knockout = gFalse, + GBool alpha = gFalse, Function *transferFunc = nullptr, + GfxColor *backdropColor = nullptr); + + // in-line image operators + void opBeginImage(Object args[], int numArgs); + Stream *buildImageStream(); + void opImageData(Object args[], int numArgs); + void opEndImage(Object args[], int numArgs); + + // type 3 font operators + void opSetCharWidth(Object args[], int numArgs); + void opSetCacheDevice(Object args[], int numArgs); + + // compatibility operators + void opBeginIgnoreUndef(Object args[], int numArgs); + void opEndIgnoreUndef(Object args[], int numArgs); + + // marked content operators + void opBeginMarkedContent(Object args[], int numArgs); + void opEndMarkedContent(Object args[], int numArgs); + void opMarkPoint(Object args[], int numArgs); + + void pushResources(Dict *resDict); + void popResources(); +}; + +#endif /* HAVE_POPPLER */ + +#endif /* PDF_PARSER_H */ diff --git a/src/extension/internal/pdfinput/poppler-transition-api.h b/src/extension/internal/pdfinput/poppler-transition-api.h new file mode 100644 index 0000000..9671811 --- /dev/null +++ b/src/extension/internal/pdfinput/poppler-transition-api.h @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * TODO short description + *//* + * Authors: + * see git history + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_POPPLER_TRANSITION_API_H +#define SEEN_POPPLER_TRANSITION_API_H + +#include <glib/poppler-features.h> + +#if POPPLER_CHECK_VERSION(0, 83, 0) +#define _POPPLER_CONST_83 const +#else +#define _POPPLER_CONST_83 +#endif + +#if POPPLER_CHECK_VERSION(0, 82, 0) +#define _POPPLER_CONST_82 const +#else +#define _POPPLER_CONST_82 +#endif + +#if POPPLER_CHECK_VERSION(0, 76, 0) +#define _POPPLER_NEW_PARSER(xref, obj) Parser(xref, obj, gFalse) +#else +#define _POPPLER_NEW_PARSER(xref, obj) Parser(xref, new Lexer(xref, obj), gFalse) +#endif + +#if POPPLER_CHECK_VERSION(0, 83, 0) +#define _POPPLER_NEW_GLOBAL_PARAMS(args...) std::unique_ptr<GlobalParams>(new GlobalParams(args)) +#else +#define _POPPLER_NEW_GLOBAL_PARAMS(args...) new GlobalParams(args) +#endif + + +#if POPPLER_CHECK_VERSION(0, 72, 0) +#define getCString c_str +#endif + +#if POPPLER_CHECK_VERSION(0,71,0) +typedef bool GBool; +#define gTrue true +#define gFalse false +#endif + +#if POPPLER_CHECK_VERSION(0,70,0) +#define _POPPLER_CONST const +#else +#define _POPPLER_CONST +#endif + +#if POPPLER_CHECK_VERSION(0,69,0) +#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(key, std::move(obj)) +#elif POPPLER_CHECK_VERSION(0,58,0) +#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(copyString(key), std::move(obj)) +#else +#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(copyString(key), &obj) +#endif + +#if POPPLER_CHECK_VERSION(0,58,0) +#define POPPLER_NEW_OBJECT_API +#define _POPPLER_FREE(obj) +#define _POPPLER_CALL(ret, func) (ret = func()) +#define _POPPLER_CALL_ARGS(ret, func, ...) (ret = func(__VA_ARGS__)) +#define _POPPLER_CALL_ARGS_DEREF _POPPLER_CALL_ARGS +#else +#define _POPPLER_FREE(obj) (obj).free() +#define _POPPLER_CALL(ret, func) (*func(&ret)) +#define _POPPLER_CALL_ARGS(ret, func, ...) (func(__VA_ARGS__, &ret)) +#define _POPPLER_CALL_ARGS_DEREF(...) (*_POPPLER_CALL_ARGS(__VA_ARGS__)) +#endif + +#if POPPLER_CHECK_VERSION(0, 29, 0) +#define POPPLER_EVEN_NEWER_NEW_COLOR_SPACE_API +#endif + +#if POPPLER_CHECK_VERSION(0, 25, 0) +#define POPPLER_EVEN_NEWER_COLOR_SPACE_API +#endif + +#endif diff --git a/src/extension/internal/pdfinput/svg-builder.cpp b/src/extension/internal/pdfinput/svg-builder.cpp new file mode 100644 index 0000000..1f04ee4 --- /dev/null +++ b/src/extension/internal/pdfinput/svg-builder.cpp @@ -0,0 +1,1938 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Native PDF import using libpoppler. + * + * Authors: + * miklos erdelyi + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2007 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 + +#include <string> + +#ifdef HAVE_POPPLER + +#include "svg-builder.h" +#include "pdf-parser.h" + +#include "document.h" +#include "png.h" + +#include "xml/document.h" +#include "xml/node.h" +#include "xml/repr.h" +#include "svg/svg.h" +#include "svg/path-string.h" +#include "svg/css-ostringstream.h" +#include "svg/svg-color.h" +#include "color.h" +#include "util/units.h" +#include "display/nr-filter-utils.h" +#include "libnrtype/font-instance.h" +#include "object/sp-defs.h" + +#include "Function.h" +#include "GfxState.h" +#include "GfxFont.h" +#include "Stream.h" +#include "Page.h" +#include "UnicodeMap.h" +#include "GlobalParams.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +//#define IFTRACE(_code) _code +#define IFTRACE(_code) + +#define TRACE(_args) IFTRACE(g_print _args) + +/** + * \struct SvgTransparencyGroup + * \brief Holds information about a PDF transparency group + */ +struct SvgTransparencyGroup { + double bbox[6]; // TODO should this be 4? + Inkscape::XML::Node *container; + + bool isolated; + bool knockout; + bool for_softmask; + + SvgTransparencyGroup *next; +}; + +/** + * \class SvgBuilder + * + */ + +SvgBuilder::SvgBuilder(SPDocument *document, gchar *docname, XRef *xref) +{ + _is_top_level = true; + _doc = document; + _docname = docname; + _xref = xref; + _xml_doc = _doc->getReprDoc(); + _container = _root = _doc->getReprRoot(); + _root->setAttribute("xml:space", "preserve"); + _init(); + + // Set default preference settings + _preferences = _xml_doc->createElement("svgbuilder:prefs"); + _preferences->setAttribute("embedImages", "1"); + _preferences->setAttribute("localFonts", "1"); +} + +SvgBuilder::SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root) { + _is_top_level = false; + _doc = parent->_doc; + _docname = parent->_docname; + _xref = parent->_xref; + _xml_doc = parent->_xml_doc; + _preferences = parent->_preferences; + _container = this->_root = root; + _init(); +} + +SvgBuilder::~SvgBuilder() = default; + +void SvgBuilder::_init() { + _font_style = nullptr; + _current_font = nullptr; + _font_specification = nullptr; + _font_scaling = 1; + _need_font_update = true; + _in_text_object = false; + _invalidated_style = true; + _current_state = nullptr; + _width = 0; + _height = 0; + + // Fill _availableFontNames (Bug LP #179589) (code cfr. FontLister) + std::vector<PangoFontFamily *> families; + font_factory::Default()->GetUIFamilies(families); + for (auto & familie : families) { + _availableFontNames.emplace_back(pango_font_family_get_name(familie)); + } + + _transp_group_stack = nullptr; + SvgGraphicsState initial_state; + initial_state.softmask = nullptr; + initial_state.group_depth = 0; + _state_stack.push_back(initial_state); + _node_stack.push_back(_container); + + _ttm[0] = 1; _ttm[1] = 0; _ttm[2] = 0; _ttm[3] = 1; _ttm[4] = 0; _ttm[5] = 0; + _ttm_is_set = false; +} + +void SvgBuilder::setDocumentSize(double width, double height) { + sp_repr_set_svg_double(_root, "width", width); + sp_repr_set_svg_double(_root, "height", height); + this->_width = width; + this->_height = height; +} + +/** + * \brief Sets groupmode of the current container to 'layer' and sets its label if given + */ +void SvgBuilder::setAsLayer(char *layer_name) { + _container->setAttribute("inkscape:groupmode", "layer"); + if (layer_name) { + _container->setAttribute("inkscape:label", layer_name); + } +} + +/** + * \brief Sets the current container's opacity + */ +void SvgBuilder::setGroupOpacity(double opacity) { + sp_repr_set_svg_double(_container, "opacity", CLAMP(opacity, 0.0, 1.0)); +} + +void SvgBuilder::saveState() { + SvgGraphicsState new_state; + new_state.group_depth = 0; + new_state.softmask = _state_stack.back().softmask; + _state_stack.push_back(new_state); + pushGroup(); +} + +void SvgBuilder::restoreState() { + while( _state_stack.back().group_depth > 0 ) { + popGroup(); + } + _state_stack.pop_back(); +} + +Inkscape::XML::Node *SvgBuilder::pushNode(const char *name) { + Inkscape::XML::Node *node = _xml_doc->createElement(name); + _node_stack.push_back(node); + _container = node; + return node; +} + +Inkscape::XML::Node *SvgBuilder::popNode() { + Inkscape::XML::Node *node = nullptr; + if ( _node_stack.size() > 1 ) { + node = _node_stack.back(); + _node_stack.pop_back(); + _container = _node_stack.back(); // Re-set container + } else { + TRACE(("popNode() called when stack is empty\n")); + node = _root; + } + return node; +} + +Inkscape::XML::Node *SvgBuilder::pushGroup() { + Inkscape::XML::Node *saved_container = _container; + Inkscape::XML::Node *node = pushNode("svg:g"); + saved_container->appendChild(node); + Inkscape::GC::release(node); + _state_stack.back().group_depth++; + // Set as a layer if this is a top-level group + if ( _container->parent() == _root && _is_top_level ) { + static int layer_count = 1; + if ( layer_count > 1 ) { + gchar *layer_name = g_strdup_printf("%s%d", _docname, layer_count); + setAsLayer(layer_name); + g_free(layer_name); + } else { + setAsLayer(_docname); + } + } + if (_container->parent()->attribute("inkscape:groupmode") != nullptr) { + _ttm[0] = _ttm[3] = 1.0; // clear ttm if parent is a layer + _ttm[1] = _ttm[2] = _ttm[4] = _ttm[5] = 0.0; + _ttm_is_set = false; + } + return _container; +} + +Inkscape::XML::Node *SvgBuilder::popGroup() { + if (_container != _root) { // Pop if the current container isn't root + popNode(); + _state_stack.back().group_depth--; + } + + return _container; +} + +Inkscape::XML::Node *SvgBuilder::getContainer() { + return _container; +} + +static gchar *svgConvertRGBToText(double r, double g, double b) { + using Inkscape::Filters::clamp; + static gchar tmp[1023] = {0}; + snprintf(tmp, 1023, + "#%02x%02x%02x", + clamp(SP_COLOR_F_TO_U(r)), + clamp(SP_COLOR_F_TO_U(g)), + clamp(SP_COLOR_F_TO_U(b))); + return (gchar *)&tmp; +} + +static gchar *svgConvertGfxRGB(GfxRGB *color) { + double r = (double)color->r / 65535.0; + double g = (double)color->g / 65535.0; + double b = (double)color->b / 65535.0; + return svgConvertRGBToText(r, g, b); +} + +static void svgSetTransform(Inkscape::XML::Node *node, double c0, double c1, + double c2, double c3, double c4, double c5) { + Geom::Affine matrix(c0, c1, c2, c3, c4, c5); + gchar *transform_text = sp_svg_transform_write(matrix); + node->setAttribute("transform", transform_text); + g_free(transform_text); +} + +/** + * \brief Generates a SVG path string from poppler's data structure + */ +static gchar *svgInterpretPath(_POPPLER_CONST_83 GfxPath *path) { + Inkscape::SVG::PathString pathString; + for (int i = 0 ; i < path->getNumSubpaths() ; ++i ) { + _POPPLER_CONST_83 GfxSubpath *subpath = path->getSubpath(i); + if (subpath->getNumPoints() > 0) { + pathString.moveTo(subpath->getX(0), subpath->getY(0)); + int j = 1; + while (j < subpath->getNumPoints()) { + if (subpath->getCurve(j)) { + pathString.curveTo(subpath->getX(j), subpath->getY(j), + subpath->getX(j+1), subpath->getY(j+1), + subpath->getX(j+2), subpath->getY(j+2)); + + j += 3; + } else { + pathString.lineTo(subpath->getX(j), subpath->getY(j)); + ++j; + } + } + if (subpath->isClosed()) { + pathString.closePath(); + } + } + } + + return g_strdup(pathString.c_str()); +} + +/** + * \brief Sets stroke style from poppler's GfxState data structure + * Uses the given SPCSSAttr for storing the style properties + */ +void SvgBuilder::_setStrokeStyle(SPCSSAttr *css, GfxState *state) { + // Stroke color/pattern + if ( state->getStrokeColorSpace()->getMode() == csPattern ) { + gchar *urltext = _createPattern(state->getStrokePattern(), state, true); + sp_repr_css_set_property(css, "stroke", urltext); + if (urltext) { + g_free(urltext); + } + } else { + GfxRGB stroke_color; + state->getStrokeRGB(&stroke_color); + sp_repr_css_set_property(css, "stroke", svgConvertGfxRGB(&stroke_color)); + } + + // Opacity + Inkscape::CSSOStringStream os_opacity; + os_opacity << state->getStrokeOpacity(); + sp_repr_css_set_property(css, "stroke-opacity", os_opacity.str().c_str()); + + // Line width + Inkscape::CSSOStringStream os_width; + double lw = state->getLineWidth(); + if (lw > 0.0) { + os_width << lw; + } else { + // emit a stroke which is 1px in toplevel user units + double pxw = Inkscape::Util::Quantity::convert(1.0, "pt", "px"); + os_width << 1.0 / state->transformWidth(pxw); + } + sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str()); + + // Line cap + switch (state->getLineCap()) { + case 0: + sp_repr_css_set_property(css, "stroke-linecap", "butt"); + break; + case 1: + sp_repr_css_set_property(css, "stroke-linecap", "round"); + break; + case 2: + sp_repr_css_set_property(css, "stroke-linecap", "square"); + break; + } + + // Line join + switch (state->getLineJoin()) { + case 0: + sp_repr_css_set_property(css, "stroke-linejoin", "miter"); + break; + case 1: + sp_repr_css_set_property(css, "stroke-linejoin", "round"); + break; + case 2: + sp_repr_css_set_property(css, "stroke-linejoin", "bevel"); + break; + } + + // Miterlimit + Inkscape::CSSOStringStream os_ml; + os_ml << state->getMiterLimit(); + sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str()); + + // Line dash + double *dash_pattern; + int dash_length; + double dash_start; + state->getLineDash(&dash_pattern, &dash_length, &dash_start); + if ( dash_length > 0 ) { + Inkscape::CSSOStringStream os_array; + for ( int i = 0 ; i < dash_length ; i++ ) { + os_array << dash_pattern[i]; + if (i < (dash_length - 1)) { + os_array << ","; + } + } + sp_repr_css_set_property(css, "stroke-dasharray", os_array.str().c_str()); + + Inkscape::CSSOStringStream os_offset; + os_offset << dash_start; + sp_repr_css_set_property(css, "stroke-dashoffset", os_offset.str().c_str()); + } else { + sp_repr_css_set_property(css, "stroke-dasharray", "none"); + sp_repr_css_set_property(css, "stroke-dashoffset", nullptr); + } +} + +/** + * \brief Sets fill style from poppler's GfxState data structure + * Uses the given SPCSSAttr for storing the style properties. + */ +void SvgBuilder::_setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd) { + + // Fill color/pattern + if ( state->getFillColorSpace()->getMode() == csPattern ) { + gchar *urltext = _createPattern(state->getFillPattern(), state); + sp_repr_css_set_property(css, "fill", urltext); + if (urltext) { + g_free(urltext); + } + } else { + GfxRGB fill_color; + state->getFillRGB(&fill_color); + sp_repr_css_set_property(css, "fill", svgConvertGfxRGB(&fill_color)); + } + + // Opacity + Inkscape::CSSOStringStream os_opacity; + os_opacity << state->getFillOpacity(); + sp_repr_css_set_property(css, "fill-opacity", os_opacity.str().c_str()); + + // Fill rule + sp_repr_css_set_property(css, "fill-rule", even_odd ? "evenodd" : "nonzero"); +} + +/** + * \brief Sets blend style properties from poppler's GfxState data structure + * \update a SPCSSAttr with all mix-blend-mode set + */ +void SvgBuilder::_setBlendMode(Inkscape::XML::Node *node, GfxState *state) +{ + SPCSSAttr *css = sp_repr_css_attr(node, "style"); + GfxBlendMode blendmode = state->getBlendMode(); + if (blendmode) { + sp_repr_css_set_property(css, "mix-blend-mode", enum_blend_mode[blendmode].key); + } + Glib::ustring value; + sp_repr_css_write_string(css, value); + node->setAttributeOrRemoveIfEmpty("style", value); + sp_repr_css_attr_unref(css); +} +/** + * \brief Sets style properties from poppler's GfxState data structure + * \return SPCSSAttr with all the relevant properties set + */ +SPCSSAttr *SvgBuilder::_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd) { + SPCSSAttr *css = sp_repr_css_attr_new(); + if (fill) { + _setFillStyle(css, state, even_odd); + } else { + sp_repr_css_set_property(css, "fill", "none"); + } + + if (stroke) { + _setStrokeStyle(css, state); + } else { + sp_repr_css_set_property(css, "stroke", "none"); + } + + return css; +} + +/** + * \brief Emits the current path in poppler's GfxState data structure + * Can be used to do filling and stroking at once. + * + * \param fill whether the path should be filled + * \param stroke whether the path should be stroked + * \param even_odd whether the even-odd rule should be used when filling the path + */ +void SvgBuilder::addPath(GfxState *state, bool fill, bool stroke, bool even_odd) { + Inkscape::XML::Node *path = _xml_doc->createElement("svg:path"); + gchar *pathtext = svgInterpretPath(state->getPath()); + path->setAttribute("d", pathtext); + g_free(pathtext); + + // Set style + SPCSSAttr *css = _setStyle(state, fill, stroke, even_odd); + sp_repr_css_change(path, css, "style"); + sp_repr_css_attr_unref(css); + _setBlendMode(path, state); + _container->appendChild(path); + Inkscape::GC::release(path); +} + +/** + * \brief Emits the current path in poppler's GfxState data structure + * The path is set to be filled with the given shading. + */ +void SvgBuilder::addShadedFill(GfxShading *shading, double *matrix, GfxPath *path, + bool even_odd) { + + Inkscape::XML::Node *path_node = _xml_doc->createElement("svg:path"); + gchar *pathtext = svgInterpretPath(path); + path_node->setAttribute("d", pathtext); + g_free(pathtext); + + // Set style + SPCSSAttr *css = sp_repr_css_attr_new(); + gchar *id = _createGradient(shading, matrix, true); + if (id) { + gchar *urltext = g_strdup_printf ("url(#%s)", id); + sp_repr_css_set_property(css, "fill", urltext); + g_free(urltext); + g_free(id); + } else { + sp_repr_css_attr_unref(css); + Inkscape::GC::release(path_node); + return; + } + if (even_odd) { + sp_repr_css_set_property(css, "fill-rule", "evenodd"); + } + sp_repr_css_set_property(css, "stroke", "none"); + sp_repr_css_change(path_node, css, "style"); + sp_repr_css_attr_unref(css); + + _container->appendChild(path_node); + Inkscape::GC::release(path_node); + + // Remove the clipping path emitted before the 'sh' operator + int up_walk = 0; + Inkscape::XML::Node *node = _container->parent(); + while( node && node->childCount() == 1 && up_walk < 3 ) { + gchar const *clip_path_url = node->attribute("clip-path"); + if (clip_path_url) { + // Obtain clipping path's id from the URL + gchar clip_path_id[32]; + strncpy(clip_path_id, clip_path_url + 5, strlen(clip_path_url) - 6); + clip_path_id[sizeof (clip_path_id) - 1] = '\0'; + SPObject *clip_obj = _doc->getObjectById(clip_path_id); + if (clip_obj) { + clip_obj->deleteObject(); + node->removeAttribute("clip-path"); + TRACE(("removed clipping path: %s\n", clip_path_id)); + } + break; + } + node = node->parent(); + up_walk++; + } +} + +/** + * \brief Clips to the current path set in GfxState + * \param state poppler's data structure + * \param even_odd whether the even-odd rule should be applied + */ +void SvgBuilder::clip(GfxState *state, bool even_odd) { + pushGroup(); + setClipPath(state, even_odd); +} + +void SvgBuilder::setClipPath(GfxState *state, bool even_odd) { + // Create the clipPath repr + Inkscape::XML::Node *clip_path = _xml_doc->createElement("svg:clipPath"); + clip_path->setAttribute("clipPathUnits", "userSpaceOnUse"); + // Create the path + Inkscape::XML::Node *path = _xml_doc->createElement("svg:path"); + gchar *pathtext = svgInterpretPath(state->getPath()); + path->setAttribute("d", pathtext); + g_free(pathtext); + if (even_odd) { + path->setAttribute("clip-rule", "evenodd"); + } + clip_path->appendChild(path); + Inkscape::GC::release(path); + // Append clipPath to defs and get id + _doc->getDefs()->getRepr()->appendChild(clip_path); + gchar *urltext = g_strdup_printf ("url(#%s)", clip_path->attribute("id")); + Inkscape::GC::release(clip_path); + _container->setAttribute("clip-path", urltext); + g_free(urltext); +} + +/** + * \brief Fills the given array with the current container's transform, if set + * \param transform array of doubles to be filled + * \return true on success; false on invalid transformation + */ +bool SvgBuilder::getTransform(double *transform) { + Geom::Affine svd; + gchar const *tr = _container->attribute("transform"); + bool valid = sp_svg_transform_read(tr, &svd); + if (valid) { + for ( int i = 0 ; i < 6 ; i++ ) { + transform[i] = svd[i]; + } + return true; + } else { + return false; + } +} + +/** + * \brief Sets the transformation matrix of the current container + */ +void SvgBuilder::setTransform(double c0, double c1, double c2, double c3, + double c4, double c5) { + // do not remember the group which is a layer + if ((_container->attribute("inkscape:groupmode") == nullptr) && !_ttm_is_set) { + _ttm[0] = c0; + _ttm[1] = c1; + _ttm[2] = c2; + _ttm[3] = c3; + _ttm[4] = c4; + _ttm[5] = c5; + _ttm_is_set = true; + } + + // Avoid transforming a group with an already set clip-path + if ( _container->attribute("clip-path") != nullptr ) { + pushGroup(); + } + TRACE(("setTransform: %f %f %f %f %f %f\n", c0, c1, c2, c3, c4, c5)); + svgSetTransform(_container, c0, c1, c2, c3, c4, c5); +} + +void SvgBuilder::setTransform(double const *transform) { + setTransform(transform[0], transform[1], transform[2], transform[3], + transform[4], transform[5]); +} + +/** + * \brief Checks whether the given pattern type can be represented in SVG + * Used by PdfParser to decide when to do fallback operations. + */ +bool SvgBuilder::isPatternTypeSupported(GfxPattern *pattern) { + if ( pattern != nullptr ) { + if ( pattern->getType() == 2 ) { // shading pattern + GfxShading *shading = (static_cast<GfxShadingPattern *>(pattern))->getShading(); + int shadingType = shading->getType(); + if ( shadingType == 2 || // axial shading + shadingType == 3 ) { // radial shading + return true; + } + return false; + } else if ( pattern->getType() == 1 ) { // tiling pattern + return true; + } + } + + return false; +} + +/** + * \brief Creates a pattern from poppler's data structure + * Handles linear and radial gradients. Creates a new PdfParser and uses it to + * build a tiling pattern. + * \return a url pointing to the created pattern + */ +gchar *SvgBuilder::_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke) { + gchar *id = nullptr; + if ( pattern != nullptr ) { + if ( pattern->getType() == 2 ) { // Shading pattern + GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern); + const double *ptm; + double m[6] = {1, 0, 0, 1, 0, 0}; + double det; + + // construct a (pattern space) -> (current space) transform matrix + + ptm = shading_pattern->getMatrix(); + det = _ttm[0] * _ttm[3] - _ttm[1] * _ttm[2]; + if (det) { + double ittm[6]; // invert ttm + ittm[0] = _ttm[3] / det; + ittm[1] = -_ttm[1] / det; + ittm[2] = -_ttm[2] / det; + ittm[3] = _ttm[0] / det; + ittm[4] = (_ttm[2] * _ttm[5] - _ttm[3] * _ttm[4]) / det; + ittm[5] = (_ttm[1] * _ttm[4] - _ttm[0] * _ttm[5]) / det; + m[0] = ptm[0] * ittm[0] + ptm[1] * ittm[2]; + m[1] = ptm[0] * ittm[1] + ptm[1] * ittm[3]; + m[2] = ptm[2] * ittm[0] + ptm[3] * ittm[2]; + m[3] = ptm[2] * ittm[1] + ptm[3] * ittm[3]; + m[4] = ptm[4] * ittm[0] + ptm[5] * ittm[2] + ittm[4]; + m[5] = ptm[4] * ittm[1] + ptm[5] * ittm[3] + ittm[5]; + } + id = _createGradient(shading_pattern->getShading(), + m, + !is_stroke); + } else if ( pattern->getType() == 1 ) { // Tiling pattern + id = _createTilingPattern(static_cast<GfxTilingPattern*>(pattern), state, is_stroke); + } + } else { + return nullptr; + } + gchar *urltext = g_strdup_printf ("url(#%s)", id); + g_free(id); + return urltext; +} + +/** + * \brief Creates a tiling pattern from poppler's data structure + * Creates a sub-page PdfParser and uses it to parse the pattern's content stream. + * \return id of the created pattern + */ +gchar *SvgBuilder::_createTilingPattern(GfxTilingPattern *tiling_pattern, + GfxState *state, bool is_stroke) { + + Inkscape::XML::Node *pattern_node = _xml_doc->createElement("svg:pattern"); + // Set pattern transform matrix + const double *p2u = tiling_pattern->getMatrix(); + double m[6] = {1, 0, 0, 1, 0, 0}; + double det; + det = _ttm[0] * _ttm[3] - _ttm[1] * _ttm[2]; // see LP Bug 1168908 + if (det) { + double ittm[6]; // invert ttm + ittm[0] = _ttm[3] / det; + ittm[1] = -_ttm[1] / det; + ittm[2] = -_ttm[2] / det; + ittm[3] = _ttm[0] / det; + ittm[4] = (_ttm[2] * _ttm[5] - _ttm[3] * _ttm[4]) / det; + ittm[5] = (_ttm[1] * _ttm[4] - _ttm[0] * _ttm[5]) / det; + m[0] = p2u[0] * ittm[0] + p2u[1] * ittm[2]; + m[1] = p2u[0] * ittm[1] + p2u[1] * ittm[3]; + m[2] = p2u[2] * ittm[0] + p2u[3] * ittm[2]; + m[3] = p2u[2] * ittm[1] + p2u[3] * ittm[3]; + m[4] = p2u[4] * ittm[0] + p2u[5] * ittm[2] + ittm[4]; + m[5] = p2u[4] * ittm[1] + p2u[5] * ittm[3] + ittm[5]; + } + Geom::Affine pat_matrix(m[0], m[1], m[2], m[3], m[4], m[5]); + gchar *transform_text = sp_svg_transform_write(pat_matrix); + pattern_node->setAttribute("patternTransform", transform_text); + g_free(transform_text); + pattern_node->setAttribute("patternUnits", "userSpaceOnUse"); + // Set pattern tiling + // FIXME: don't ignore XStep and YStep + const double *bbox = tiling_pattern->getBBox(); + sp_repr_set_svg_double(pattern_node, "x", 0.0); + sp_repr_set_svg_double(pattern_node, "y", 0.0); + sp_repr_set_svg_double(pattern_node, "width", bbox[2] - bbox[0]); + sp_repr_set_svg_double(pattern_node, "height", bbox[3] - bbox[1]); + + // Convert BBox for PdfParser + PDFRectangle box; + box.x1 = bbox[0]; + box.y1 = bbox[1]; + box.x2 = bbox[2]; + box.y2 = bbox[3]; + // Create new SvgBuilder and sub-page PdfParser + SvgBuilder *pattern_builder = new SvgBuilder(this, pattern_node); + PdfParser *pdf_parser = new PdfParser(_xref, pattern_builder, tiling_pattern->getResDict(), + &box); + // Get pattern color space + GfxPatternColorSpace *pat_cs = (GfxPatternColorSpace *)( is_stroke ? state->getStrokeColorSpace() + : state->getFillColorSpace() ); + // Set fill/stroke colors if this is an uncolored tiling pattern + GfxColorSpace *cs = nullptr; + if ( tiling_pattern->getPaintType() == 2 && ( cs = pat_cs->getUnder() ) ) { + GfxState *pattern_state = pdf_parser->getState(); + pattern_state->setFillColorSpace(cs->copy()); + pattern_state->setFillColor(state->getFillColor()); + pattern_state->setStrokeColorSpace(cs->copy()); + pattern_state->setStrokeColor(state->getFillColor()); + } + + // Generate the SVG pattern + pdf_parser->parse(tiling_pattern->getContentStream()); + + // Cleanup + delete pdf_parser; + delete pattern_builder; + + // Append the pattern to defs + _doc->getDefs()->getRepr()->appendChild(pattern_node); + gchar *id = g_strdup(pattern_node->attribute("id")); + Inkscape::GC::release(pattern_node); + + return id; +} + +/** + * \brief Creates a linear or radial gradient from poppler's data structure + * \param shading poppler's data structure for the shading + * \param matrix gradient transformation, can be null + * \param for_shading true if we're creating this for a shading operator; false otherwise + * \return id of the created object + */ +gchar *SvgBuilder::_createGradient(GfxShading *shading, double *matrix, bool for_shading) { + Inkscape::XML::Node *gradient; + _POPPLER_CONST Function *func; + int num_funcs; + bool extend0, extend1; + + if ( shading->getType() == 2 ) { // Axial shading + gradient = _xml_doc->createElement("svg:linearGradient"); + GfxAxialShading *axial_shading = static_cast<GfxAxialShading*>(shading); + double x1, y1, x2, y2; + axial_shading->getCoords(&x1, &y1, &x2, &y2); + sp_repr_set_svg_double(gradient, "x1", x1); + sp_repr_set_svg_double(gradient, "y1", y1); + sp_repr_set_svg_double(gradient, "x2", x2); + sp_repr_set_svg_double(gradient, "y2", y2); + extend0 = axial_shading->getExtend0(); + extend1 = axial_shading->getExtend1(); + num_funcs = axial_shading->getNFuncs(); + func = axial_shading->getFunc(0); + } else if (shading->getType() == 3) { // Radial shading + gradient = _xml_doc->createElement("svg:radialGradient"); + GfxRadialShading *radial_shading = static_cast<GfxRadialShading*>(shading); + double x1, y1, r1, x2, y2, r2; + radial_shading->getCoords(&x1, &y1, &r1, &x2, &y2, &r2); + // FIXME: the inner circle's radius is ignored here + sp_repr_set_svg_double(gradient, "fx", x1); + sp_repr_set_svg_double(gradient, "fy", y1); + sp_repr_set_svg_double(gradient, "cx", x2); + sp_repr_set_svg_double(gradient, "cy", y2); + sp_repr_set_svg_double(gradient, "r", r2); + extend0 = radial_shading->getExtend0(); + extend1 = radial_shading->getExtend1(); + num_funcs = radial_shading->getNFuncs(); + func = radial_shading->getFunc(0); + } else { // Unsupported shading type + return nullptr; + } + gradient->setAttribute("gradientUnits", "userSpaceOnUse"); + // If needed, flip the gradient transform around the y axis + if (matrix) { + Geom::Affine pat_matrix(matrix[0], matrix[1], matrix[2], matrix[3], + matrix[4], matrix[5]); + if ( !for_shading && _is_top_level ) { + Geom::Affine flip(1.0, 0.0, 0.0, -1.0, 0.0, Inkscape::Util::Quantity::convert(_height, "px", "pt")); + pat_matrix *= flip; + } + gchar *transform_text = sp_svg_transform_write(pat_matrix); + gradient->setAttribute("gradientTransform", transform_text); + g_free(transform_text); + } + + if ( extend0 && extend1 ) { + gradient->setAttribute("spreadMethod", "pad"); + } + + if ( num_funcs > 1 || !_addGradientStops(gradient, shading, func) ) { + Inkscape::GC::release(gradient); + return nullptr; + } + + Inkscape::XML::Node *defs = _doc->getDefs()->getRepr(); + defs->appendChild(gradient); + gchar *id = g_strdup(gradient->attribute("id")); + Inkscape::GC::release(gradient); + + return id; +} + +#define EPSILON 0.0001 +/** + * \brief Adds a stop with the given properties to the gradient's representation + */ +void SvgBuilder::_addStopToGradient(Inkscape::XML::Node *gradient, double offset, + GfxRGB *color, double opacity) { + Inkscape::XML::Node *stop = _xml_doc->createElement("svg:stop"); + SPCSSAttr *css = sp_repr_css_attr_new(); + Inkscape::CSSOStringStream os_opacity; + gchar *color_text = nullptr; + if ( _transp_group_stack != nullptr && _transp_group_stack->for_softmask ) { + double gray = (double)color->r / 65535.0; + gray = CLAMP(gray, 0.0, 1.0); + os_opacity << gray; + color_text = (char*) "#ffffff"; + } else { + os_opacity << opacity; + color_text = svgConvertGfxRGB(color); + } + sp_repr_css_set_property(css, "stop-opacity", os_opacity.str().c_str()); + sp_repr_css_set_property(css, "stop-color", color_text); + + sp_repr_css_change(stop, css, "style"); + sp_repr_css_attr_unref(css); + sp_repr_set_css_double(stop, "offset", offset); + + gradient->appendChild(stop); + Inkscape::GC::release(stop); +} + +static bool svgGetShadingColorRGB(GfxShading *shading, double offset, GfxRGB *result) { + GfxColorSpace *color_space = shading->getColorSpace(); + GfxColor temp; + if ( shading->getType() == 2 ) { // Axial shading + (static_cast<GfxAxialShading*>(shading))->getColor(offset, &temp); + } else if ( shading->getType() == 3 ) { // Radial shading + (static_cast<GfxRadialShading*>(shading))->getColor(offset, &temp); + } else { + return false; + } + // Convert it to RGB + color_space->getRGB(&temp, result); + + return true; +} + +#define INT_EPSILON 8 +bool SvgBuilder::_addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading, + _POPPLER_CONST Function *func) { + int type = func->getType(); + if ( type == 0 || type == 2 ) { // Sampled or exponential function + GfxRGB stop1, stop2; + if ( !svgGetShadingColorRGB(shading, 0.0, &stop1) || + !svgGetShadingColorRGB(shading, 1.0, &stop2) ) { + return false; + } else { + _addStopToGradient(gradient, 0.0, &stop1, 1.0); + _addStopToGradient(gradient, 1.0, &stop2, 1.0); + } + } else if ( type == 3 ) { // Stitching + auto stitchingFunc = static_cast<_POPPLER_CONST StitchingFunction*>(func); + const double *bounds = stitchingFunc->getBounds(); + const double *encode = stitchingFunc->getEncode(); + int num_funcs = stitchingFunc->getNumFuncs(); + + // Add stops from all the stitched functions + GfxRGB prev_color, color; + svgGetShadingColorRGB(shading, bounds[0], &prev_color); + _addStopToGradient(gradient, bounds[0], &prev_color, 1.0); + for ( int i = 0 ; i < num_funcs ; i++ ) { + svgGetShadingColorRGB(shading, bounds[i + 1], &color); + // Add stops + if (stitchingFunc->getFunc(i)->getType() == 2) { // process exponential fxn + double expE = (static_cast<_POPPLER_CONST ExponentialFunction*>(stitchingFunc->getFunc(i)))->getE(); + if (expE > 1.0) { + expE = (bounds[i + 1] - bounds[i])/expE; // approximate exponential as a single straight line at x=1 + if (encode[2*i] == 0) { // normal sequence + _addStopToGradient(gradient, bounds[i + 1] - expE, &prev_color, 1.0); + } else { // reflected sequence + _addStopToGradient(gradient, bounds[i] + expE, &color, 1.0); + } + } + } + _addStopToGradient(gradient, bounds[i + 1], &color, 1.0); + prev_color = color; + } + } else { // Unsupported function type + return false; + } + + return true; +} + +/** + * \brief Sets _invalidated_style to true to indicate that styles have to be updated + * Used for text output when glyphs are buffered till a font change + */ +void SvgBuilder::updateStyle(GfxState *state) { + if (_in_text_object) { + _invalidated_style = true; + _current_state = state; + } +} + +/* + MatchingChars + Count for how many characters s1 matches sp taking into account + that a space in sp may be removed or replaced by some other tokens + specified in the code. (Bug LP #179589) +*/ +static size_t MatchingChars(std::string s1, std::string sp) +{ + size_t is = 0; + size_t ip = 0; + + while(is < s1.length() && ip < sp.length()) { + if (s1[is] == sp[ip]) { + is++; ip++; + } else if (sp[ip] == ' ') { + ip++; + if (s1[is] == '_') { // Valid matches to spaces in sp. + is++; + } + } else { + break; + } + } + return ip; +} + +/* + SvgBuilder::_BestMatchingFont + Scan the available fonts to find the font name that best matches PDFname. + (Bug LP #179589) +*/ +std::string SvgBuilder::_BestMatchingFont(std::string PDFname) +{ + double bestMatch = 0; + std::string bestFontname = "Arial"; + + for (auto fontname : _availableFontNames) { + // At least the first word of the font name should match. + size_t minMatch = fontname.find(" "); + if (minMatch == std::string::npos) { + minMatch = fontname.length(); + } + + size_t Match = MatchingChars(PDFname, fontname); + if (Match >= minMatch) { + double relMatch = (float)Match / (fontname.length() + PDFname.length()); + if (relMatch > bestMatch) { + bestMatch = relMatch; + bestFontname = fontname; + } + } + } + + if (bestMatch == 0) + return PDFname; + else + return bestFontname; +} + +/** + * This array holds info about translating font weight names to more or less CSS equivalents + */ +static char *font_weight_translator[][2] = { + {(char*) "bold", (char*) "bold"}, + {(char*) "light", (char*) "300"}, + {(char*) "black", (char*) "900"}, + {(char*) "heavy", (char*) "900"}, + {(char*) "ultrabold", (char*) "800"}, + {(char*) "extrabold", (char*) "800"}, + {(char*) "demibold", (char*) "600"}, + {(char*) "semibold", (char*) "600"}, + {(char*) "medium", (char*) "500"}, + {(char*) "book", (char*) "normal"}, + {(char*) "regular", (char*) "normal"}, + {(char*) "roman", (char*) "normal"}, + {(char*) "normal", (char*) "normal"}, + {(char*) "ultralight", (char*) "200"}, + {(char*) "extralight", (char*) "200"}, + {(char*) "thin", (char*) "100"} +}; + +/** + * \brief Updates _font_style according to the font set in parameter state + */ +void SvgBuilder::updateFont(GfxState *state) { + + TRACE(("updateFont()\n")); + _need_font_update = false; + updateTextMatrix(state); // Ensure that we have a text matrix built + + if (_font_style) { + //sp_repr_css_attr_unref(_font_style); + } + _font_style = sp_repr_css_attr_new(); + GfxFont *font = state->getFont(); + // Store original name + if (font->getName()) { + _font_specification = font->getName()->getCString(); + } else { + _font_specification = "Arial"; + } + + // Prune the font name to get the correct font family name + // In a PDF font names can look like this: IONIPB+MetaPlusBold-Italic + char *font_family = nullptr; + char *font_style = nullptr; + char *font_style_lowercase = nullptr; + const char *plus_sign = strstr(_font_specification, "+"); + if (plus_sign) { + font_family = g_strdup(plus_sign + 1); + _font_specification = plus_sign + 1; + } else { + font_family = g_strdup(_font_specification); + } + char *style_delim = nullptr; + if ( ( style_delim = g_strrstr(font_family, "-") ) || + ( style_delim = g_strrstr(font_family, ",") ) ) { + font_style = style_delim + 1; + font_style_lowercase = g_ascii_strdown(font_style, -1); + style_delim[0] = 0; + } + + // Font family + if (font->getFamily()) { // if font family is explicitly given use it. + sp_repr_css_set_property(_font_style, "font-family", font->getFamily()->getCString()); + } else { + int attr_value = 1; + sp_repr_get_int(_preferences, "localFonts", &attr_value); + if (attr_value != 0) { + // Find the font that best matches the stripped down (orig)name (Bug LP #179589). + sp_repr_css_set_property(_font_style, "font-family", _BestMatchingFont(font_family).c_str()); + } else { + sp_repr_css_set_property(_font_style, "font-family", font_family); + } + } + + // Font style + if (font->isItalic()) { + sp_repr_css_set_property(_font_style, "font-style", "italic"); + } else if (font_style) { + if ( strstr(font_style_lowercase, "italic") || + strstr(font_style_lowercase, "slanted") ) { + sp_repr_css_set_property(_font_style, "font-style", "italic"); + } else if (strstr(font_style_lowercase, "oblique")) { + sp_repr_css_set_property(_font_style, "font-style", "oblique"); + } + } + + // Font variant -- default 'normal' value + sp_repr_css_set_property(_font_style, "font-variant", "normal"); + + // Font weight + GfxFont::Weight font_weight = font->getWeight(); + char *css_font_weight = nullptr; + if ( font_weight != GfxFont::WeightNotDefined ) { + if ( font_weight == GfxFont::W400 ) { + css_font_weight = (char*) "normal"; + } else if ( font_weight == GfxFont::W700 ) { + css_font_weight = (char*) "bold"; + } else { + gchar weight_num[4] = "100"; + weight_num[0] = (gchar)( '1' + (font_weight - GfxFont::W100) ); + sp_repr_css_set_property(_font_style, "font-weight", (gchar *)&weight_num); + } + } else if (font_style) { + // Apply the font weight translations + int num_translations = sizeof(font_weight_translator) / ( 2 * sizeof(char *) ); + for ( int i = 0 ; i < num_translations ; i++ ) { + if (strstr(font_style_lowercase, font_weight_translator[i][0])) { + css_font_weight = font_weight_translator[i][1]; + } + } + } else { + css_font_weight = (char*) "normal"; + } + if (css_font_weight) { + sp_repr_css_set_property(_font_style, "font-weight", css_font_weight); + } + g_free(font_family); + if (font_style_lowercase) { + g_free(font_style_lowercase); + } + + // Font stretch + GfxFont::Stretch font_stretch = font->getStretch(); + gchar *stretch_value = nullptr; + switch (font_stretch) { + case GfxFont::UltraCondensed: + stretch_value = (char*) "ultra-condensed"; + break; + case GfxFont::ExtraCondensed: + stretch_value = (char*) "extra-condensed"; + break; + case GfxFont::Condensed: + stretch_value = (char*) "condensed"; + break; + case GfxFont::SemiCondensed: + stretch_value = (char*) "semi-condensed"; + break; + case GfxFont::Normal: + stretch_value = (char*) "normal"; + break; + case GfxFont::SemiExpanded: + stretch_value = (char*) "semi-expanded"; + break; + case GfxFont::Expanded: + stretch_value = (char*) "expanded"; + break; + case GfxFont::ExtraExpanded: + stretch_value = (char*) "extra-expanded"; + break; + case GfxFont::UltraExpanded: + stretch_value = (char*) "ultra-expanded"; + break; + default: + break; + } + if ( stretch_value != nullptr ) { + sp_repr_css_set_property(_font_style, "font-stretch", stretch_value); + } + + // Font size + Inkscape::CSSOStringStream os_font_size; + double css_font_size = _font_scaling * state->getFontSize(); + if ( font->getType() == fontType3 ) { + const double *font_matrix = font->getFontMatrix(); + if ( font_matrix[0] != 0.0 ) { + css_font_size *= font_matrix[3] / font_matrix[0]; + } + } + os_font_size << css_font_size; + sp_repr_css_set_property(_font_style, "font-size", os_font_size.str().c_str()); + + // Writing mode + if ( font->getWMode() == 0 ) { + sp_repr_css_set_property(_font_style, "writing-mode", "lr"); + } else { + sp_repr_css_set_property(_font_style, "writing-mode", "tb"); + } + + _current_font = font; + _invalidated_style = true; +} + +/** + * \brief Shifts the current text position by the given amount (specified in text space) + */ +void SvgBuilder::updateTextShift(GfxState *state, double shift) { + double shift_value = -shift * 0.001 * fabs(state->getFontSize()); + if (state->getFont()->getWMode()) { + _text_position[1] += shift_value; + } else { + _text_position[0] += shift_value; + } +} + +/** + * \brief Updates current text position + */ +void SvgBuilder::updateTextPosition(double tx, double ty) { + Geom::Point new_position(tx, ty); + _text_position = new_position; +} + +/** + * \brief Flushes the buffered characters + */ +void SvgBuilder::updateTextMatrix(GfxState *state) { + _flushText(); + // Update text matrix + const double *text_matrix = state->getTextMat(); + double w_scale = sqrt( text_matrix[0] * text_matrix[0] + text_matrix[2] * text_matrix[2] ); + double h_scale = sqrt( text_matrix[1] * text_matrix[1] + text_matrix[3] * text_matrix[3] ); + double max_scale; + if ( w_scale > h_scale ) { + max_scale = w_scale; + } else { + max_scale = h_scale; + } + // Calculate new text matrix + Geom::Affine new_text_matrix(text_matrix[0] * state->getHorizScaling(), + text_matrix[1] * state->getHorizScaling(), + -text_matrix[2], -text_matrix[3], + 0.0, 0.0); + + if ( fabs( max_scale - 1.0 ) > EPSILON ) { + // Cancel out scaling by font size in text matrix + for ( int i = 0 ; i < 4 ; i++ ) { + new_text_matrix[i] /= max_scale; + } + } + _text_matrix = new_text_matrix; + _font_scaling = max_scale; +} + +/** + * \brief Writes the buffered characters to the SVG document + */ +void SvgBuilder::_flushText() { + // Ignore empty strings + if ( _glyphs.empty()) { + _glyphs.clear(); + return; + } + std::vector<SvgGlyph>::iterator i = _glyphs.begin(); + const SvgGlyph& first_glyph = (*i); + int render_mode = first_glyph.render_mode; + // Ignore invisible characters + if ( render_mode == 3 ) { + _glyphs.clear(); + return; + } + + Inkscape::XML::Node *text_node = _xml_doc->createElement("svg:text"); + // Set text matrix + Geom::Affine text_transform(_text_matrix); + text_transform[4] = first_glyph.position[0]; + text_transform[5] = first_glyph.position[1]; + gchar *transform = sp_svg_transform_write(text_transform); + text_node->setAttribute("transform", transform); + g_free(transform); + + bool new_tspan = true; + bool same_coords[2] = {true, true}; + Geom::Point last_delta_pos; + unsigned int glyphs_in_a_row = 0; + Inkscape::XML::Node *tspan_node = nullptr; + Glib::ustring x_coords; + Glib::ustring y_coords; + Glib::ustring text_buffer; + + // Output all buffered glyphs + while (true) { + const SvgGlyph& glyph = (*i); + std::vector<SvgGlyph>::iterator prev_iterator = i - 1; + // Check if we need to make a new tspan + if (glyph.style_changed) { + new_tspan = true; + } else if ( i != _glyphs.begin() ) { + const SvgGlyph& prev_glyph = (*prev_iterator); + if ( !( ( glyph.dy == 0.0 && prev_glyph.dy == 0.0 && + glyph.text_position[1] == prev_glyph.text_position[1] ) || + ( glyph.dx == 0.0 && prev_glyph.dx == 0.0 && + glyph.text_position[0] == prev_glyph.text_position[0] ) ) ) { + new_tspan = true; + } + } + + // Create tspan node if needed + if ( new_tspan || i == _glyphs.end() ) { + if (tspan_node) { + // Set the x and y coordinate arrays + if ( same_coords[0] ) { + sp_repr_set_svg_double(tspan_node, "x", last_delta_pos[0]); + } else { + tspan_node->setAttributeOrRemoveIfEmpty("x", x_coords); + } + if ( same_coords[1] ) { + sp_repr_set_svg_double(tspan_node, "y", last_delta_pos[1]); + } else { + tspan_node->setAttributeOrRemoveIfEmpty("y", y_coords); + } + TRACE(("tspan content: %s\n", text_buffer.c_str())); + if ( glyphs_in_a_row > 1 ) { + tspan_node->setAttribute("sodipodi:role", "line"); + } + // Add text content node to tspan + Inkscape::XML::Node *text_content = _xml_doc->createTextNode(text_buffer.c_str()); + tspan_node->appendChild(text_content); + Inkscape::GC::release(text_content); + text_node->appendChild(tspan_node); + // Clear temporary buffers + x_coords.clear(); + y_coords.clear(); + text_buffer.clear(); + Inkscape::GC::release(tspan_node); + glyphs_in_a_row = 0; + } + if ( i == _glyphs.end() ) { + sp_repr_css_attr_unref((*prev_iterator).style); + break; + } else { + tspan_node = _xml_doc->createElement("svg:tspan"); + + /////// + // Create a font specification string and save the attribute in the style + PangoFontDescription *descr = pango_font_description_from_string(glyph.font_specification); + Glib::ustring properFontSpec = font_factory::Default()->ConstructFontSpecification(descr); + pango_font_description_free(descr); + sp_repr_css_set_property(glyph.style, "-inkscape-font-specification", properFontSpec.c_str()); + + // Set style and unref SPCSSAttr if it won't be needed anymore + // assume all <tspan> nodes in a <text> node share the same style + sp_repr_css_change(text_node, glyph.style, "style"); + if ( glyph.style_changed && i != _glyphs.begin() ) { // Free previous style + sp_repr_css_attr_unref((*prev_iterator).style); + } + } + new_tspan = false; + } + if ( glyphs_in_a_row > 0 ) { + x_coords.append(" "); + y_coords.append(" "); + // Check if we have the same coordinates + const SvgGlyph& prev_glyph = (*prev_iterator); + for ( int p = 0 ; p < 2 ; p++ ) { + if ( glyph.text_position[p] != prev_glyph.text_position[p] ) { + same_coords[p] = false; + } + } + } + // Append the coordinates to their respective strings + Geom::Point delta_pos( glyph.text_position - first_glyph.text_position ); + delta_pos[1] += glyph.rise; + delta_pos[1] *= -1.0; // flip it + delta_pos *= _font_scaling; + Inkscape::CSSOStringStream os_x; + os_x << delta_pos[0]; + x_coords.append(os_x.str()); + Inkscape::CSSOStringStream os_y; + os_y << delta_pos[1]; + y_coords.append(os_y.str()); + last_delta_pos = delta_pos; + + // Append the character to the text buffer + if ( !glyph.code.empty() ) { + text_buffer.append(1, glyph.code[0]); + } + + glyphs_in_a_row++; + ++i; + } + _container->appendChild(text_node); + Inkscape::GC::release(text_node); + + _glyphs.clear(); +} + +void SvgBuilder::beginString(GfxState *state) { + if (_need_font_update) { + updateFont(state); + } + IFTRACE(double *m = state->getTextMat()); + TRACE(("tm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5])); + IFTRACE(m = state->getCTM()); + TRACE(("ctm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5])); +} + +/** + * \brief Adds the specified character to the text buffer + * Takes care of converting it to UTF-8 and generates a new style repr if style + * has changed since the last call. + */ +void SvgBuilder::addChar(GfxState *state, double x, double y, + double dx, double dy, + double originX, double originY, + CharCode /*code*/, int /*nBytes*/, Unicode const *u, int uLen) { + + + bool is_space = ( uLen == 1 && u[0] == 32 ); + // Skip beginning space + if ( is_space && _glyphs.empty()) { + Geom::Point delta(dx, dy); + _text_position += delta; + return; + } + // Allow only one space in a row + if ( is_space && (_glyphs[_glyphs.size() - 1].code.size() == 1) && + (_glyphs[_glyphs.size() - 1].code[0] == 32) ) { + Geom::Point delta(dx, dy); + _text_position += delta; + return; + } + + SvgGlyph new_glyph; + new_glyph.is_space = is_space; + new_glyph.position = Geom::Point( x - originX, y - originY ); + new_glyph.text_position = _text_position; + new_glyph.dx = dx; + new_glyph.dy = dy; + Geom::Point delta(dx, dy); + _text_position += delta; + + // Convert the character to UTF-8 since that's our SVG document's encoding + { + gunichar2 uu[8] = {0}; + + for (int i = 0; i < uLen; i++) { + uu[i] = u[i]; + } + + gchar *tmp = g_utf16_to_utf8(uu, uLen, nullptr, nullptr, nullptr); + if ( tmp && *tmp ) { + new_glyph.code = tmp; + } else { + new_glyph.code.clear(); + } + g_free(tmp); + } + + // Copy current style if it has changed since the previous glyph + if (_invalidated_style || _glyphs.empty()) { + new_glyph.style_changed = true; + int render_mode = state->getRender(); + // Set style + bool has_fill = !( render_mode & 1 ); + bool has_stroke = ( render_mode & 3 ) == 1 || ( render_mode & 3 ) == 2; + new_glyph.style = _setStyle(state, has_fill, has_stroke); + // Find a way to handle blend modes on text + /* GfxBlendMode blendmode = state->getBlendMode(); + if (blendmode) { + sp_repr_css_set_property(new_glyph.style, "mix-blend-mode", enum_blend_mode[blendmode].key); + } */ + new_glyph.render_mode = render_mode; + sp_repr_css_merge(new_glyph.style, _font_style); // Merge with font style + _invalidated_style = false; + } else { + new_glyph.style_changed = false; + // Point to previous glyph's style information + const SvgGlyph& prev_glyph = _glyphs.back(); + new_glyph.style = prev_glyph.style; + /* GfxBlendMode blendmode = state->getBlendMode(); + if (blendmode) { + sp_repr_css_set_property(new_glyph.style, "mix-blend-mode", enum_blend_mode[blendmode].key); + } */ + new_glyph.render_mode = prev_glyph.render_mode; + } + new_glyph.font_specification = _font_specification; + new_glyph.rise = state->getRise(); + + _glyphs.push_back(new_glyph); +} + +void SvgBuilder::endString(GfxState * /*state*/) { +} + +void SvgBuilder::beginTextObject(GfxState *state) { + _in_text_object = true; + _invalidated_style = true; // Force copying of current state + _current_state = state; +} + +void SvgBuilder::endTextObject(GfxState * /*state*/) { + _flushText(); + // TODO: clip if render_mode >= 4 + _in_text_object = false; +} + +/** + * Helper functions for supporting direct PNG output into a base64 encoded stream + */ +void png_write_vector(png_structp png_ptr, png_bytep data, png_size_t length) +{ + auto *v_ptr = reinterpret_cast<std::vector<guchar> *>(png_get_io_ptr(png_ptr)); // Get pointer to stream + for ( unsigned i = 0 ; i < length ; i++ ) { + v_ptr->push_back(data[i]); + } +} + +/** + * \brief Creates an <image> element containing the given ImageStream as a PNG + * + */ +Inkscape::XML::Node *SvgBuilder::_createImage(Stream *str, int width, int height, + GfxImageColorMap *color_map, bool interpolate, + int *mask_colors, bool alpha_only, + bool invert_alpha) { + + // Create PNG write struct + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if ( png_ptr == nullptr ) { + return nullptr; + } + // Create PNG info struct + png_infop info_ptr = png_create_info_struct(png_ptr); + if ( info_ptr == nullptr ) { + png_destroy_write_struct(&png_ptr, nullptr); + return nullptr; + } + // Set error handler + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + return nullptr; + } + // Decide whether we should embed this image + int attr_value = 1; + sp_repr_get_int(_preferences, "embedImages", &attr_value); + bool embed_image = ( attr_value != 0 ); + // Set read/write functions + std::vector<guchar> png_buffer; + FILE *fp = nullptr; + gchar *file_name = nullptr; + if (embed_image) { + png_set_write_fn(png_ptr, &png_buffer, png_write_vector, nullptr); + } else { + static int counter = 0; + file_name = g_strdup_printf("%s_img%d.png", _docname, counter++); + fp = fopen(file_name, "wb"); + if ( fp == nullptr ) { + png_destroy_write_struct(&png_ptr, &info_ptr); + g_free(file_name); + return nullptr; + } + png_init_io(png_ptr, fp); + } + + // Set header data + if ( !invert_alpha && !alpha_only ) { + png_set_invert_alpha(png_ptr); + } + png_color_8 sig_bit; + if (alpha_only) { + png_set_IHDR(png_ptr, info_ptr, + width, + height, + 8, /* bit_depth */ + PNG_COLOR_TYPE_GRAY, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + sig_bit.red = 0; + sig_bit.green = 0; + sig_bit.blue = 0; + sig_bit.gray = 8; + sig_bit.alpha = 0; + } else { + png_set_IHDR(png_ptr, info_ptr, + width, + height, + 8, /* bit_depth */ + PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + sig_bit.red = 8; + sig_bit.green = 8; + sig_bit.blue = 8; + sig_bit.alpha = 8; + } + png_set_sBIT(png_ptr, info_ptr, &sig_bit); + png_set_bgr(png_ptr); + // Write the file header + png_write_info(png_ptr, info_ptr); + + // Convert pixels + ImageStream *image_stream; + if (alpha_only) { + if (color_map) { + image_stream = new ImageStream(str, width, color_map->getNumPixelComps(), + color_map->getBits()); + } else { + image_stream = new ImageStream(str, width, 1, 1); + } + image_stream->reset(); + + // Convert grayscale values + unsigned char *buffer = new unsigned char[width]; + int invert_bit = invert_alpha ? 1 : 0; + for ( int y = 0 ; y < height ; y++ ) { + unsigned char *row = image_stream->getLine(); + if (color_map) { + color_map->getGrayLine(row, buffer, width); + } else { + unsigned char *buf_ptr = buffer; + for ( int x = 0 ; x < width ; x++ ) { + if ( row[x] ^ invert_bit ) { + *buf_ptr++ = 0; + } else { + *buf_ptr++ = 255; + } + } + } + png_write_row(png_ptr, (png_bytep)buffer); + } + delete [] buffer; + } else if (color_map) { + image_stream = new ImageStream(str, width, + color_map->getNumPixelComps(), + color_map->getBits()); + image_stream->reset(); + + // Convert RGB values + unsigned int *buffer = new unsigned int[width]; + if (mask_colors) { + for ( int y = 0 ; y < height ; y++ ) { + unsigned char *row = image_stream->getLine(); + color_map->getRGBLine(row, buffer, width); + + unsigned int *dest = buffer; + for ( int x = 0 ; x < width ; x++ ) { + // Check each color component against the mask + for ( int i = 0; i < color_map->getNumPixelComps() ; i++) { + if ( row[i] < mask_colors[2*i] * 255 || + row[i] > mask_colors[2*i + 1] * 255 ) { + *dest = *dest | 0xff000000; + break; + } + } + // Advance to the next pixel + row += color_map->getNumPixelComps(); + dest++; + } + // Write it to the PNG + png_write_row(png_ptr, (png_bytep)buffer); + } + } else { + for ( int i = 0 ; i < height ; i++ ) { + unsigned char *row = image_stream->getLine(); + memset((void*)buffer, 0xff, sizeof(int) * width); + color_map->getRGBLine(row, buffer, width); + png_write_row(png_ptr, (png_bytep)buffer); + } + } + delete [] buffer; + + } else { // A colormap must be provided, so quit + png_destroy_write_struct(&png_ptr, &info_ptr); + if (!embed_image) { + fclose(fp); + g_free(file_name); + } + return nullptr; + } + delete image_stream; + str->close(); + // Close PNG + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + // Create repr + Inkscape::XML::Node *image_node = _xml_doc->createElement("svg:image"); + sp_repr_set_svg_double(image_node, "width", 1); + sp_repr_set_svg_double(image_node, "height", 1); + if( !interpolate ) { + SPCSSAttr *css = sp_repr_css_attr_new(); + // This should be changed after CSS4 Images widely supported. + sp_repr_css_set_property(css, "image-rendering", "optimizeSpeed"); + sp_repr_css_change(image_node, css, "style"); + sp_repr_css_attr_unref(css); + } + + // PS/PDF images are placed via a transformation matrix, no preserveAspectRatio used + image_node->setAttribute("preserveAspectRatio", "none"); + + // Set transformation + + svgSetTransform(image_node, 1.0, 0.0, 0.0, -1.0, 0.0, 1.0); + + // Create href + if (embed_image) { + // Append format specification to the URI + auto *base64String = g_base64_encode(png_buffer.data(), png_buffer.size()); + auto png_data = std::string("data:image/png;base64,") + base64String; + g_free(base64String); + image_node->setAttributeOrRemoveIfEmpty("xlink:href", png_data); + } else { + fclose(fp); + image_node->setAttribute("xlink:href", file_name); + g_free(file_name); + } + + return image_node; +} + +/** + * \brief Creates a <mask> with the specified width and height and adds to <defs> + * If we're not the top-level SvgBuilder, creates a <defs> too and adds the mask to it. + * \return the created XML node + */ +Inkscape::XML::Node *SvgBuilder::_createMask(double width, double height) { + Inkscape::XML::Node *mask_node = _xml_doc->createElement("svg:mask"); + mask_node->setAttribute("maskUnits", "userSpaceOnUse"); + sp_repr_set_svg_double(mask_node, "x", 0.0); + sp_repr_set_svg_double(mask_node, "y", 0.0); + sp_repr_set_svg_double(mask_node, "width", width); + sp_repr_set_svg_double(mask_node, "height", height); + // Append mask to defs + if (_is_top_level) { + _doc->getDefs()->getRepr()->appendChild(mask_node); + Inkscape::GC::release(mask_node); + return _doc->getDefs()->getRepr()->lastChild(); + } else { // Work around for renderer bug when mask isn't defined in pattern + static int mask_count = 0; + Inkscape::XML::Node *defs = _root->firstChild(); + if ( !( defs && !strcmp(defs->name(), "svg:defs") ) ) { + // Create <defs> node + defs = _xml_doc->createElement("svg:defs"); + _root->addChild(defs, nullptr); + Inkscape::GC::release(defs); + defs = _root->firstChild(); + } + gchar *mask_id = g_strdup_printf("_mask%d", mask_count++); + mask_node->setAttribute("id", mask_id); + g_free(mask_id); + defs->appendChild(mask_node); + Inkscape::GC::release(mask_node); + return defs->lastChild(); + } +} + +void SvgBuilder::addImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map, + bool interpolate, int *mask_colors) +{ + + Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, mask_colors); + if (image_node) { + _setBlendMode(image_node, state); + _container->appendChild(image_node); + Inkscape::GC::release(image_node); + } +} + +void SvgBuilder::addImageMask(GfxState *state, Stream *str, int width, int height, + bool invert, bool interpolate) { + + // Create a rectangle + Inkscape::XML::Node *rect = _xml_doc->createElement("svg:rect"); + sp_repr_set_svg_double(rect, "x", 0.0); + sp_repr_set_svg_double(rect, "y", 0.0); + sp_repr_set_svg_double(rect, "width", 1.0); + sp_repr_set_svg_double(rect, "height", 1.0); + svgSetTransform(rect, 1.0, 0.0, 0.0, -1.0, 0.0, 1.0); + // Get current fill style and set it on the rectangle + SPCSSAttr *css = sp_repr_css_attr_new(); + _setFillStyle(css, state, false); + sp_repr_css_change(rect, css, "style"); + sp_repr_css_attr_unref(css); + _setBlendMode(rect, state); + + // Scaling 1x1 surfaces might not work so skip setting a mask with this size + if ( width > 1 || height > 1 ) { + Inkscape::XML::Node *mask_image_node = + _createImage(str, width, height, nullptr, interpolate, nullptr, true, invert); + if (mask_image_node) { + // Create the mask + Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0); + // Remove unnecessary transformation from the mask image + mask_image_node->removeAttribute("transform"); + mask_node->appendChild(mask_image_node); + Inkscape::GC::release(mask_image_node); + gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id")); + rect->setAttribute("mask", mask_url); + g_free(mask_url); + } + } + + // Add the rectangle to the container + _container->appendChild(rect); + Inkscape::GC::release(rect); +} + +void SvgBuilder::addMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map, + bool interpolate, Stream *mask_str, int mask_width, int mask_height, bool invert_mask, + bool mask_interpolate) +{ + + Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height, + nullptr, mask_interpolate, nullptr, true, invert_mask); + Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr); + if ( mask_image_node && image_node ) { + // Create mask for the image + Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0); + // Remove unnecessary transformation from the mask image + mask_image_node->removeAttribute("transform"); + mask_node->appendChild(mask_image_node); + // Scale the mask to the size of the image + Geom::Affine mask_transform((double)width, 0.0, 0.0, (double)height, 0.0, 0.0); + gchar *transform_text = sp_svg_transform_write(mask_transform); + mask_node->setAttribute("maskTransform", transform_text); + g_free(transform_text); + // Set mask and add image + gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id")); + image_node->setAttribute("mask", mask_url); + g_free(mask_url); + _container->appendChild(image_node); + } + if (mask_image_node) { + Inkscape::GC::release(mask_image_node); + } + if (image_node) { + _setBlendMode(image_node, state); + Inkscape::GC::release(image_node); + } +} + +void SvgBuilder::addSoftMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map, + bool interpolate, Stream *mask_str, int mask_width, int mask_height, + GfxImageColorMap *mask_color_map, bool mask_interpolate) +{ + + Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height, + mask_color_map, mask_interpolate, nullptr, true); + Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr); + if ( mask_image_node && image_node ) { + // Create mask for the image + Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0); + // Remove unnecessary transformation from the mask image + mask_image_node->removeAttribute("transform"); + mask_node->appendChild(mask_image_node); + // Set mask and add image + gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id")); + image_node->setAttribute("mask", mask_url); + g_free(mask_url); + _container->appendChild(image_node); + } + if (mask_image_node) { + Inkscape::GC::release(mask_image_node); + } + if (image_node) { + _setBlendMode(image_node, state); + Inkscape::GC::release(image_node); + } +} + +/** + * \brief Starts building a new transparency group + */ +void SvgBuilder::pushTransparencyGroup(GfxState * /*state*/, double *bbox, + GfxColorSpace * /*blending_color_space*/, + bool isolated, bool knockout, + bool for_softmask) { + + // Push node stack + pushNode("svg:g"); + + // Setup new transparency group + SvgTransparencyGroup *transpGroup = new SvgTransparencyGroup; + for (size_t i = 0; i < 4; i++) { + transpGroup->bbox[i] = bbox[i]; + } + transpGroup->isolated = isolated; + transpGroup->knockout = knockout; + transpGroup->for_softmask = for_softmask; + transpGroup->container = _container; + + // Push onto the stack + transpGroup->next = _transp_group_stack; + _transp_group_stack = transpGroup; +} + +void SvgBuilder::popTransparencyGroup(GfxState * /*state*/) { + // Restore node stack + popNode(); +} + +/** + * \brief Places the current transparency group into the current container + */ +void SvgBuilder::paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/) { + SvgTransparencyGroup *transpGroup = _transp_group_stack; + _container->appendChild(transpGroup->container); + Inkscape::GC::release(transpGroup->container); + // Pop the stack + _transp_group_stack = transpGroup->next; + delete transpGroup; +} + +/** + * \brief Creates a mask using the current transparency group as its content + */ +void SvgBuilder::setSoftMask(GfxState * /*state*/, double * /*bbox*/, bool /*alpha*/, + Function * /*transfer_func*/, GfxColor * /*backdrop_color*/) { + + // Create mask + Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0); + // Add the softmask content to it + SvgTransparencyGroup *transpGroup = _transp_group_stack; + mask_node->appendChild(transpGroup->container); + Inkscape::GC::release(transpGroup->container); + // Apply the mask + _state_stack.back().softmask = mask_node; + pushGroup(); + gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id")); + _container->setAttribute("mask", mask_url); + g_free(mask_url); + // Pop the stack + _transp_group_stack = transpGroup->next; + delete transpGroup; +} + +void SvgBuilder::clearSoftMask(GfxState * /*state*/) { + if (_state_stack.back().softmask) { + _state_stack.back().softmask = nullptr; + popGroup(); + } +} + +} } } /* namespace Inkscape, Extension, Internal */ + +#endif /* HAVE_POPPLER */ + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/pdfinput/svg-builder.h b/src/extension/internal/pdfinput/svg-builder.h new file mode 100644 index 0000000..050465d --- /dev/null +++ b/src/extension/internal/pdfinput/svg-builder.h @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H +#define SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H + +/* + * Authors: + * miklos erdelyi + * + * Copyright (C) 2007 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 HAVE_POPPLER +#include "poppler-transition-api.h" + +class SPDocument; +namespace Inkscape { + namespace XML { + struct Document; + class Node; + } +} + +#include <2geom/point.h> +#include <2geom/affine.h> +#include <glibmm/ustring.h> + +#include "CharTypes.h" +class Function; +class GfxState; +struct GfxColor; +class GfxColorSpace; +struct GfxRGB; +class GfxPath; +class GfxPattern; +class GfxTilingPattern; +class GfxShading; +class GfxFont; +class GfxImageColorMap; +class Stream; +class XRef; + +class SPCSSAttr; + +#include <vector> +#include <glib.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { + +struct SvgTransparencyGroup; + +/** + * Holds information about the current softmask and group depth for use of libpoppler. + * Could be later used to store other graphics state parameters so that we could + * emit only the differences in style settings from the parent state. + */ +struct SvgGraphicsState { + Inkscape::XML::Node *softmask; // Points to current softmask node + int group_depth; // Depth of nesting groups at this level +}; + +/** + * Holds information about glyphs added by PdfParser which haven't been added + * to the document yet. + */ +struct SvgGlyph { + Geom::Point position; // Absolute glyph coords + Geom::Point text_position; // Absolute glyph coords in text space + double dx; // X advance value + double dy; // Y advance value + double rise; // Text rise parameter + Glib::ustring code; // UTF-8 coded character + bool is_space; + + bool style_changed; // Set to true if style has to be reset + SPCSSAttr *style; + int render_mode; // Text render mode + const char *font_specification; // Pointer to current font specification +}; + +/** + * Builds the inner SVG representation using libpoppler from the calls of PdfParser. + */ +class SvgBuilder { +public: + SvgBuilder(SPDocument *document, gchar *docname, XRef *xref); + SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root); + virtual ~SvgBuilder(); + + // Property setting + void setDocumentSize(double width, double height); // Document size in px + void setAsLayer(char *layer_name=nullptr); + void setGroupOpacity(double opacity); + Inkscape::XML::Node *getPreferences() { + return _preferences; + } + + // Handling the node stack + Inkscape::XML::Node *pushGroup(); + Inkscape::XML::Node *popGroup(); + Inkscape::XML::Node *getContainer(); // Returns current group node + + // Path adding + void addPath(GfxState *state, bool fill, bool stroke, bool even_odd=false); + void addShadedFill(GfxShading *shading, double *matrix, GfxPath *path, bool even_odd=false); + + // Image handling + void addImage(GfxState *state, Stream *str, int width, int height, + GfxImageColorMap *color_map, bool interpolate, int *mask_colors); + void addImageMask(GfxState *state, Stream *str, int width, int height, + bool invert, bool interpolate); + void addMaskedImage(GfxState *state, Stream *str, int width, int height, + GfxImageColorMap *color_map, bool interpolate, + Stream *mask_str, int mask_width, int mask_height, + bool invert_mask, bool mask_interpolate); + void addSoftMaskedImage(GfxState *state, Stream *str, int width, int height, + GfxImageColorMap *color_map, bool interpolate, + Stream *mask_str, int mask_width, int mask_height, + GfxImageColorMap *mask_color_map, bool mask_interpolate); + + // Transparency group and soft mask handling + void pushTransparencyGroup(GfxState *state, double *bbox, + GfxColorSpace *blending_color_space, + bool isolated, bool knockout, + bool for_softmask); + void popTransparencyGroup(GfxState *state); + void paintTransparencyGroup(GfxState *state, double *bbox); + void setSoftMask(GfxState *state, double *bbox, bool alpha, + Function *transfer_func, GfxColor *backdrop_color); + void clearSoftMask(GfxState *state); + + // Text handling + void beginString(GfxState *state); + void endString(GfxState *state); + void addChar(GfxState *state, double x, double y, + double dx, double dy, + double originX, double originY, + CharCode code, int nBytes, Unicode const *u, int uLen); + void beginTextObject(GfxState *state); + void endTextObject(GfxState *state); + + bool isPatternTypeSupported(GfxPattern *pattern); + + // State manipulation + void saveState(); + void restoreState(); + void updateStyle(GfxState *state); + void updateFont(GfxState *state); + void updateTextPosition(double tx, double ty); + void updateTextShift(GfxState *state, double shift); + void updateTextMatrix(GfxState *state); + + // Clipping + void clip(GfxState *state, bool even_odd=false); + void setClipPath(GfxState *state, bool even_odd=false); + + // Transforming + void setTransform(double c0, double c1, double c2, double c3, double c4, + double c5); + void setTransform(double const *transform); + bool getTransform(double *transform); + +private: + void _init(); + + // Pattern creation + gchar *_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke=false); + gchar *_createGradient(GfxShading *shading, double *matrix, bool for_shading=false); + void _addStopToGradient(Inkscape::XML::Node *gradient, double offset, + GfxRGB *color, double opacity); + bool _addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading, + _POPPLER_CONST Function *func); + gchar *_createTilingPattern(GfxTilingPattern *tiling_pattern, GfxState *state, + bool is_stroke=false); + // Image/mask creation + Inkscape::XML::Node *_createImage(Stream *str, int width, int height, + GfxImageColorMap *color_map, bool interpolate, + int *mask_colors, bool alpha_only=false, + bool invert_alpha=false); + Inkscape::XML::Node *_createMask(double width, double height); + // Style setting + SPCSSAttr *_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd=false); + void _setStrokeStyle(SPCSSAttr *css, GfxState *state); + void _setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd); + void _setBlendMode(Inkscape::XML::Node *node, GfxState *state); + void _flushText(); // Write buffered text into doc + + std::string _BestMatchingFont(std::string PDFname); + + // Handling of node stack + Inkscape::XML::Node *pushNode(const char* name); + Inkscape::XML::Node *popNode(); + std::vector<Inkscape::XML::Node *> _node_stack; + std::vector<int> _group_depth; // Depth of nesting groups + SvgTransparencyGroup *_transp_group_stack; // Transparency group stack + std::vector<SvgGraphicsState> _state_stack; + + SPCSSAttr *_font_style; // Current font style + GfxFont *_current_font; + const char *_font_specification; + double _font_scaling; + bool _need_font_update; + Geom::Affine _text_matrix; + Geom::Point _text_position; + std::vector<SvgGlyph> _glyphs; // Added characters + bool _in_text_object; // Whether we are inside a text object + bool _invalidated_style; + GfxState *_current_state; + std::vector<std::string> _availableFontNames; // Full names, used for matching font names (Bug LP #179589). + + bool _is_top_level; // Whether this SvgBuilder is the top-level one + SPDocument *_doc; + gchar *_docname; // Basename of the URI from which this document is created + XRef *_xref; // Cross-reference table from the PDF doc we're converting from + Inkscape::XML::Document *_xml_doc; + Inkscape::XML::Node *_root; // Root node from the point of view of this SvgBuilder + Inkscape::XML::Node *_container; // Current container (group/pattern/mask) + Inkscape::XML::Node *_preferences; // Preferences container node + double _width; // Document size in px + double _height; // Document size in px + double _ttm[6]; ///< temporary transform matrix + bool _ttm_is_set; +}; + + +} // namespace Internal +} // namespace Extension +} // namespace Inkscape + +#endif // HAVE_POPPLER + +#endif // SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/polyfill/README.md b/src/extension/internal/polyfill/README.md new file mode 100644 index 0000000..2677a50 --- /dev/null +++ b/src/extension/internal/polyfill/README.md @@ -0,0 +1,19 @@ +# JavaScript polyfills + +This directory contains JavaScript "Polyfills" to support rendering of SVG 2 +features that are not well supported by browsers, but appeared in the 2016 +[specification](https://www.w3.org/TR/2016/CR-SVG2-20160915/pservers.html#MeshGradients) + +The included files are: + - `mesh.js` mesh gradients supporting bicubic meshes and mesh on strokes. + - `mesh_compressed.include` mesh.js minified and wrapped as a C++11 raw string literal. + - `hatch.js` hatch paint server supporting linear and absolute paths hatches + (relative paths are not fully supported) + - `hatch_tests` folder with tests used for `hatch.js` rendering + +## Details +The coding standard used is [semistandard](https://github.com/Flet/semistandard), +a more permissive (allows endrow semicolons) over the famous, open-source +[standardjs](https://standardjs.com/). + +The minifier used for the compressed version is [JavaScript minifier](https://javascript-minifier.com/). diff --git a/src/extension/internal/polyfill/hatch.js b/src/extension/internal/polyfill/hatch.js new file mode 100644 index 0000000..db3d88c --- /dev/null +++ b/src/extension/internal/polyfill/hatch.js @@ -0,0 +1,400 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Use patterns to render a hatch paint server via this polyfill + *//* + * Copyright (C) 2019 Valentin Ionita + * Distributed under GNU General Public License version 2 or later. See <http://fsf.org/>. + */ + +(function () { + // Name spaces ----------------------------------- + const svgNS = 'http://www.w3.org/2000/svg'; + const xlinkNS = 'http://www.w3.org/1999/xlink'; + const unitObjectBoundingBox = 'objectBoundingBox'; + const unitUserSpace = 'userSpaceOnUse'; + + // Set multiple attributes to an element + const setAttributes = (el, attrs) => { + for (let key in attrs) { + el.setAttribute(key, attrs[key]); + } + }; + + // Copy attributes from the hatch with 'id' to the current element + const setReference = (el, id) => { + const attr = [ + 'x', 'y', 'pitch', 'rotate', + 'hatchUnits', 'hatchContentUnits', 'transform' + ]; + const template = document.getElementById(id.slice(1)); + + if (template && template.nodeName === 'hatch') { + attr.forEach(a => { + let t = template.getAttribute(a); + if (el.getAttribute(a) === null && t !== null) { + el.setAttribute(a, t); + } + }); + + if (el.children.length === 0) { + Array.from(template.children).forEach(c => { + el.appendChild(c.cloneNode(true)); + }); + } + } + }; + + // Order pain-order of hatchpaths relative to their pitch + const orderHatchPaths = (paths) => { + const nodeArray = []; + paths.forEach(p => nodeArray.push(p)); + + return nodeArray.sort((a, b) => + // (pitch - a.offset) - (pitch - b.offset) + Number(b.getAttribute('offset')) - Number(a.getAttribute('offset')) + ); + }; + + // Generate x-axis coordinates for the pattern paths + const generatePositions = (width, diagonal, initial, distance) => { + const offset = (diagonal - width) / 2; + const leftDistance = initial + offset; + const rightDistance = width + offset + distance; + const units = Math.round(leftDistance / distance) + 1; + let array = []; + + for (let i = initial - units * distance; i < rightDistance; i += distance) { + array.push(i); + } + + return array; + }; + + // Turn a path array into a tokenized version of it + const parsePath = (data) => { + let array = []; + let i = 0; + let len = data.length; + let last = 0; + + /* + * Last state (last) index map + * 0 => () + * 1 => (x y) + * 2 => (x) + * 3 => (y) + * 4 => (x1 y1 x2 y2 x y) + * 5 => (x2 y2 x y) + * 6 => (_ _ _ _ _ x y) + * 7 => (_) + */ + + while (i < len) { + switch (data[i].toUpperCase()) { + case 'Z': + array.push(data[i]); + i += 1; + last = 0; + break; + case 'M': + case 'L': + case 'T': + array.push(data[i], new Point(Number(data[i + 1]), Number(data[i + 2]))); + i += 3; + last = 1; + break; + case 'H': + array.push(data[i], new Point(Number(data[i + 1]), null)); + i += 2; + last = 2; + break; + case 'V': + array.push(data[i], new Point(null, Number(data[i + 1]))); + i += 2; + last = 3; + break; + case 'C': + array.push( + data[i], new Point(Number(data[i + 1]), Number(data[i + 2])), + new Point(Number(data[i + 3]), Number(data[i + 4])), + new Point(Number(data[i + 5]), Number(data[i + 6])) + ); + i += 7; + last = 4; + break; + case 'S': + case 'Q': + array.push( + data[i], new Point(Number(data[i + 1]), Number(data[i + 2])), + new Point(Number(data[i + 3]), Number(data[i + 4])) + ); + i += 5; + last = 5; + break; + case 'A': + array.push( + data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4], + data[i + 5], new Point(Number(data[i + 6]), Number(data[i + 7])) + ); + i += 8; + last = 6; + break; + case 'B': + array.push(data[i], data[i + 1]); + i += 2; + last = 7; + break; + default: + switch (last) { + case 1: + array.push(new Point(Number(data[i]), Number(data[i + 1]))); + i += 2; + break; + case 2: + array.push(new Point(Number(data[i]), null)); + i += 1; + break; + case 3: + array.push(new Point(null, Number(data[i]))); + i += 1; + break; + case 4: + array.push( + new Point(Number(data[i]), Number(data[i + 1])), + new Point(Number(data[i + 2]), Number(data[i + 3])), + new Point(Number(data[i + 4]), Number(data[i + 5])) + ); + i += 6; + break; + case 5: + array.push( + new Point(Number(data[i]), Number(data[i + 1])), + new Point(Number(data[i + 2]), Number(data[i + 3])) + ); + i += 4; + break; + case 6: + array.push( + data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4], + new Point(Number(data[i + 5]), Number(data[i + 6])) + ); + i += 7; + break; + default: + array.push(data[i]); + i += 1; + } + } + } + + return array; + }; + + const getYDistance = (hatchpath) => { + const path = document.createElementNS(svgNS, 'path'); + let d = hatchpath.getAttribute('d'); + + if (d[0].toUpperCase() !== 'M') { + d = `M 0,0 ${d}`; + } + + path.setAttribute('d', d); + + return path.getPointAtLength(path.getTotalLength()).y - + path.getPointAtLength(0).y; + }; + + // Point class -------------------------------------- + class Point { + constructor (x, y) { + this.x = x; + this.y = y; + } + + toString () { + return `${this.x} ${this.y}`; + } + + isPoint () { + return true; + } + + clone () { + return new Point(this.x, this.y); + } + + add (v) { + return new Point(this.x + v.x, this.y + v.y); + } + + distSquared (v) { + let x = this.x - v.x; + let y = this.y - v.y; + return (x * x + y * y); + } + } + + // Start of document processing --------------------- + const shapes = document.querySelectorAll('rect,circle,ellipse,path,text'); + + shapes.forEach((shape, i) => { + // Get id. If no id, create one. + let shapeId = shape.getAttribute('id'); + if (!shapeId) { + shapeId = 'hatch_shape_' + i; + shape.setAttribute('id', shapeId); + } + + const fill = shape.getAttribute('fill') || shape.style.fill; + const fillURL = fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/); + + if (fillURL && fillURL[1]) { + const hatch = document.getElementById(fillURL[1]); + + if (hatch && hatch.nodeName === 'hatch') { + const href = hatch.getAttributeNS(xlinkNS, 'href'); + + if (href !== null && href !== '') { + setReference(hatch, href); + } + + // Degenerate hatch, with no hatchpath children + if (hatch.children.length === 0) { + return; + } + + const bbox = shape.getBBox(); + const hatchDiag = Math.ceil(Math.sqrt( + bbox.width * bbox.width + bbox.height * bbox.height + )); + + // Hatch variables + const units = hatch.getAttribute('hatchUnits') || unitObjectBoundingBox; + const contentUnits = hatch.getAttribute('hatchContentUnits') || unitUserSpace; + const rotate = Number(hatch.getAttribute('rotate')) || 0; + const transform = hatch.getAttribute('transform') || + hatch.getAttribute('hatchTransform') || ''; + const hatchpaths = orderHatchPaths(hatch.querySelectorAll('hatchpath,hatchPath')); + const x = units === unitObjectBoundingBox + ? (Number(hatch.getAttribute('x')) * bbox.width) || 0 + : Number(hatch.getAttribute('x')) || 0; + const y = units === unitObjectBoundingBox + ? (Number(hatch.getAttribute('y')) * bbox.width) || 0 + : Number(hatch.getAttribute('y')) || 0; + let pitch = units === unitObjectBoundingBox + ? (Number(hatch.getAttribute('pitch')) * bbox.width) || 0 + : Number(hatch.getAttribute('pitch')) || 0; + + if (contentUnits === unitObjectBoundingBox && bbox.height) { + pitch /= bbox.height; + } + + // A negative value is an error. + // A value of zero disables rendering of the element + if (pitch <= 0) { + console.error('Non-positive pitch'); + return; + } + + // Pattern variables + const pattern = document.createElementNS(svgNS, 'pattern'); + const patternId = `${fillURL[1]}_pattern`; + let patternWidth = bbox.width - bbox.width % pitch; + let patternHeight = 0; + + const xPositions = generatePositions(patternWidth, hatchDiag, x, pitch); + + hatchpaths.forEach(hatchpath => { + let offset = Number(hatchpath.getAttribute('offset')) || 0; + offset = offset > pitch ? (offset % pitch) : offset; + const currentXPositions = xPositions.map(p => p + offset); + + const path = document.createElementNS(svgNS, 'path'); + let d = ''; + + for (let j = 0; j < hatchpath.attributes.length; ++j) { + const attr = hatchpath.attributes.item(j); + if (attr.name !== 'd') { + path.setAttribute(attr.name, attr.value); + } + } + + if (hatchpath.getAttribute('d') === null) { + d += currentXPositions.reduce( + (acc, xPos) => `${acc}M ${xPos} ${y} V ${hatchDiag} `, '' + ); + patternHeight = hatchDiag; + } else { + const hatchData = hatchpath.getAttribute('d'); + const data = parsePath( + hatchData.match(/([+-]?(\d+(\.\d+)?))|[MmZzLlHhVvCcSsQqTtAaBb]/g) + ); + const len = data.length; + const startsWithM = data[0] === 'M'; + const relative = data[0].toLowerCase() === data[0]; + const point = new Point(0, 0); + let yOffset = getYDistance(hatchpath); + + if (data[len - 1].y !== undefined && yOffset < data[len - 1].y) { + yOffset = data[len - 1].y; + } + + // The offset must be positive + if (yOffset <= 0) { + console.error('y offset is non-positive'); + return; + } + patternHeight = bbox.height - bbox.height % yOffset; + + const currentYPositions = generatePositions( + patternHeight, hatchDiag, y, yOffset + ); + + currentXPositions.forEach(xPos => { + point.x = xPos; + + if (!startsWithM && !relative) { + d += `M ${xPos} 0`; + } + + currentYPositions.forEach(yPos => { + point.y = yPos; + + if (relative) { + // Path is relative, set the first point in each path render + d += `M ${xPos} ${yPos} ${hatchData}`; + } else { + // Path is absolute, translate every point + d += data.map(e => e.isPoint && e.isPoint() ? e.add(point) : e) + .map(e => e.isPoint && e.isPoint() ? e.toString() : e) + .reduce((acc, e) => `${acc} ${e}`, ''); + } + }); + }); + + // The hatchpaths are infinite, so they have no fill + path.style.fill = 'none'; + } + + path.setAttribute('d', d); + pattern.appendChild(path); + }); + + setAttributes(pattern, { + 'id': patternId, + 'patternUnits': unitUserSpace, + 'patternContentUnits': contentUnits, + 'width': patternWidth, + 'height': patternHeight, + 'x': bbox.x, + 'y': bbox.y, + 'patternTransform': `rotate(${rotate} ${0} ${0}) ${transform}` + }); + hatch.parentElement.insertBefore(pattern, hatch); + + shape.style.fill = `url(#${patternId})`; + shape.setAttribute('fill', `url(#${patternId})`); + } + } + }); +})(); diff --git a/src/extension/internal/polyfill/hatch_compressed.include b/src/extension/internal/polyfill/hatch_compressed.include new file mode 100644 index 0000000..2db6124 --- /dev/null +++ b/src/extension/internal/polyfill/hatch_compressed.include @@ -0,0 +1,4 @@ +//SPDX-License-Identifier: GPL-2.0-or-later +R"=====( +!function(){const t="http://www.w3.org/2000/svg",e=(t,e,r,n)=>{const u=(e-t)/2,i=r+u,s=t+u+n;let h=[];for(let t=r-(Math.round(i/n)+1)*n;t<s;t+=n)h.push(t);return h};class r{constructor(t,e){this.x=t,this.y=e}toString(){return`${this.x} ${this.y}`}isPoint(){return!0}clone(){return new r(this.x,this.y)}add(t){return new r(this.x+t.x,this.y+t.y)}distSquared(t){let e=this.x-t.x,r=this.y-t.y;return e*e+r*r}}document.querySelectorAll("rect,circle,ellipse,path,text").forEach((n,u)=>{let i=n.getAttribute("id");i||(i="hatch_shape_"+u,n.setAttribute("id",i));const s=(n.getAttribute("fill")||n.style.fill).match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);if(s&&s[1]){const u=document.getElementById(s[1]);if(u&&"hatch"===u.nodeName){const i=u.getAttributeNS("http://www.w3.org/1999/xlink","href");if(null!==i&&""!==i&&((t,e)=>{const r=["x","y","pitch","rotate","hatchUnits","hatchContentUnits","transform"],n=document.getElementById(e.slice(1));n&&"hatch"===n.nodeName&&(r.forEach(e=>{let r=n.getAttribute(e);null===t.getAttribute(e)&&null!==r&&t.setAttribute(e,r)}),0===t.children.length&&Array.from(n.children).forEach(e=>{t.appendChild(e.cloneNode(!0))}))})(u,i),0===u.children.length)return;const h=n.getBBox(),o=Math.ceil(Math.sqrt(h.width*h.width+h.height*h.height)),a=u.getAttribute("hatchUnits")||"objectBoundingBox",c=u.getAttribute("hatchContentUnits")||"userSpaceOnUse",b=Number(u.getAttribute("rotate"))||0,l=u.getAttribute("transform")||u.getAttribute("hatchTransform")||"",m=(t=>{const e=[];return t.forEach(t=>e.push(t)),e.sort((t,e)=>Number(e.getAttribute("offset"))-Number(t.getAttribute("offset")))})(u.querySelectorAll("hatchpath,hatchPath")),d="objectBoundingBox"===a?Number(u.getAttribute("x"))*h.width||0:Number(u.getAttribute("x"))||0,g="objectBoundingBox"===a?Number(u.getAttribute("y"))*h.width||0:Number(u.getAttribute("y"))||0;let p="objectBoundingBox"===a?Number(u.getAttribute("pitch"))*h.width||0:Number(u.getAttribute("pitch"))||0;if("objectBoundingBox"===c&&h.height&&(p/=h.height),p<=0)return void console.error("Non-positive pitch");const N=document.createElementNS(t,"pattern"),f=`${s[1]}_pattern`;let w=h.width-h.width%p,A=0;const y=e(w,o,d,p);m.forEach(n=>{let u=Number(n.getAttribute("offset"))||0;u=u>p?u%p:u;const i=y.map(t=>t+u),s=document.createElementNS(t,"path");let a="";for(let t=0;t<n.attributes.length;++t){const e=n.attributes.item(t);"d"!==e.name&&s.setAttribute(e.name,e.value)}if(null===n.getAttribute("d"))a+=i.reduce((t,e)=>`${t}M ${e} ${g} V ${o} `,""),A=o;else{const u=n.getAttribute("d"),c=(t=>{let e=[],n=0,u=t.length,i=0;for(;n<u;)switch(t[n].toUpperCase()){case"Z":e.push(t[n]),n+=1,i=0;break;case"M":case"L":case"T":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2]))),n+=3,i=1;break;case"H":e.push(t[n],new r(Number(t[n+1]),null)),n+=2,i=2;break;case"V":e.push(t[n],new r(null,Number(t[n+1]))),n+=2,i=3;break;case"C":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2])),new r(Number(t[n+3]),Number(t[n+4])),new r(Number(t[n+5]),Number(t[n+6]))),n+=7,i=4;break;case"S":case"Q":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2])),new r(Number(t[n+3]),Number(t[n+4]))),n+=5,i=5;break;case"A":e.push(t[n],t[n+1],t[n+2],t[n+3],t[n+4],t[n+5],new r(Number(t[n+6]),Number(t[n+7]))),n+=8,i=6;break;case"B":e.push(t[n],t[n+1]),n+=2,i=7;break;default:switch(i){case 1:e.push(new r(Number(t[n]),Number(t[n+1]))),n+=2;break;case 2:e.push(new r(Number(t[n]),null)),n+=1;break;case 3:e.push(new r(null,Number(t[n]))),n+=1;break;case 4:e.push(new r(Number(t[n]),Number(t[n+1])),new r(Number(t[n+2]),Number(t[n+3])),new r(Number(t[n+4]),Number(t[n+5]))),n+=6;break;case 5:e.push(new r(Number(t[n]),Number(t[n+1])),new r(Number(t[n+2]),Number(t[n+3]))),n+=4;break;case 6:e.push(t[n],t[n+1],t[n+2],t[n+3],t[n+4],new r(Number(t[n+5]),Number(t[n+6]))),n+=7;break;default:e.push(t[n]),n+=1}}return e})(u.match(/([+-]?(\d+(\.\d+)?))|[MmZzLlHhVvCcSsQqTtAaBb]/g)),b=c.length,l="M"===c[0],m=c[0].toLowerCase()===c[0],d=new r(0,0);let p=(e=>{const r=document.createElementNS(t,"path");let n=e.getAttribute("d");return"M"!==n[0].toUpperCase()&&(n=`M 0,0 ${n}`),r.setAttribute("d",n),r.getPointAtLength(r.getTotalLength()).y-r.getPointAtLength(0).y})(n);if(void 0!==c[b-1].y&&p<c[b-1].y&&(p=c[b-1].y),p<=0)return void console.error("y offset is non-positive");A=h.height-h.height%p;const N=e(A,o,g,p);i.forEach(t=>{d.x=t,l||m||(a+=`M ${t} 0`),N.forEach(e=>{d.y=e,a+=m?`M ${t} ${e} ${u}`:c.map(t=>t.isPoint&&t.isPoint()?t.add(d):t).map(t=>t.isPoint&&t.isPoint()?t.toString():t).reduce((t,e)=>`${t} ${e}`,"")})}),s.style.fill="none"}s.setAttribute("d",a),N.appendChild(s)}),((t,e)=>{for(let r in e)t.setAttribute(r,e[r])})(N,{id:f,patternUnits:"userSpaceOnUse",patternContentUnits:c,width:w,height:A,x:h.x,y:h.y,patternTransform:`rotate(${b} 0 0) ${l}`}),u.parentElement.insertBefore(N,u),n.style.fill=`url(#${f})`,n.setAttribute("fill",`url(#${f})`)}}})}(); +)=====" diff --git a/src/extension/internal/polyfill/hatch_tests/hatch.svg b/src/extension/internal/polyfill/hatch_tests/hatch.svg new file mode 100644 index 0000000..7e2f8de --- /dev/null +++ b/src/extension/internal/polyfill/hatch_tests/hatch.svg @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="360"> + <defs> + <hatch + pitch="15" + hatchUnits="userSpaceOnUse" + id="simple6"> + <hatchPath + d="m 0,0 5,10 5,-5" + offset="5" + stroke="#a080ff" /> + </hatch> + <hatch + id="transform2" + hatchUnits="userSpaceOnUse" + pitch="15" + rotate="30"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" /> + </hatch> + <hatch + id="transform4" + hatchUnits="userSpaceOnUse" + pitch="15" + rotate="45"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" /> + </hatch> + <hatch + id="transform8" + hatchUnits="userSpaceOnUse" + pitch="15" + x="-5" + y="-10" + rotate="30"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" /> + </hatch> + <hatch + id="transform9" + hatchUnits="userSpaceOnUse" + pitch="15" + rotate="30" + x="-5" + y="-10" + hatchTransform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-8.4757068,43.273395)"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" /> + </hatch> + </defs> + <rect fill="url(#simple6)" stroke="black" stroke-width="2" x="25" y="25" width="150" height="150"/> + <rect fill="url(#transform2) #ff0000;" stroke="black" stroke-width="1" width="115" height="115" x="25" y="200" /> + + <script type="text/javascript" xlink:href="../hatch.js"></script> +</svg> diff --git a/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg b/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg new file mode 100644 index 0000000..ca9ea2f --- /dev/null +++ b/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + xmlns:xlink="http://www.w3.org/1999/xlink" + inkscape:version="1.0alpha2 (5c5a8378bf, 2019-06-27, custom)" + sodipodi:docname="hatch01_with_js.svg" + id="svg24" + version="1.1" + height="400" + width="400"> + <metadata + id="metadata28"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <sodipodi:namedview + inkscape:current-layer="svg24" + inkscape:window-maximized="0" + inkscape:window-y="23" + inkscape:window-x="26" + inkscape:cy="200" + inkscape:cx="200" + inkscape:zoom="0.6025" + showgrid="false" + id="namedview26" + inkscape:window-height="480" + inkscape:window-width="686" + inkscape:pageshadow="2" + inkscape:pageopacity="0" + guidetolerance="10" + gridtolerance="10" + objecttolerance="10" + borderopacity="1" + inkscape:document-rotation="0" + bordercolor="#666666" + pagecolor="#ffffff" /> + <defs + id="defs14"> + <hatch + rotate="135" + pitch="5" + hatchContentUnits="userSpaceOnUse" + hatchUnits="userSpaceOnUse" + id="hatch1"> + <hatchpath + id="hatchpath2" + stroke-width="2" + stroke="#a080ff" /> + </hatch> + <hatch + rotate="135" + pitch="0.05" + hatchContentUnits="userSpaceOnUse" + hatchUnits="objectBoundingBox" + id="hatch2"> + <hatchpath + id="hatchpath5" + stroke-width="2" + stroke="#a080ff" /> + </hatch> + <hatch + rotate="135" + pitch="5" + hatchContentUnits="objectBoundingBox" + hatchUnits="userSpaceOnUse" + id="hatch3"> + <hatchpath + id="hatchpath8" + stroke-width="0.02" + stroke="#a080ff" /> + </hatch> + <hatch + rotate="135" + pitch="0.05" + hatchContentUnits="objectBoundingBox" + hatchUnits="objectBoundingBox" + id="hatch4"> + <hatchpath + id="hatchpath11" + stroke-width="0.02" + stroke="#a080ff" /> + </hatch> + </defs> + <rect + id="rect16" + height="100" + width="100" + y="50" + x="50" + stroke-width="2" + stroke="black" + fill="url(#hatch1)" /> + <rect + id="rect18" + height="100" + width="100" + y="50" + x="250" + stroke-width="2" + stroke="black" + fill="url(#hatch2)" /> + <rect + id="rect20" + height="100" + width="100" + y="250" + x="50" + stroke-width="2" + stroke="black" + fill="url(#hatch3)" /> + <rect + id="rect22" + height="100" + width="100" + y="250" + x="250" + stroke-width="2" + stroke="black" + fill="url(#hatch4)" /> + <script type="text/javascript" xlink:href="../hatch.js"></script> +</svg> diff --git a/src/extension/internal/polyfill/hatch_tests/hatch_test.svg b/src/extension/internal/polyfill/hatch_tests/hatch_test.svg new file mode 100644 index 0000000..49fecfb --- /dev/null +++ b/src/extension/internal/polyfill/hatch_tests/hatch_test.svg @@ -0,0 +1,11731 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="2600" + height="1700" + id="svg3780" + version="1.1" + inkscape:version="1.0alpha2 (2d4d49aaa0, 2019-06-18, custom)" + sodipodi:docname="hatch_test (copy).svg"> + <defs + id="defs3782"> + <hatch + id="simple1" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + stroke-width="2" + id="hatchPath2" /> + </hatch> + <hatch + id="simple2" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="15" + id="hatchPath5" /> + </hatch> + <hatch + id="simple3" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="M 0,0 5,10" + id="hatchPath8" /> + </hatch> + <hatch + id="simple4" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="L 0,0 5,10" + id="hatchPath11" /> + </hatch> + <hatch + id="simple5" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="M 0,0 5,10 10,5" + id="hatchPath14" /> + </hatch> + <hatch + id="simple6" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="m 0,0 5,10 5,-5" + id="hatchPath17" /> + </hatch> + <hatch + id="simple7" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="M 0,0 5,10 M 5,20" + id="hatchPath20" /> + </hatch> + <hatch + id="transform1" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="L 0,0 5,10 0,20" + id="hatchPath23" /> + </hatch> + <hatch + id="transform2" + hatchUnits="userSpaceOnUse" + pitch="15" + rotate="30"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" + id="hatchPath26" /> + </hatch> + <hatch + id="transform4" + hatchUnits="userSpaceOnUse" + pitch="15" + rotate="45"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" + id="hatchPath29" /> + </hatch> + <hatch + id="transform7" + hatchUnits="userSpaceOnUse" + pitch="15" + x="-5" + y="-10"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" + id="hatchPath32" /> + </hatch> + <hatch + id="transform8" + hatchUnits="userSpaceOnUse" + pitch="15" + x="-5" + y="-10" + rotate="30"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" + id="hatchPath35" /> + </hatch> + <hatch + id="transform9" + hatchUnits="userSpaceOnUse" + pitch="15" + rotate="30" + x="-5" + y="-10" + hatchTransform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-8.4757068,43.273395)"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,20" + id="hatchPath38" /> + </hatch> + <hatch + id="multiple1" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + id="hatchPath41" /> + <hatchPath + stroke="#32ff3f" + offset="10" + id="hatchPath43" /> + </hatch> + <hatch + id="multiple2" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + id="hatchPath46" /> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10" + id="hatchPath48" /> + </hatch> + <hatch + id="multiple3" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="L 0,0 5,17" + id="hatchPath51" /> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10" + id="hatchPath53" /> + </hatch> + <hatch + id="stroke1" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + stroke-width="5" + stroke-dasharray="10 4 2 4" + id="hatchPath56" /> + </hatch> + <hatch + id="overflow1" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + d="L 0,0 5,5 -5,15, 0,20" + id="hatchPath59" /> + </hatch> + <hatch + id="overflow2" + style="overflow:hidden" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="0" + d="L 0,0 5,5 -5,15, 0,20" + id="hatchPath62" /> + </hatch> + <hatch + id="overflow3" + style="overflow:visible" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="0" + d="L 0,0 5,5 -5,15, 0,20" + id="hatchPath65" /> + </hatch> + <hatch + id="overflow4" + style="overflow:visible" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#32ff3f" + offset="5" + id="hatchPath68" /> + <hatchPath + stroke="#ff0000" + offset="20" + id="hatchPath70" /> + </hatch> + <hatch + id="ref1" + hatchUnits="userSpaceOnUse" + pitch="15"> + <hatchPath + stroke="#a080ff" + offset="5" + id="hatchPath73" /> + </hatch> + <hatch + id="ref2" + xlink:href="#ref1" /> + <hatch + id="ref3" + xlink:href="#ref1" + pitch="45" /> + <hatch + id="degenerate1" + pitch="45" /> + <hatch + id="degenerate2" + xlink:href="#nonexisting" + pitch="45" /> + <hatch + id="degenerate3" + pitch="30"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 0,15" + id="hatchPath80" /> + </hatch> + <hatch + id="degenerate4" + pitch="30"> + <hatchPath + stroke="#a080ff" + offset="10" + d="L 0,0 5,10 -5,15" + id="hatchPath83" /> + </hatch> + <linearGradient + inkscape:collect="always" + id="linearGradient11910"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop11912" /> + <stop + style="stop-color:#000000;stop-opacity:0;" + offset="1" + id="stop11914" /> + </linearGradient> + <linearGradient + id="linearGradient10590"> + <stop + style="stop-color:#000000;stop-opacity:1;" + offset="0" + id="stop10592" /> + <stop + style="stop-color:#c9c9c9;stop-opacity:0;" + offset="1" + id="stop10594" /> + </linearGradient> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4338"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4340" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4342"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4344" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4346"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4348" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4350"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4352" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4354"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4356" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4358"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4360" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4362"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4364" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4366"> + <rect + style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4368" + width="115" + height="115" + x="175" + y="42.362183" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7203"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7205" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7207"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7209" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7211"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7213" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7215"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7217" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7219"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7221" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7223"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7225" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7227"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7229" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7231"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7233" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7235"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7237" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7239"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7241" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7243"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7245" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7247"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7249" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7251"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7253" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7255"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7257" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7259"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7261" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7263"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7265" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7267"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7269" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7271"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7273" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7275"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7277" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7279"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7281" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7283"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7285" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7287"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7289" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7291"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7293" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7295"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7297" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7299"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7301" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7303"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7305" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7307"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7309" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7311"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7313" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7315"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7317" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7319"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7321" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7323"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7325" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7327"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7329" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7331"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7333" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7335"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7337" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7339"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7341" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7343"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7345" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7347"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7349" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7351"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7353" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7355"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7357" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7359"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7361" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7363"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7365" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7367"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7369" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7371"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7373" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7375"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7377" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7379"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7381" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7383"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7385" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7387"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7389" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7391"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7393" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7395"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7397" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7399"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7401" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7403"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7405" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7407"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7409" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7411"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7413" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7415"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7417" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7419"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7421" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7423"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7425" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7427"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7429" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7431"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7433" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7435"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7437" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7439"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7441" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7443"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7445" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7447"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7449" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7451"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7453" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7455"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7457" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7459"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7461" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7463"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7465" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7467"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7469" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7471"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7473" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7475"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7477" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7479"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7481" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7483"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7485" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7487"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7489" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7491"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7493" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7495"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7497" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7499"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7501" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7503"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7505" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7507"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7509" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7511"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7513" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7515"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7517" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7519"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7521" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7523"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7525" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7527"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7529" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7531"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7533" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7535"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7537" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7539"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7541" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7543"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7545" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7547"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7549" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7551"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7553" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7555"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7557" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7559"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7561" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7563"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7565" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7567"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7569" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7571"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7573" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7575"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7577" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7579"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7581" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7583"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7585" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7587"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7589" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7591"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7593" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7595"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7597" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7599"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7601" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7603"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7605" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7607"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7609" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7611"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7613" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7615"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7617" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7619"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7621" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7623"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7625" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7627"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7629" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7631"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7633" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7635"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7637" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7639"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7641" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7643"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7645" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7647"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7649" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7651"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7653" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7655"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7657" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7659"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7661" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7663"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7665" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7667"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7669" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7671"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7673" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7675"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7677" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7679"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7681" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7683"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7685" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7687"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7689" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7691"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7693" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7695"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7697" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7699"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7701" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7703"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7705" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7707"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7709" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7711"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7713" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7715"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7717" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7719"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7721" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7723"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7725" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7727"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7729" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7731"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7733" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7735"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7737" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7739"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7741" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7743"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7745" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7747"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7749" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7751"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7753" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7755"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7757" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7759"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7761" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7763"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7765" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7767"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7769" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7771"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7773" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7775"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7777" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7779"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7781" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7783"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7785" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7787"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7789" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7791"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7793" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7795"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7797" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7799"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7801" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7803"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7805" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7807"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7809" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7811"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7813" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7815"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7817" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7819"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7821" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7823"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7825" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7827"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7829" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7831"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7833" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7835"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7837" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7839"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7841" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7843"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7845" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7847"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7849" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7851"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7853" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7855"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7857" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7859"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7861" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7863"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7865" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7867"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7869" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7871"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7873" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7875"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7877" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7879"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7881" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7883"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7885" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7887"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7889" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7891"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7893" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7895"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7897" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7899"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7901" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7903"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7905" + width="115" + height="115" + x="170.5" + y="327.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7948"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7950" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7952"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7954" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7956"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7958" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7960"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7962" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7964"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7966" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7968"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7970" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7972"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7974" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath7976"> + <rect + y="452.86218" + x="170.5" + height="115" + width="115" + id="rect7978" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8370"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8372" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8374"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8376" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8378"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8380" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8382"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8384" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8386"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8388" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8390"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8392" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8394"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8396" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8398"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8400" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8402"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8404" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8406"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8408" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8410"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8412" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8414"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8416" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8418"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8420" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8422"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8424" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8426"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8428" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8430"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8432" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8434"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8436" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8438"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8440" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8442"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8444" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8446"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8448" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8450"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8452" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8454"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8456" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8458"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8460" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8462"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8464" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8466"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8468" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8470"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8472" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8474"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8476" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8478"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8480" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8482"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8484" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8486"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8488" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8490"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8492" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8494"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8496" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8498"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8500" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8502"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8504" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8506"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8508" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8510"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8512" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8514"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8516" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8518"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8520" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8522"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8524" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8526"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8528" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8530"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8532" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8534"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8536" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8538"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8540" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8542"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8544" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8546"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8548" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8550"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8552" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8554"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8556" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8558"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8560" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8562"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8564" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8566"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8568" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8570"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8572" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8574"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8576" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8578"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8580" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8582"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8584" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8586"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8588" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8590"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8592" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8594"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8596" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8598"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8600" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8602"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8604" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8606"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8608" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8610"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8612" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8614"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8616" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8618"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8620" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8622"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8624" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8626"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8628" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8630"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8632" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8634"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8636" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8638"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8640" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8642"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8644" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8646"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8648" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8650"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8652" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8654"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8656" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8658"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8660" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8662"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8664" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8666"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8668" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8670"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8672" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8674"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8676" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8678"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8680" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8682"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8684" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8686"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8688" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8690"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8692" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8694"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8696" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8698"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8700" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8702"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8704" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8706"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8708" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8710"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8712" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8714"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8716" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8718"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8720" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8722"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8724" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8726"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8728" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8730"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8732" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8734"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8736" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8738"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8740" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8742"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8744" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8746"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8748" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8750"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8752" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8754"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8756" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8758"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8760" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8762"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8764" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8766"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8768" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8770"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8772" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8774"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8776" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8778"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8780" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8782"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8784" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8786"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8788" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8790"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8792" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8794"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8796" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8798"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8800" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8802"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8804" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8806"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8808" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8810"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8812" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8814"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8816" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8818"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8820" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8822"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8824" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8826"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8828" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8830"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8832" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8834"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8836" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8838"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8840" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8842"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8844" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8846"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8848" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8850"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8852" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8854"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8856" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8858"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8860" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8862"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8864" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8866"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8868" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8870"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8872" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8874"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8876" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8878"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8880" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8882"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8884" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8886"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8888" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8890"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8892" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8894"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8896" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8898"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8900" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8902"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8904" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8906"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8908" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8910"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8912" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8914"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8916" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8918"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8920" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8922"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8924" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8926"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8928" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8930"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8932" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8934"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8936" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8938"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8940" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8942"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8944" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8946"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8948" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8950"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8952" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8954"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8956" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8958"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8960" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8962"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8964" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8966"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8968" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8970"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8972" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8974"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8976" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8978"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8980" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8982"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8984" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8986"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8988" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8990"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8992" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8994"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect8996" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath8998"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9000" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9002"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9004" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9006"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9008" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9010"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9012" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9014"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9016" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9018"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9020" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9022"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9024" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9026"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9028" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9030"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9032" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9034"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9036" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9038"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9040" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9042"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9044" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9046"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9048" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9050"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9052" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9054"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9056" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9058"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9060" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9062"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9064" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9066"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9068" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9070"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9072" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9074"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9076" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9078"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9080" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9082"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9084" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9086"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9088" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9090"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9092" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9094"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9096" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9098"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9100" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9102"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9104" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9106"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9108" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9110"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9112" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9114"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9116" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9118"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9120" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9122"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9124" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9126"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9128" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9130"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9132" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9134"> + <rect + style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9136" + width="115" + height="115" + x="170.5" + y="577.86218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9545"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9547" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9549"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9551" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9553"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9555" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9557"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9559" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9561"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9563" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9565"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9567" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9569"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9571" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath9573"> + <rect + y="62.862183" + x="355.5" + height="115" + width="115" + id="rect9575" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10402"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10404" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10406"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10408" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10410"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10412" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10414"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10416" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10418"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10420" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10422"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10424" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10426"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10428" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10430"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10432" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10434"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10436" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10438"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10440" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10442"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10444" + width="115" + height="115" + x="520" + y="267.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10476"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10478" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10480"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10482" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10484"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10486" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10488"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10490" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10492"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10494" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10496"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10498" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10500"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10502" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10504"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10506" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10508"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10510" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10512"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10514" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10516"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10518" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10520"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10522" + width="115" + height="115" + x="520" + y="462.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10554"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10556" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10558"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10560" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10562"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10564" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10566"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10568" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10570"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10572" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10574"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10576" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10578"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10580" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10582"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10584" + width="115" + height="115" + x="505" + y="577.36218" /> + </clipPath> + <pattern + patternUnits="userSpaceOnUse" + width="71" + height="71" + patternTransform="translate(-160.5,-138.125)" + id="pattern10608"> + <rect + y="0.48718262" + x="0.5" + height="70" + width="70" + id="rect10606" + style="opacity:0.94899998;fill:#000000;fill-opacity:1;stroke:#ffff00;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none;stroke-dashoffset:0" /> + </pattern> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10625"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10627" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10629"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10631" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10633"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10635" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10637"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10639" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10641"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10643" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10645"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10647" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10649"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10651" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10653"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10655" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10657"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10659" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10661"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10663" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10665"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10667" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10669"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10671" + width="115" + height="115" + x="530" + y="702.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10757"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10759" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10761"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10763" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10765"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10767" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10769"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10771" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10773"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10775" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10777"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10779" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10781"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10783" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10785"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10787" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10789"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10791" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10793"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10795" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10797"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10799" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10801"> + <rect + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10803" + width="115" + height="115" + x="535" + y="762.36218" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10877"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10879" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10881"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10883" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10885"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10887" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10889"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10891" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10893"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10895" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10897"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10899" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10901"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10903" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10905"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10907" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10909"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10911" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10913"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10915" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10917"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10919" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10921"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10923" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10925"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10927" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10929"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10931" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath10933"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect10935" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11035"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11037" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11039"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11041" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11043"> + <rect + y="212.36218" + x="915" + height="115" + width="115" + id="rect11045" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11047"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11049" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11051"> + <rect + y="212.36218" + x="930" + height="115" + width="115" + id="rect11053" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11055"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11057" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11059"> + <rect + y="212.36218" + x="945" + height="115" + width="115" + id="rect11061" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11063"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11065" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11067"> + <rect + y="212.36218" + x="960" + height="115" + width="115" + id="rect11069" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11071"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11073" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11075"> + <rect + y="212.36218" + x="975" + height="115" + width="115" + id="rect11077" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11079"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11081" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11083"> + <rect + y="212.36218" + x="990" + height="115" + width="115" + id="rect11085" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11087"> + <rect + y="342.36218" + x="1005" + height="115" + width="115" + id="rect11089" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11091"> + <rect + y="212.36218" + x="1005" + height="115" + width="115" + id="rect11093" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11155"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11157" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11159"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11161" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11163"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11165" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11167"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11169" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11171"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11173" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11175"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11177" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11179"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11181" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11183"> + <rect + y="217.36218" + x="1485" + height="115" + width="115" + id="rect11185" + style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11488"> + <rect + y="168.9841" + x="-17.026188" + height="95.052338" + width="95.052338" + id="rect11490" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11492"> + <rect + y="177.75092" + x="-8.259387" + height="95.052338" + width="95.052338" + id="rect11494" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11496"> + <rect + y="186.51772" + x="0.50741416" + height="95.052338" + width="95.052338" + id="rect11498" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11500"> + <rect + y="195.28452" + x="9.2742147" + height="95.052338" + width="95.052338" + id="rect11502" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11504"> + <rect + y="204.05132" + x="18.041016" + height="95.052338" + width="95.052338" + id="rect11506" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11508"> + <rect + y="212.81812" + x="26.807816" + height="95.052338" + width="95.052338" + id="rect11510" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11512"> + <rect + y="221.58492" + x="35.574615" + height="95.052338" + width="95.052338" + id="rect11514" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11516"> + <rect + y="221.58492" + x="35.574615" + height="95.052338" + width="95.052338" + id="rect11518" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11520"> + <rect + y="221.58492" + x="35.574615" + height="95.052338" + width="95.052338" + id="rect11522" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11524"> + <rect + y="221.58492" + x="35.574615" + height="95.052338" + width="95.052338" + id="rect11526" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath11528"> + <rect + y="221.58492" + x="35.574615" + height="95.052338" + width="95.052338" + id="rect11530" + style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" /> + </clipPath> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient11910" + id="linearGradient11916" + x1="179.5" + y1="-217.63782" + x2="480.5" + y2="-217.63782" + gradientUnits="userSpaceOnUse" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.3245239" + inkscape:cx="1145.4883" + inkscape:cy="1129.6405" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:snap-bbox="true" + inkscape:window-width="1920" + inkscape:window-height="1014" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + inkscape:object-paths="true" + inkscape:object-nodes="true" + inkscape:document-rotation="0"> + <inkscape:grid + type="xygrid" + id="grid3794" /> + </sodipodi:namedview> + <metadata + id="metadata3785"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1"> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="27.142857" + y="42.362183" + id="text3788"><tspan + sodipodi:role="line" + id="tspan3790" + x="27.142857" + y="42.362183" + style="font-size:24px;line-height:1.25;font-family:sans-serif">Simple hatches</tspan></text> + <g + id="g4372" + transform="translate(384.5,5.5000026)"> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4366)" + inkscape:connector-curvature="0" + id="path3798" + d="M 180,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4362)" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195,42.362183 V 192.36218" + id="path4310" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4358)" + inkscape:connector-curvature="0" + id="path4314" + d="M 210,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4354)" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 225,42.362183 V 192.36218" + id="path4318" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4350)" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 240,42.362183 V 192.36218" + id="path4322" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4346)" + inkscape:connector-curvature="0" + id="path4326" + d="M 255,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4342)" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 270,42.362183 V 192.36218" + id="path4330" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4338)" + inkscape:connector-curvature="0" + id="path4334" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="62.362183" + x="181" + height="115" + width="115" + id="rect4370" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + style="fill:url(#simple1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4383" + width="115" + height="115" + x="435.5" + y="67.862183" /> + <g + transform="translate(384.5,135.5)" + id="g4385"> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 180,42.362183 V 192.36218" + id="path4387" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4366)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path4389" + d="M 195,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4362)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 210,42.362183 V 192.36218" + id="path4391" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4358)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path4393" + d="M 225,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4354)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path4395" + d="M 240,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4350)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 255,42.362183 V 192.36218" + id="path4397" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4346)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path4399" + d="M 270,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4342)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 285,42.362183 V 192.36218" + id="path4401" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4338)" + transform="translate(6,20)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect4403" + width="115" + height="115" + x="181" + y="62.362183" /> + </g> + <rect + y="197.86218" + x="435.5" + height="115" + width="115" + id="rect4405" + style="fill:url(#simple2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g4695" + transform="translate(384.5,135.5)"> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4366)" + inkscape:connector-curvature="0" + id="path4697" + d="M 180,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4362)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195,42.362183 V 192.36218" + id="path4699" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4358)" + inkscape:connector-curvature="0" + id="path4701" + d="M 210,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4354)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 225,42.362183 V 192.36218" + id="path4703" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4350)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 240,42.362183 V 192.36218" + id="path4705" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4346)" + inkscape:connector-curvature="0" + id="path4707" + d="M 255,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4342)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 270,42.362183 V 192.36218" + id="path4709" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4338)" + inkscape:connector-curvature="0" + id="path4711" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="62.362183" + x="181" + height="115" + width="115" + id="rect4713" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,327.36218 5,10" + id="path5443" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7903)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,337.36218 5,10" + id="path5445" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7899)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,347.36218 5,10" + id="path5447" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7895)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,357.36218 5,10" + id="path5449" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7891)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,367.36218 5,10" + id="path5451" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7887)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5453" + d="m 175,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7883)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5455" + d="m 175,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7879)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5457" + d="m 175,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7875)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5459" + d="m 175,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7871)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5461" + d="m 175,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7867)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,377.36218 5,10" + id="path5463" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7863)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5465" + d="m 175,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7859)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5467" + d="m 175,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7855)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5469" + d="m 175,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7851)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5471" + d="m 175,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7847)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5473" + d="m 175,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7843)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,387.36218 5,10" + id="path5475" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7839)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,397.36218 5,10" + id="path5477" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7835)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,407.36218 5,10" + id="path5479" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7831)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,417.36218 5,10" + id="path5481" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7827)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,427.36218 5,10" + id="path5483" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7823)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5485" + d="m 175,437.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7819)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5487" + d="m 190,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7815)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5489" + d="m 190,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7811)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5491" + d="m 190,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7807)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5493" + d="m 190,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7803)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5495" + d="m 190,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7799)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,327.36218 5,10" + id="path5497" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7795)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,337.36218 5,10" + id="path5499" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7791)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,347.36218 5,10" + id="path5501" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7787)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,357.36218 5,10" + id="path5503" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7783)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,367.36218 5,10" + id="path5505" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7779)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5507" + d="m 190,377.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7775)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,387.36218 5,10" + id="path5509" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7771)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,397.36218 5,10" + id="path5511" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7767)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,407.36218 5,10" + id="path5513" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7763)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,417.36218 5,10" + id="path5515" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7759)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,427.36218 5,10" + id="path5517" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7755)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5519" + d="m 190,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7751)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5521" + d="m 190,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7747)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5523" + d="m 190,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7743)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5525" + d="m 190,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7739)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5527" + d="m 190,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7735)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,437.36218 5,10" + id="path5529" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7731)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5531" + d="m 205,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7727)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5533" + d="m 205,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7723)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5535" + d="m 205,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7719)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5537" + d="m 205,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7715)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5539" + d="m 205,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7711)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,327.36218 5,10" + id="path5541" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7707)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,337.36218 5,10" + id="path5543" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7703)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,347.36218 5,10" + id="path5545" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7699)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,357.36218 5,10" + id="path5547" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7695)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,367.36218 5,10" + id="path5549" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7691)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5551" + d="m 205,377.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7687)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,387.36218 5,10" + id="path5553" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7683)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,397.36218 5,10" + id="path5555" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7679)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,407.36218 5,10" + id="path5557" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7675)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,417.36218 5,10" + id="path5559" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7671)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,427.36218 5,10" + id="path5561" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7667)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5563" + d="m 205,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7663)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5565" + d="m 205,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7659)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5567" + d="m 205,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7655)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5569" + d="m 205,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7651)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5571" + d="m 205,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7647)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,437.36218 5,10" + id="path5573" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7643)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,327.36218 5,10" + id="path5575" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7639)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,337.36218 5,10" + id="path5577" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7635)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,347.36218 5,10" + id="path5579" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7631)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,357.36218 5,10" + id="path5581" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7627)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,367.36218 5,10" + id="path5583" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7623)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5585" + d="m 220,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7619)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5587" + d="m 220,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7615)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5589" + d="m 220,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7611)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5591" + d="m 220,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7607)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5593" + d="m 220,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7603)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,377.36218 5,10" + id="path5595" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7599)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5597" + d="m 220,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7595)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5599" + d="m 220,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7591)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5601" + d="m 220,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7587)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5603" + d="m 220,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7583)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5605" + d="m 220,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7579)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,387.36218 5,10" + id="path5607" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7575)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,397.36218 5,10" + id="path5609" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7571)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,407.36218 5,10" + id="path5611" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7567)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,417.36218 5,10" + id="path5613" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7563)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,427.36218 5,10" + id="path5615" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7559)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5617" + d="m 220,437.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7555)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5619" + d="m 235,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7551)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5621" + d="m 235,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7547)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5623" + d="m 235,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7543)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5625" + d="m 235,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7539)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5627" + d="m 235,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7535)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,327.36218 5,10" + id="path5629" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7531)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,337.36218 5,10" + id="path5631" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7527)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,347.36218 5,10" + id="path5633" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7523)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,357.36218 5,10" + id="path5635" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7519)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,367.36218 5,10" + id="path5637" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7515)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5639" + d="m 235,377.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7511)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,387.36218 5,10" + id="path5641" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7507)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,397.36218 5,10" + id="path5643" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7503)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,407.36218 5,10" + id="path5645" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7499)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,417.36218 5,10" + id="path5647" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7495)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,427.36218 5,10" + id="path5649" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7491)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5651" + d="m 235,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7487)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5653" + d="m 235,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7483)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5655" + d="m 235,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7479)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5657" + d="m 235,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7475)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5659" + d="m 235,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7471)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,437.36218 5,10" + id="path5661" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7467)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,327.36218 5,10" + id="path5663" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7463)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,337.36218 5,10" + id="path5665" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7459)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,347.36218 5,10" + id="path5667" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7455)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,357.36218 5,10" + id="path5669" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7451)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,367.36218 5,10" + id="path5671" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7447)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5673" + d="m 250,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7443)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5675" + d="m 250,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7439)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5677" + d="m 250,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7435)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5679" + d="m 250,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7431)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5681" + d="m 250,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7427)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,377.36218 5,10" + id="path5683" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7423)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5685" + d="m 250,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7419)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5687" + d="m 250,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7415)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5689" + d="m 250,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7411)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5691" + d="m 250,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7407)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5693" + d="m 250,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7403)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,387.36218 5,10" + id="path5695" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7399)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,397.36218 5,10" + id="path5697" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7395)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,407.36218 5,10" + id="path5699" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7391)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,417.36218 5,10" + id="path5701" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7387)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,427.36218 5,10" + id="path5703" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7383)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5705" + d="m 250,437.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7379)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,327.36218 5,10" + id="path5707" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7375)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,337.36218 5,10" + id="path5709" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7371)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,347.36218 5,10" + id="path5711" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7367)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,357.36218 5,10" + id="path5713" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7363)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,367.36218 5,10" + id="path5715" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7359)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5717" + d="m 265,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7355)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5719" + d="m 265,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7351)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5721" + d="m 265,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7347)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5723" + d="m 265,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7343)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5725" + d="m 265,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7339)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,377.36218 5,10" + id="path5727" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7335)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5729" + d="m 265,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7331)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5731" + d="m 265,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7327)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5733" + d="m 265,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7323)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5735" + d="m 265,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7319)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5737" + d="m 265,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7315)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,387.36218 5,10" + id="path5739" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7311)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,397.36218 5,10" + id="path5741" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7307)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,407.36218 5,10" + id="path5743" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7303)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,417.36218 5,10" + id="path5745" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7299)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,427.36218 5,10" + id="path5747" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7295)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5749" + d="m 265,437.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7291)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5751" + d="m 280,327.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7287)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5753" + d="m 280,337.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7283)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5755" + d="m 280,347.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7279)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5757" + d="m 280,357.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7275)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5759" + d="m 280,367.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7271)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,327.36218 5,10" + id="path5761" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7267)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,337.36218 5,10" + id="path5763" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7263)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,347.36218 5,10" + id="path5765" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7259)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,357.36218 5,10" + id="path5767" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7255)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,367.36218 5,10" + id="path5769" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7251)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5771" + d="m 280,377.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7247)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,387.36218 5,10" + id="path5773" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7243)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,397.36218 5,10" + id="path5775" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7239)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,407.36218 5,10" + id="path5777" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7235)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,417.36218 5,10" + id="path5779" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7231)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,427.36218 5,10" + id="path5781" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7227)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5783" + d="m 280,387.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7223)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5785" + d="m 280,397.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7219)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5787" + d="m 280,407.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7215)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5789" + d="m 280,417.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7211)" + transform="translate(395,2.6171874e-6)" /> + <path + inkscape:connector-curvature="0" + id="path5791" + d="m 280,427.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7207)" + transform="translate(395,2.6171874e-6)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,437.36218 5,10" + id="path5793" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7203)" + transform="translate(395,2.6171874e-6)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7907" + width="115" + height="115" + x="565.5" + y="327.86218" /> + <rect + style="fill:url(#simple3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect7909" + width="115" + height="115" + x="435.5" + y="327.86218" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path7930" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7976)" + transform="translate(395,5.0000026)" /> + <path + inkscape:connector-curvature="0" + id="path7932" + d="m 190,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7972)" + transform="translate(395,5.0000026)" /> + <path + inkscape:connector-curvature="0" + id="path7934" + d="m 205,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7968)" + transform="translate(395,5.0000026)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path7936" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7964)" + transform="translate(395,5.0000026)" /> + <path + inkscape:connector-curvature="0" + id="path7938" + d="m 235,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7960)" + transform="translate(395,5.0000026)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path7940" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7956)" + transform="translate(395,5.0000026)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path7942" + inkscape:connector-curvature="0" + clip-path="url(#clipPath7952)" + transform="translate(395,5.0000026)" /> + <path + inkscape:connector-curvature="0" + id="path7944" + d="m 280,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath7948)" + transform="translate(395,5.0000026)" /> + <rect + y="457.86218" + x="565.5" + height="115" + width="115" + id="rect7980" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + y="457.86218" + x="435.5" + height="115" + width="115" + id="rect7982" + style="fill:url(#simple4) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g9142" + transform="translate(395,5.0000026)"> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9134)" + inkscape:connector-curvature="0" + id="path7984" + d="m 175,582.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9130)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,587.36218 5,10 5,-5" + id="path7986" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9126)" + inkscape:connector-curvature="0" + id="path7988" + d="m 175,592.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9122)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,597.36218 5,10 5,-5" + id="path7990" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9118)" + inkscape:connector-curvature="0" + id="path7992" + d="m 175,602.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9114)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,607.36218 5,10 5,-5" + id="path7994" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9110)" + inkscape:connector-curvature="0" + id="path7996" + d="m 175,612.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9106)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,617.36218 5,10 5,-5" + id="path7998" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9102)" + inkscape:connector-curvature="0" + id="path8000" + d="m 175,622.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9098)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,627.36218 5,10 5,-5" + id="path8002" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9094)" + inkscape:connector-curvature="0" + id="path8004" + d="m 175,632.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9090)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,637.36218 5,10 5,-5" + id="path8006" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9086)" + inkscape:connector-curvature="0" + id="path8008" + d="m 175,642.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9082)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,647.36218 5,10 5,-5" + id="path8010" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9078)" + inkscape:connector-curvature="0" + id="path8012" + d="m 175,652.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9074)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,657.36218 5,10 5,-5" + id="path8014" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9070)" + inkscape:connector-curvature="0" + id="path8016" + d="m 175,662.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9066)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,667.36218 5,10 5,-5" + id="path8018" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9062)" + inkscape:connector-curvature="0" + id="path8020" + d="m 175,672.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9058)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,582.36218 5,10 5,-5" + id="path8022" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9054)" + inkscape:connector-curvature="0" + id="path8024" + d="m 190,587.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9050)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,592.36218 5,10 5,-5" + id="path8026" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9046)" + inkscape:connector-curvature="0" + id="path8028" + d="m 190,597.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9042)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,602.36218 5,10 5,-5" + id="path8030" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9038)" + inkscape:connector-curvature="0" + id="path8032" + d="m 190,607.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9034)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,612.36218 5,10 5,-5" + id="path8034" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9030)" + inkscape:connector-curvature="0" + id="path8036" + d="m 190,617.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9026)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,622.36218 5,10 5,-5" + id="path8038" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9022)" + inkscape:connector-curvature="0" + id="path8040" + d="m 190,627.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9018)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,632.36218 5,10 5,-5" + id="path8042" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9014)" + inkscape:connector-curvature="0" + id="path8044" + d="m 190,637.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9010)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,642.36218 5,10 5,-5" + id="path8046" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9006)" + inkscape:connector-curvature="0" + id="path8048" + d="m 190,647.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath9002)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,652.36218 5,10 5,-5" + id="path8050" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8998)" + inkscape:connector-curvature="0" + id="path8052" + d="m 190,657.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8994)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,662.36218 5,10 5,-5" + id="path8054" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8990)" + inkscape:connector-curvature="0" + id="path8056" + d="m 190,667.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8986)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,672.36218 5,10 5,-5" + id="path8058" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8982)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,582.36218 5,10 5,-5" + id="path8060" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8978)" + inkscape:connector-curvature="0" + id="path8062" + d="m 205,587.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8974)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,592.36218 5,10 5,-5" + id="path8064" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8970)" + inkscape:connector-curvature="0" + id="path8066" + d="m 205,597.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8966)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,602.36218 5,10 5,-5" + id="path8068" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8962)" + inkscape:connector-curvature="0" + id="path8070" + d="m 205,607.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8958)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,612.36218 5,10 5,-5" + id="path8072" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8954)" + inkscape:connector-curvature="0" + id="path8074" + d="m 205,617.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8950)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,622.36218 5,10 5,-5" + id="path8076" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8946)" + inkscape:connector-curvature="0" + id="path8078" + d="m 205,627.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8942)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,632.36218 5,10 5,-5" + id="path8080" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8938)" + inkscape:connector-curvature="0" + id="path8082" + d="m 205,637.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8934)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,642.36218 5,10 5,-5" + id="path8084" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8930)" + inkscape:connector-curvature="0" + id="path8086" + d="m 205,647.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8926)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,652.36218 5,10 5,-5" + id="path8088" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8922)" + inkscape:connector-curvature="0" + id="path8090" + d="m 205,657.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8918)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,662.36218 5,10 5,-5" + id="path8092" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8914)" + inkscape:connector-curvature="0" + id="path8094" + d="m 205,667.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8910)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,672.36218 5,10 5,-5" + id="path8096" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8906)" + inkscape:connector-curvature="0" + id="path8098" + d="m 220,582.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8902)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,587.36218 5,10 5,-5" + id="path8100" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8898)" + inkscape:connector-curvature="0" + id="path8102" + d="m 220,592.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8894)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,597.36218 5,10 5,-5" + id="path8104" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8890)" + inkscape:connector-curvature="0" + id="path8106" + d="m 220,602.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8886)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,607.36218 5,10 5,-5" + id="path8108" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8882)" + inkscape:connector-curvature="0" + id="path8110" + d="m 220,612.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8878)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,617.36218 5,10 5,-5" + id="path8112" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8874)" + inkscape:connector-curvature="0" + id="path8114" + d="m 220,622.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8870)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,627.36218 5,10 5,-5" + id="path8116" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8866)" + inkscape:connector-curvature="0" + id="path8118" + d="m 220,632.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8862)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,637.36218 5,10 5,-5" + id="path8120" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8858)" + inkscape:connector-curvature="0" + id="path8122" + d="m 220,642.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8854)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,647.36218 5,10 5,-5" + id="path8124" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8850)" + inkscape:connector-curvature="0" + id="path8126" + d="m 220,652.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8846)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,657.36218 5,10 5,-5" + id="path8128" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8842)" + inkscape:connector-curvature="0" + id="path8130" + d="m 220,662.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8838)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,667.36218 5,10 5,-5" + id="path8132" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8834)" + inkscape:connector-curvature="0" + id="path8134" + d="m 220,672.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8830)" + inkscape:connector-curvature="0" + id="path8136" + d="m 235,582.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8826)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,587.36218 5,10 5,-5" + id="path8138" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8822)" + inkscape:connector-curvature="0" + id="path8140" + d="m 235,592.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8818)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,597.36218 5,10 5,-5" + id="path8142" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8814)" + inkscape:connector-curvature="0" + id="path8144" + d="m 235,602.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8810)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,607.36218 5,10 5,-5" + id="path8146" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8806)" + inkscape:connector-curvature="0" + id="path8148" + d="m 235,612.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8802)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,617.36218 5,10 5,-5" + id="path8150" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8798)" + inkscape:connector-curvature="0" + id="path8152" + d="m 235,622.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8794)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,627.36218 5,10 5,-5" + id="path8154" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8790)" + inkscape:connector-curvature="0" + id="path8156" + d="m 235,632.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8786)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,637.36218 5,10 5,-5" + id="path8158" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8782)" + inkscape:connector-curvature="0" + id="path8160" + d="m 235,642.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8778)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,647.36218 5,10 5,-5" + id="path8162" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8774)" + inkscape:connector-curvature="0" + id="path8164" + d="m 235,652.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8770)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,657.36218 5,10 5,-5" + id="path8166" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8766)" + inkscape:connector-curvature="0" + id="path8168" + d="m 235,662.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8762)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,667.36218 5,10 5,-5" + id="path8170" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8758)" + inkscape:connector-curvature="0" + id="path8172" + d="m 235,672.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8754)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,582.36218 5,10 5,-5" + id="path8174" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8750)" + inkscape:connector-curvature="0" + id="path8176" + d="m 250,587.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8746)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,592.36218 5,10 5,-5" + id="path8178" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8742)" + inkscape:connector-curvature="0" + id="path8180" + d="m 250,597.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8738)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,602.36218 5,10 5,-5" + id="path8182" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8734)" + inkscape:connector-curvature="0" + id="path8184" + d="m 250,607.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8730)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,612.36218 5,10 5,-5" + id="path8186" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8726)" + inkscape:connector-curvature="0" + id="path8188" + d="m 250,617.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8722)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,622.36218 5,10 5,-5" + id="path8190" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8718)" + inkscape:connector-curvature="0" + id="path8192" + d="m 250,627.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8714)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,632.36218 5,10 5,-5" + id="path8194" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8710)" + inkscape:connector-curvature="0" + id="path8196" + d="m 250,637.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8706)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,642.36218 5,10 5,-5" + id="path8198" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8702)" + inkscape:connector-curvature="0" + id="path8200" + d="m 250,647.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8698)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,652.36218 5,10 5,-5" + id="path8202" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8694)" + inkscape:connector-curvature="0" + id="path8204" + d="m 250,657.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8690)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,662.36218 5,10 5,-5" + id="path8206" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8686)" + inkscape:connector-curvature="0" + id="path8208" + d="m 250,667.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8682)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,672.36218 5,10 5,-5" + id="path8210" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8678)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,582.36218 5,10 5,-5" + id="path8212" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8674)" + inkscape:connector-curvature="0" + id="path8214" + d="m 265,587.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8670)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,592.36218 5,10 5,-5" + id="path8216" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8666)" + inkscape:connector-curvature="0" + id="path8218" + d="m 265,597.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8662)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,602.36218 5,10 5,-5" + id="path8220" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8658)" + inkscape:connector-curvature="0" + id="path8222" + d="m 265,607.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8654)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,612.36218 5,10 5,-5" + id="path8224" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8650)" + inkscape:connector-curvature="0" + id="path8226" + d="m 265,617.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8646)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,622.36218 5,10 5,-5" + id="path8228" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8642)" + inkscape:connector-curvature="0" + id="path8230" + d="m 265,627.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8638)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,632.36218 5,10 5,-5" + id="path8232" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8634)" + inkscape:connector-curvature="0" + id="path8234" + d="m 265,637.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8630)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,642.36218 5,10 5,-5" + id="path8236" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8626)" + inkscape:connector-curvature="0" + id="path8238" + d="m 265,647.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8622)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,652.36218 5,10 5,-5" + id="path8240" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8618)" + inkscape:connector-curvature="0" + id="path8242" + d="m 265,657.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8614)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,662.36218 5,10 5,-5" + id="path8244" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8610)" + inkscape:connector-curvature="0" + id="path8246" + d="m 265,667.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8606)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,672.36218 5,10 5,-5" + id="path8248" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8602)" + inkscape:connector-curvature="0" + id="path8250" + d="m 280,582.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8598)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,587.36218 5,10 5,-5" + id="path8252" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8594)" + inkscape:connector-curvature="0" + id="path8254" + d="m 280,592.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8590)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,597.36218 5,10 5,-5" + id="path8256" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8586)" + inkscape:connector-curvature="0" + id="path8258" + d="m 280,602.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8582)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,607.36218 5,10 5,-5" + id="path8260" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8578)" + inkscape:connector-curvature="0" + id="path8262" + d="m 280,612.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8574)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,617.36218 5,10 5,-5" + id="path8264" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8570)" + inkscape:connector-curvature="0" + id="path8266" + d="m 280,622.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8566)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,627.36218 5,10 5,-5" + id="path8268" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8562)" + inkscape:connector-curvature="0" + id="path8270" + d="m 280,632.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8558)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,637.36218 5,10 5,-5" + id="path8272" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8554)" + inkscape:connector-curvature="0" + id="path8274" + d="m 280,642.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8550)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,647.36218 5,10 5,-5" + id="path8276" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8546)" + inkscape:connector-curvature="0" + id="path8278" + d="m 280,652.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8542)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,657.36218 5,10 5,-5" + id="path8280" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8538)" + inkscape:connector-curvature="0" + id="path8282" + d="m 280,662.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8534)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,667.36218 5,10 5,-5" + id="path8284" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8530)" + inkscape:connector-curvature="0" + id="path8286" + d="m 280,672.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8526)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,577.36218 5,10 5,-5" + id="path8288" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8522)" + inkscape:connector-curvature="0" + id="path8290" + d="m 190,577.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8518)" + inkscape:connector-curvature="0" + id="path8292" + d="m 205,577.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8514)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,577.36218 5,10 5,-5" + id="path8294" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8510)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,577.36218 5,10 5,-5" + id="path8296" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8506)" + inkscape:connector-curvature="0" + id="path8298" + d="m 250,577.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8502)" + inkscape:connector-curvature="0" + id="path8300" + d="m 265,577.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8498)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,577.36218 5,10 5,-5" + id="path8302" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8494)" + inkscape:connector-curvature="0" + id="path8304" + d="m 175,572.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8490)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,572.36218 5,10 5,-5" + id="path8306" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8486)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,572.36218 5,10 5,-5" + id="path8308" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8482)" + inkscape:connector-curvature="0" + id="path8310" + d="m 220,572.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8478)" + inkscape:connector-curvature="0" + id="path8312" + d="m 235,572.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8474)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,572.36218 5,10 5,-5" + id="path8314" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8470)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,572.36218 5,10 5,-5" + id="path8316" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8466)" + inkscape:connector-curvature="0" + id="path8318" + d="m 280,572.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8462)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,677.36218 5,10 5,-5" + id="path8322" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8458)" + inkscape:connector-curvature="0" + id="path8324" + d="m 190,677.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8454)" + inkscape:connector-curvature="0" + id="path8326" + d="m 205,677.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8450)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,677.36218 5,10 5,-5" + id="path8328" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8446)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,677.36218 5,10 5,-5" + id="path8330" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8442)" + inkscape:connector-curvature="0" + id="path8332" + d="m 250,677.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8438)" + inkscape:connector-curvature="0" + id="path8334" + d="m 265,677.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8434)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,677.36218 5,10 5,-5" + id="path8336" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8430)" + inkscape:connector-curvature="0" + id="path8338" + d="m 175,682.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8426)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 190,682.36218 5,10 5,-5" + id="path8340" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8422)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 205,682.36218 5,10 5,-5" + id="path8342" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8418)" + inkscape:connector-curvature="0" + id="path8344" + d="m 220,682.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8414)" + inkscape:connector-curvature="0" + id="path8346" + d="m 235,682.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8410)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 250,682.36218 5,10 5,-5" + id="path8348" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8406)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 265,682.36218 5,10 5,-5" + id="path8350" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8402)" + inkscape:connector-curvature="0" + id="path8352" + d="m 280,682.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8398)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 175,687.36218 5,10 5,-5" + id="path8354" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8394)" + inkscape:connector-curvature="0" + id="path8356" + d="m 190,687.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8390)" + inkscape:connector-curvature="0" + id="path8358" + d="m 205,687.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8386)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 220,687.36218 5,10 5,-5" + id="path8360" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8382)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 235,687.36218 5,10 5,-5" + id="path8362" + inkscape:connector-curvature="0" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8378)" + inkscape:connector-curvature="0" + id="path8364" + d="m 250,687.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8374)" + inkscape:connector-curvature="0" + id="path8366" + d="m 265,687.36218 5,10 5,-5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + transform="translate(0,5)" + clip-path="url(#clipPath8370)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 280,687.36218 5,10 5,-5" + id="path8368" + inkscape:connector-curvature="0" /> + <rect + y="582.86218" + x="170.5" + height="115" + width="115" + id="rect9138" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <rect + style="fill:url(#simple5) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9140" + width="115" + height="115" + x="435.5" + y="587.86218" /> + <use + x="0" + y="0" + xlink:href="#g9142" + id="use9337" + transform="translate(0,130)" + width="744.09448" + height="1052.3622" /> + <rect + y="717.86218" + x="435.5" + height="115" + width="115" + id="rect9339" + style="fill:url(#simple6) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 570,847.36218 5,10" + id="path9341" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9343" + d="m 570,867.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 570,887.36218 5,10" + id="path9345" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9347" + d="m 570,907.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 570,927.36218 5,10" + id="path9349" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9351" + d="m 570,947.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + inkscape:connector-curvature="0" + id="path9355" + d="m 585,847.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 585,867.36218 5,10" + id="path9357" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9359" + d="m 585,887.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 585,907.36218 5,10" + id="path9361" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9363" + d="m 585,927.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 585,947.36218 5,10" + id="path9365" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 600,847.36218 5,10" + id="path9369" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9371" + d="m 600,867.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 600,887.36218 5,10" + id="path9373" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9375" + d="m 600,907.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 600,927.36218 5,10" + id="path9377" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9379" + d="m 600,947.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + inkscape:connector-curvature="0" + id="path9383" + d="m 615,847.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 615,867.36218 5,10" + id="path9385" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9387" + d="m 615,887.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 615,907.36218 5,10" + id="path9389" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9391" + d="m 615,927.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 615,947.36218 5,10" + id="path9393" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 630,847.36218 5,10" + id="path9397" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9399" + d="m 630,867.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 630,887.36218 5,10" + id="path9401" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9403" + d="m 630,907.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 630,927.36218 5,10" + id="path9405" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9407" + d="m 630,947.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + inkscape:connector-curvature="0" + id="path9411" + d="m 645,847.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 645,867.36218 5,10" + id="path9413" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9415" + d="m 645,887.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 645,907.36218 5,10" + id="path9417" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9419" + d="m 645,927.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 645,947.36218 5,10" + id="path9421" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 660,847.36218 5,10" + id="path9425" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9427" + d="m 660,867.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 660,887.36218 5,10" + id="path9429" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9431" + d="m 660,907.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 660,927.36218 5,10" + id="path9433" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9435" + d="m 660,947.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + inkscape:connector-curvature="0" + id="path9439" + d="m 675,847.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 675,867.36218 5,10" + id="path9441" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9443" + d="m 675,887.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 675,907.36218 5,10" + id="path9445" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path9447" + d="m 675,927.36218 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 675,947.36218 5,10" + id="path9449" + inkscape:connector-curvature="0" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9453" + width="115" + height="115" + x="565.5" + y="847.86218" /> + <rect + style="fill:url(#simple7) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect9455" + width="115" + height="115" + x="435.5" + y="847.86218" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 360,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + id="path9457" + inkscape:connector-curvature="0" + clip-path="url(#clipPath9573)" + transform="translate(1153.5,14.500003)" /> + <path + inkscape:connector-curvature="0" + id="path9459" + d="m 375,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath9569)" + transform="translate(1153.5,14.500003)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 390,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + id="path9461" + inkscape:connector-curvature="0" + clip-path="url(#clipPath9565)" + transform="translate(1153.5,14.500003)" /> + <path + inkscape:connector-curvature="0" + id="path9463" + d="m 405,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath9561)" + transform="translate(1153.5,14.500003)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 420,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + id="path9465" + inkscape:connector-curvature="0" + clip-path="url(#clipPath9557)" + transform="translate(1153.5,14.500003)" /> + <path + inkscape:connector-curvature="0" + id="path9467" + d="m 435,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath9553)" + transform="translate(1153.5,14.500003)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 450,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + id="path9469" + inkscape:connector-curvature="0" + clip-path="url(#clipPath9549)" + transform="translate(1153.5,14.500003)" /> + <path + inkscape:connector-curvature="0" + id="path9471" + d="m 465,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath9545)" + transform="translate(1153.5,14.500003)" /> + <rect + y="77.362183" + x="1509" + height="115" + width="115" + id="rect9577" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + y="77.362183" + x="1378" + height="115" + width="115" + id="rect9579" + style="fill:url(#transform1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 464.08013,373.71824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025" + id="path10332" + inkscape:connector-curvature="0" + inkscape:transform-center-x="25.91987" + inkscape:transform-center-y="-54.894536" + clip-path="url(#clipPath10442)" + transform="translate(988,-59.999997)" /> + <path + inkscape:transform-center-y="-54.894536" + inkscape:transform-center-x="25.919871" + inkscape:connector-curvature="0" + id="path10352" + d="m 477.07051,381.21824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10438)" + transform="translate(988,-59.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 490.06089,388.71824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025" + id="path10354" + inkscape:connector-curvature="0" + inkscape:transform-center-x="25.919872" + inkscape:transform-center-y="-54.894536" + clip-path="url(#clipPath10434)" + transform="translate(988,-59.999997)" /> + <path + inkscape:transform-center-y="-54.894536" + inkscape:transform-center-x="25.919873" + inkscape:connector-curvature="0" + id="path10356" + d="m 503.05127,396.21824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10430)" + transform="translate(988,-59.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 516.04165,403.71824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025" + id="path10358" + inkscape:connector-curvature="0" + inkscape:transform-center-x="25.919874" + inkscape:transform-center-y="-54.894536" + clip-path="url(#clipPath10426)" + transform="translate(988,-59.999997)" /> + <path + inkscape:transform-center-y="-54.894536" + inkscape:transform-center-x="25.919875" + inkscape:connector-curvature="0" + id="path10360" + d="m 529.03203,411.21824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10422)" + transform="translate(988,-59.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 542.02241,418.71824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025" + id="path10362" + inkscape:connector-curvature="0" + inkscape:transform-center-x="25.919876" + inkscape:transform-center-y="-54.894536" + clip-path="url(#clipPath10418)" + transform="translate(988,-59.999997)" /> + <path + inkscape:transform-center-y="-54.894536" + inkscape:transform-center-x="25.919877" + inkscape:connector-curvature="0" + id="path10364" + d="m 555.01279,426.21824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10414)" + transform="translate(988,-59.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 568.00318,433.71824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025" + id="path10366" + inkscape:connector-curvature="0" + inkscape:transform-center-x="25.919868" + inkscape:transform-center-y="-54.894536" + clip-path="url(#clipPath10410)" + transform="translate(988,-59.999997)" /> + <path + inkscape:transform-center-y="-54.894536" + inkscape:transform-center-x="25.919869" + inkscape:connector-curvature="0" + id="path10368" + d="m 580.99356,441.21824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10406)" + transform="translate(988,-59.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 593.98394,448.71824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025" + id="path10370" + inkscape:connector-curvature="0" + inkscape:transform-center-x="25.91987" + inkscape:transform-center-y="-54.894536" + clip-path="url(#clipPath10402)" + transform="translate(988,-59.999997)" /> + <path + inkscape:transform-center-y="-8.6602474" + inkscape:transform-center-x="5" + inkscape:connector-curvature="0" + id="path10376" + d="m 475,797.62497 9.33013,-6.16025 L 485,780.30447 494.33013,774.14421 495,762.98396 504.33013,756.8237 505,745.66345 514.33013,739.5032 515,728.34294 524.33013,722.18269 525,711.02243 534.33013,704.86218 535,693.70193 544.33013,687.54167 545,676.38142 554.33013,670.22116 555,659.06091 564.33013,652.90066 565,641.7404 574.33013,635.58015 575,624.41989" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10669)" + transform="translate(978,-105)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 487.99038,805.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + id="path10378" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-7.99038" + inkscape:transform-center-y="-16.160247" + clip-path="url(#clipPath10665)" + transform="translate(978,-105)" /> + <path + inkscape:transform-center-y="-23.660247" + inkscape:transform-center-x="-20.98076" + inkscape:connector-curvature="0" + id="path10380" + d="m 500.98076,812.62497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10661)" + transform="translate(978,-105)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 513.97114,820.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + id="path10382" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-33.97114" + inkscape:transform-center-y="-31.160247" + clip-path="url(#clipPath10657)" + transform="translate(978,-105)" /> + <path + inkscape:transform-center-y="-38.660247" + inkscape:transform-center-x="-46.96153" + inkscape:connector-curvature="0" + id="path10384" + d="m 526.96153,827.62497 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10653)" + transform="translate(978,-105)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 539.95191,835.12497 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026" + id="path10386" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-59.95191" + inkscape:transform-center-y="-46.160247" + clip-path="url(#clipPath10649)" + transform="translate(978,-105)" /> + <path + inkscape:transform-center-y="-53.660247" + inkscape:transform-center-x="-72.94229" + inkscape:connector-curvature="0" + id="path10388" + d="m 552.94229,842.62497 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10645)" + transform="translate(978,-105)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 565.93267,850.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + id="path10390" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-85.93267" + inkscape:transform-center-y="-61.160247" + clip-path="url(#clipPath10641)" + transform="translate(978,-105)" /> + <path + inkscape:transform-center-y="-68.660247" + inkscape:transform-center-x="-98.92305" + inkscape:connector-curvature="0" + id="path10392" + d="m 578.92305,857.62497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10637)" + transform="translate(978,-105)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 591.91343,865.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + id="path10394" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-111.91343" + inkscape:transform-center-y="-76.160247" + clip-path="url(#clipPath10633)" + transform="translate(978,-105)" /> + <path + inkscape:transform-center-y="-83.660247" + inkscape:transform-center-x="-124.90381" + inkscape:connector-curvature="0" + id="path10396" + d="m 604.90381,872.62497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10629)" + transform="translate(978,-105)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 617.89419,880.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026" + id="path10398" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-137.89419" + inkscape:transform-center-y="-91.160247" + clip-path="url(#clipPath10625)" + transform="translate(978,-105)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10446" + width="115" + height="115" + x="1508" + y="207.36218" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 438.68272,550.75053 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066" + id="path10452" + inkscape:connector-curvature="0" + inkscape:transform-center-x="10.6066" + inkscape:transform-center-y="-17.677667" + clip-path="url(#clipPath10520)" + transform="translate(988,-125)" /> + <path + inkscape:transform-center-y="-28.284267" + inkscape:connector-curvature="0" + id="path10454" + d="m 449.28932,561.35713 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10516)" + transform="translate(988,-125)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 459.89593,571.96373 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066" + id="path10456" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-10.606605" + inkscape:transform-center-y="-38.890872" + clip-path="url(#clipPath10512)" + transform="translate(988,-125)" /> + <path + inkscape:transform-center-y="-49.497472" + inkscape:transform-center-x="-21.213205" + inkscape:connector-curvature="0" + id="path10458" + d="m 470.50253,582.57033 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10508)" + transform="translate(988,-125)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 481.10913,593.17694 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066" + id="path10460" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-31.81981" + inkscape:transform-center-y="-60.104077" + clip-path="url(#clipPath10504)" + transform="translate(988,-125)" /> + <path + inkscape:transform-center-y="-70.710677" + inkscape:transform-center-x="-42.42641" + inkscape:connector-curvature="0" + id="path10462" + d="m 491.71573,603.78354 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10500)" + transform="translate(988,-125)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 502.32233,614.39014 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066" + id="path10464" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-53.03301" + inkscape:transform-center-y="-81.317277" + clip-path="url(#clipPath10496)" + transform="translate(988,-125)" /> + <path + inkscape:transform-center-y="-91.923877" + inkscape:transform-center-x="-63.63961" + inkscape:connector-curvature="0" + id="path10466" + d="m 512.92893,624.99674 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10492)" + transform="translate(988,-125)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 523.53554,635.60334 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066" + id="path10468" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-74.246215" + inkscape:transform-center-y="-102.53048" + clip-path="url(#clipPath10488)" + transform="translate(988,-125)" /> + <path + inkscape:transform-center-y="-113.13708" + inkscape:transform-center-x="-84.852815" + inkscape:connector-curvature="0" + id="path10470" + d="m 534.14214,646.20994 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10484)" + transform="translate(988,-125)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 544.74874,656.81655 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066" + id="path10472" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-95.45942" + inkscape:transform-center-y="-123.74369" + clip-path="url(#clipPath10480)" + transform="translate(988,-125)" /> + <path + inkscape:transform-center-y="-134.35029" + inkscape:transform-center-x="-106.06602" + inkscape:connector-curvature="0" + id="path10474" + d="m 555.35534,667.42315 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10476)" + transform="translate(988,-125)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10524" + width="115" + height="115" + x="1508" + y="337.36218" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 505,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + id="path10528" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-7.815239" + inkscape:transform-center-y="-60.5" + clip-path="url(#clipPath10582)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <path + inkscape:transform-center-y="-60.5" + inkscape:transform-center-x="-7.8261091" + inkscape:connector-curvature="0" + id="path10530" + d="m 520,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10578)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 535,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + id="path10532" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-7.8261097" + inkscape:transform-center-y="-60.5" + clip-path="url(#clipPath10574)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <path + inkscape:transform-center-y="-60.5" + inkscape:transform-center-x="-7.8261103" + inkscape:connector-curvature="0" + id="path10534" + d="m 550,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10570)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 565,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + id="path10536" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-7.8261109" + inkscape:transform-center-y="-60.5" + clip-path="url(#clipPath10566)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <path + inkscape:transform-center-y="-60.5" + inkscape:transform-center-x="-7.8261115" + inkscape:connector-curvature="0" + id="path10538" + d="m 580,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10562)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 595,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + id="path10540" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-7.8261121" + inkscape:transform-center-y="-60.5" + clip-path="url(#clipPath10558)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <path + inkscape:transform-center-y="-60.5" + inkscape:transform-center-x="-7.8261127" + inkscape:connector-curvature="0" + id="path10542" + d="m 610,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10" + style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10554)" + transform="matrix(1.0434783,0,0,1,981.04348,-110)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10586" + width="120.00001" + height="115" + x="1508" + y="467.36218" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1380" + y="892.36218" + id="text10615"><tspan + sodipodi:role="line" + id="tspan10617" + x="1380" + y="892.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">Since hatchUnits="userSpaceOnUse" is used</tspan><tspan + sodipodi:role="line" + x="1380" + y="905.69556" + id="tspan10619" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">the rendering will match when hatched shape</tspan><tspan + sodipodi:role="line" + x="1380" + y="919.02893" + id="tspan10621" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">is moved to the point 0,0</tspan></text> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10673" + width="115" + height="115" + x="1508" + y="597.36218" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 503.77134,889.56737 7.41782,-8.36516 -2.24143,-10.95335 7.41781,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335" + id="path10731" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-0.773965" + inkscape:transform-center-y="-20.612607" + clip-path="url(#clipPath10801)" + transform="translate(973,-34.999997)" /> + <path + inkscape:transform-center-y="-24.494892" + inkscape:transform-center-x="-15.262855" + inkscape:connector-curvature="0" + id="path10733" + d="m 518.26023,893.44966 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10797)" + transform="translate(973,-34.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 532.74912,897.33195 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335" + id="path10735" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-29.751745" + inkscape:transform-center-y="-28.377182" + clip-path="url(#clipPath10793)" + transform="translate(973,-34.999997)" /> + <path + inkscape:transform-center-y="-32.259467" + inkscape:transform-center-x="-44.24063" + inkscape:connector-curvature="0" + id="path10737" + d="m 547.23801,901.21423 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24143,-10.95335" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10789)" + transform="translate(973,-34.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 561.72689,905.09652 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336" + id="path10739" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-58.729515" + inkscape:transform-center-y="-36.141752" + clip-path="url(#clipPath10785)" + transform="translate(973,-34.999997)" /> + <path + inkscape:transform-center-y="-40.024037" + inkscape:transform-center-x="-73.218405" + inkscape:connector-curvature="0" + id="path10741" + d="m 576.21578,908.9788 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24143,-10.95335 7.41781,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10781)" + transform="translate(973,-34.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 590.70467,912.86109 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41781,-8.36516 -2.24143,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336" + id="path10743" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-87.707295" + inkscape:transform-center-y="-43.906322" + clip-path="url(#clipPath10777)" + transform="translate(973,-34.999997)" /> + <path + inkscape:transform-center-y="-47.788607" + inkscape:transform-center-x="-102.19618" + inkscape:connector-curvature="0" + id="path10745" + d="m 605.19356,916.74337 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24143,-10.95335" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10773)" + transform="translate(973,-34.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 619.68244,920.62566 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24143,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336" + id="path10747" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-116.68507" + inkscape:transform-center-y="-51.670892" + clip-path="url(#clipPath10769)" + transform="translate(973,-34.999997)" /> + <path + inkscape:transform-center-y="-55.553182" + inkscape:transform-center-x="-131.17395" + inkscape:connector-curvature="0" + id="path10749" + d="m 634.17133,924.50795 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10765)" + transform="translate(973,-34.999997)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="m 648.66022,928.39023 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335" + id="path10751" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-145.66284" + inkscape:transform-center-y="-59.435467" + clip-path="url(#clipPath10761)" + transform="translate(973,-34.999997)" /> + <path + inkscape:transform-center-y="-63.317752" + inkscape:transform-center-x="-160.15173" + inkscape:connector-curvature="0" + id="path10753" + d="m 663.14911,932.27252 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + clip-path="url(#clipPath10757)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10805" + width="115" + height="115" + x="1508" + y="727.36218" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 2380.5,82.862185 V 197.86218" + id="path10807" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="M 2385.5,82.862185 V 197.86218" + id="path10809" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path10811" + d="M 2395.5,82.862185 V 197.86218" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path10813" + d="M 2400.5,82.862185 V 197.86218" + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 2410.5,82.862185 V 197.86218" + id="path10815" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="M 2415.5,82.862185 V 197.86218" + id="path10817" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path10819" + d="M 2425.5,82.862185 V 197.86218" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path10821" + d="M 2430.5,82.862185 V 197.86218" + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 2440.5,82.862185 V 197.86218" + id="path10823" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="M 2445.5,82.862185 V 197.86218" + id="path10825" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path10827" + d="M 2455.5,82.862185 V 197.86218" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path10829" + d="M 2460.5,82.862185 V 197.86218" + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="M 2470.5,82.862185 V 197.86218" + id="path10831" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" + d="M 2475.5,82.862185 V 197.86218" + id="path10833" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path10835" + d="M 2485.5,82.862185 V 197.86218" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path10837" + d="M 2490.5,82.862185 V 197.86218" + style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10839" + width="115" + height="115" + x="2375.5" + y="82.862183" /> + <rect + style="fill:url(#multiple1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect10841" + width="115" + height="115" + x="2245" + y="82.362183" /> + <path + inkscape:connector-curvature="0" + id="path10843" + d="m 1010,212.36218 v 115" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath10933)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path10845" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10929)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1025,212.36218 v 115" + id="path10847" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10925)" + transform="translate(1370.5,0.5)" /> + <path + inkscape:connector-curvature="0" + id="path10849" + d="m 1030,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath10921)" + transform="translate(1370.5,0.5)" /> + <path + inkscape:connector-curvature="0" + id="path10851" + d="m 1040,212.36218 v 115" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath10917)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1045,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path10853" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10913)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1055,212.36218 v 115" + id="path10855" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10909)" + transform="translate(1370.5,0.5)" /> + <path + inkscape:connector-curvature="0" + id="path10857" + d="m 1060,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath10905)" + transform="translate(1370.5,0.5)" /> + <path + inkscape:connector-curvature="0" + id="path10859" + d="m 1070,212.36218 v 115" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath10901)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1075,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path10861" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10897)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1085,212.36218 v 115" + id="path10863" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10893)" + transform="translate(1370.5,0.5)" /> + <path + inkscape:connector-curvature="0" + id="path10865" + d="m 1090,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath10889)" + transform="translate(1370.5,0.5)" /> + <path + inkscape:connector-curvature="0" + id="path10867" + d="m 1100,212.36218 v 115" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath10885)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1105,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path10869" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10881)" + transform="translate(1370.5,0.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1115,212.36218 v 115" + id="path10871" + inkscape:connector-curvature="0" + clip-path="url(#clipPath10877)" + transform="translate(1370.5,0.5)" /> + <rect + y="212.86218" + x="2375.5" + height="115" + width="115" + id="rect10937" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + clip-path="url(#clipPath11091)" + inkscape:connector-curvature="0" + id="path11001" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + transform="translate(1370.5,130.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1010,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + id="path11003" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11087)" + transform="translate(1370.5,0.5)" /> + <path + transform="translate(1385.5,130.5)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path11005" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11083)" /> + <path + inkscape:connector-curvature="0" + id="path11007" + d="m 1025,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11079)" + transform="translate(1370.5,0.5)" /> + <path + clip-path="url(#clipPath11075)" + inkscape:connector-curvature="0" + id="path11009" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + transform="translate(1400.5,130.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1040,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + id="path11011" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11071)" + transform="translate(1370.5,0.5)" /> + <path + transform="translate(1415.5,130.5)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path11013" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11067)" /> + <path + inkscape:connector-curvature="0" + id="path11015" + d="m 1055,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11063)" + transform="translate(1370.5,0.5)" /> + <path + clip-path="url(#clipPath11059)" + inkscape:connector-curvature="0" + id="path11017" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + transform="translate(1430.5,130.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1070,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + id="path11019" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11055)" + transform="translate(1370.5,0.5)" /> + <path + transform="translate(1445.5,130.5)" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + id="path11021" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11051)" /> + <path + inkscape:connector-curvature="0" + id="path11023" + d="m 1085,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11047)" + transform="translate(1370.5,0.5)" /> + <path + clip-path="url(#clipPath11043)" + inkscape:connector-curvature="0" + id="path11025" + d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + transform="translate(1460.5,130.5)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1100,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + id="path11027" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11039)" + transform="translate(1370.5,0.5)" /> + <path + inkscape:connector-curvature="0" + id="path11031" + d="m 1115,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11035)" + transform="translate(1370.5,0.5)" /> + <rect + y="342.86218" + x="2375.5" + height="115" + width="115" + id="rect11095" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" + d="m 570,1132.3622 v 115" + id="path11097" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11099" + d="m 585,1132.3622 v 115" + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" + d="m 600,1132.3622 v 115" + id="path11101" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11103" + d="m 615,1132.3622 v 115" + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" + d="m 630,1132.3622 v 115" + id="path11105" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11107" + d="m 645,1132.3622 v 115" + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" + d="m 660,1132.3622 v 115" + id="path11109" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11111" + d="m 675,1132.3622 v 115" + style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" /> + <rect + y="1132.3622" + x="565.00018" + height="115" + width="115" + id="rect11113" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1510.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11115" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11117" + d="m 1525.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1540.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11119" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11121" + d="m 1555.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1570.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11123" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11125" + d="m 1585.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1600.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11127" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11129" + d="m 1615.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11133" + width="115" + height="115" + x="1505.5002" + y="1132.8622" /> + <path + inkscape:connector-curvature="0" + id="path11135" + d="m 1485,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11183)" + transform="translate(19.5,1175)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1500,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11137" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11179)" + transform="translate(19.5,1175)" /> + <path + inkscape:connector-curvature="0" + id="path11139" + d="m 1515,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11175)" + transform="translate(19.5,1175)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1530,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11141" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11171)" + transform="translate(19.5,1175)" /> + <path + inkscape:connector-curvature="0" + id="path11143" + d="m 1545,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11167)" + transform="translate(19.5,1175)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1560,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11145" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11163)" + transform="translate(19.5,1175)" /> + <path + inkscape:connector-curvature="0" + id="path11147" + d="m 1575,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + clip-path="url(#clipPath11159)" + transform="translate(19.5,1175)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1590,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10" + id="path11149" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11155)" + transform="translate(19.5,1175)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11187" + width="115" + height="115" + x="1504.5002" + y="1392.3622" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1505.5,1262.8622 5,5 -5,5" + id="path11189" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11191" + d="m 1505.5,1282.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1505.5,1302.8622 5,5 -5,5" + id="path11193" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11195" + d="m 1505.5,1322.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1505.5,1342.8622 5,5 -5,5" + id="path11197" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11199" + d="m 1505.5,1362.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path11203" + d="m 1520.5,1262.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1520.5,1282.8622 5,5 -5,5" + id="path11205" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11207" + d="m 1520.5,1302.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1520.5,1322.8622 5,5 -5,5" + id="path11209" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11211" + d="m 1520.5,1342.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1520.5,1362.8622 5,5 -5,5" + id="path11213" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1535.5,1262.8622 5,5 -5,5" + id="path11217" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11219" + d="m 1535.5,1282.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1535.5,1302.8622 5,5 -5,5" + id="path11221" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11223" + d="m 1535.5,1322.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1535.5,1342.8622 5,5 -5,5" + id="path11225" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11227" + d="m 1535.5,1362.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path11231" + d="m 1550.5,1262.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1550.5,1282.8622 5,5 -5,5" + id="path11233" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11235" + d="m 1550.5,1302.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1550.5,1322.8622 5,5 -5,5" + id="path11237" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11239" + d="m 1550.5,1342.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1550.5,1362.8622 5,5 -5,5" + id="path11241" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1565.5,1262.8622 5,5 -5,5" + id="path11245" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11247" + d="m 1565.5,1282.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1565.5,1302.8622 5,5 -5,5" + id="path11249" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11251" + d="m 1565.5,1322.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1565.5,1342.8622 5,5 -5,5" + id="path11253" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11255" + d="m 1565.5,1362.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path11259" + d="m 1580.5,1262.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1580.5,1282.8622 5,5 -5,5" + id="path11261" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11263" + d="m 1580.5,1302.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1580.5,1322.8622 5,5 -5,5" + id="path11265" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11267" + d="m 1580.5,1342.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1580.5,1362.8622 5,5 -5,5" + id="path11269" + inkscape:connector-curvature="0" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1595.5,1262.8622 5,5 -5,5" + id="path11273" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11275" + d="m 1595.5,1282.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1595.5,1302.8622 5,5 -5,5" + id="path11277" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11279" + d="m 1595.5,1322.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1595.5,1342.8622 5,5 -5,5" + id="path11281" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11283" + d="m 1595.5,1362.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path11287" + d="m 1610.5,1262.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1610.5,1282.8622 5,5 -5,5" + id="path11289" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11291" + d="m 1610.5,1302.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1610.5,1322.8622 5,5 -5,5" + id="path11293" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path11295" + d="m 1610.5,1342.8622 5,5 -5,5" + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 1610.5,1362.8622 5,5 -5,5" + id="path11297" + inkscape:connector-curvature="0" /> + <rect + y="1262.8622" + x="1505.5002" + height="115" + width="115" + id="rect11315" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <g + id="g11321" + transform="translate(2194,540)"> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4366)" + inkscape:connector-curvature="0" + id="path11323" + d="M 180,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4362)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195,42.362183 V 192.36218" + id="path11325" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4358)" + inkscape:connector-curvature="0" + id="path11327" + d="M 210,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4354)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 225,42.362183 V 192.36218" + id="path11329" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4350)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 240,42.362183 V 192.36218" + id="path11331" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4346)" + inkscape:connector-curvature="0" + id="path11333" + d="M 255,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4342)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 270,42.362183 V 192.36218" + id="path11335" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4338)" + inkscape:connector-curvature="0" + id="path11337" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="62.362183" + x="181" + height="115" + width="115" + id="rect11339" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <g + transform="translate(2194,540)" + id="g11341"> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 180,42.362183 V 192.36218" + id="path11343" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4366)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11345" + d="M 195,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4362)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 210,42.362183 V 192.36218" + id="path11347" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4358)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11349" + d="M 225,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4354)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11351" + d="M 240,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4350)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 255,42.362183 V 192.36218" + id="path11353" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4346)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11355" + d="M 270,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4342)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 285,42.362183 V 192.36218" + id="path11357" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4338)" + transform="translate(6,20)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11359" + width="115" + height="115" + x="181" + y="62.362183" /> + </g> + <g + transform="translate(2194,670)" + id="g11361"> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 180,42.362183 V 192.36218" + id="path11363" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4366)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11365" + d="M 195,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4362)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 210,42.362183 V 192.36218" + id="path11367" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4358)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11369" + d="M 225,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4354)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11371" + d="M 240,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4350)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 255,42.362183 V 192.36218" + id="path11373" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4346)" + transform="translate(6,20)" /> + <path + inkscape:connector-curvature="0" + id="path11375" + d="M 270,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath4342)" + transform="translate(6,20)" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 285,42.362183 V 192.36218" + id="path11377" + inkscape:connector-curvature="0" + clip-path="url(#clipPath4338)" + transform="translate(6,20)" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11379" + width="115" + height="115" + x="181" + y="62.362183" /> + </g> + <g + id="g11381" + transform="translate(2194,670)"> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4366)" + inkscape:connector-curvature="0" + id="path11383" + d="M 180,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4362)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195,42.362183 V 192.36218" + id="path11385" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4358)" + inkscape:connector-curvature="0" + id="path11387" + d="M 210,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4354)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 225,42.362183 V 192.36218" + id="path11389" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4350)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 240,42.362183 V 192.36218" + id="path11391" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4346)" + inkscape:connector-curvature="0" + id="path11393" + d="M 255,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4342)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 270,42.362183 V 192.36218" + id="path11395" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4338)" + inkscape:connector-curvature="0" + id="path11397" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="62.362183" + x="181" + height="115" + width="115" + id="rect11399" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + inkscape:connector-curvature="0" + id="path11409" + d="M 225,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath11528)" + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)" + inkscape:transform-center-x="28.034277" + inkscape:transform-center-y="-38.122284" /> + <path + inkscape:connector-curvature="0" + id="path11411" + d="M 240,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath11524)" + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)" + inkscape:transform-center-x="20.444614" + inkscape:transform-center-y="-45.711947" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 255,42.362183 V 192.36218" + id="path11413" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11520)" + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)" + inkscape:transform-center-x="9.8380123" + inkscape:transform-center-y="-51.015248" /> + <path + inkscape:connector-curvature="0" + id="path11415" + d="M 270,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + clip-path="url(#clipPath11516)" + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)" + inkscape:transform-center-x="-0.76858941" + inkscape:transform-center-y="-56.318549" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 285,42.362183 V 192.36218" + id="path11417" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11512)" + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)" + inkscape:transform-center-x="-11.375191" + inkscape:transform-center-y="-61.62185" /> + <path + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2343.0663,605.3821)" + clip-path="url(#clipPath11508)" + inkscape:connector-curvature="0" + id="path11450" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:transform-center-x="-21.981791" + inkscape:transform-center-y="-72.22845" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 285,42.362183 V 192.36218" + id="path11452" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11504)" + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2353.6729,615.9887)" + inkscape:transform-center-x="-32.588391" + inkscape:transform-center-y="-82.83505" /> + <path + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2364.2795,626.5953)" + clip-path="url(#clipPath11500)" + inkscape:connector-curvature="0" + id="path11454" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:transform-center-x="-43.194991" + inkscape:transform-center-y="-93.44165" /> + <path + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 285,42.362183 V 192.36218" + id="path11456" + inkscape:connector-curvature="0" + clip-path="url(#clipPath11496)" + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2374.8861,637.2019)" + inkscape:transform-center-x="-53.801591" + inkscape:transform-center-y="-104.04825" /> + <path + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2385.4927,647.8085)" + clip-path="url(#clipPath11492)" + inkscape:connector-curvature="0" + id="path11458" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:transform-center-x="-64.408191" + inkscape:transform-center-y="-114.65485" /> + <path + transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2396.0993,658.41511)" + clip-path="url(#clipPath11488)" + inkscape:connector-curvature="0" + id="path11462" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + inkscape:transform-center-x="-75.014791" + inkscape:transform-center-y="-125.26146" /> + <rect + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11532" + width="115" + height="115" + x="2375.5" + y="862.86218" /> + <rect + y="1132.3622" + x="2370" + height="115" + width="115" + id="rect11534" + style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11536" + width="115" + height="115" + x="2370" + y="1262.3622" /> + <rect + y="1392.3622" + x="2370" + height="115" + width="115" + id="rect11538" + style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11540" + width="115" + height="115" + x="2370" + y="1522.3622" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="52.480652" + y="112.36218" + id="text11542"><tspan + sodipodi:role="line" + id="tspan11544" + x="52.480652" + y="112.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="simple1" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="125.69556" + id="tspan11546" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" stroke-width="2"/></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="139.02893" + id="tspan11548" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="52.480652" + y="242.36218" + id="text11550"><tspan + sodipodi:role="line" + id="tspan11552" + x="52.480652" + y="242.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="simple2" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="255.69556" + id="tspan11554" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="15"/></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="269.02893" + id="tspan11556" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="52.480652" + y="372.36218" + id="text11558"><tspan + sodipodi:role="line" + id="tspan11560" + x="52.480652" + y="372.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="simple3" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="385.69556" + id="tspan11562" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="M 0,0 5,10"/></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="399.02893" + id="tspan11564" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="52.480652" + y="502.36218" + id="text11566"><tspan + sodipodi:role="line" + id="tspan11568" + x="52.480652" + y="502.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="simple4" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="515.69556" + id="tspan11570" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,10"/></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="529.02893" + id="tspan11572" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="52.480652" + y="632.36218" + id="text11574"><tspan + sodipodi:role="line" + id="tspan11576" + x="52.480652" + y="632.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="simple5" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="645.69556" + id="tspan11578" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="M 0,0 5,10 10,5"/></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="659.02893" + id="tspan11580" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="52.480652" + y="762.36218" + id="text11582"><tspan + sodipodi:role="line" + id="tspan11584" + x="52.480652" + y="762.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="simple6" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="775.69556" + id="tspan11586" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="m 0,0 5,10 5,-5"/></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="789.02893" + id="tspan11588" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="52.480652" + y="892.36218" + id="text11590"><tspan + sodipodi:role="line" + id="tspan11592" + x="52.480652" + y="892.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="simple7" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="905.69556" + id="tspan11594" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="M 0,0 5,10 M 5,20"/></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="919.02893" + id="tspan11596" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan><tspan + sodipodi:role="line" + x="52.480652" + y="932.3623" + id="tspan11598" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text> + <g + id="g11600" + transform="translate(1324.4998,1460.5)"> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4366)" + inkscape:connector-curvature="0" + id="path11602" + d="M 180,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4362)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195,42.362183 V 192.36218" + id="path11604" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4358)" + inkscape:connector-curvature="0" + id="path11606" + d="M 210,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4354)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 225,42.362183 V 192.36218" + id="path11608" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4350)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 240,42.362183 V 192.36218" + id="path11610" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4346)" + inkscape:connector-curvature="0" + id="path11612" + d="M 255,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4342)" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 270,42.362183 V 192.36218" + id="path11614" + inkscape:connector-curvature="0" /> + <path + transform="translate(6,20)" + clip-path="url(#clipPath4338)" + inkscape:connector-curvature="0" + id="path11616" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="62.362183" + x="181" + height="115" + width="115" + id="rect11618" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + </g> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4366)" + inkscape:connector-curvature="0" + id="path11622" + d="M 180,42.362183 V 192.36218" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4362)" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 195,42.362183 V 192.36218" + id="path11624" + inkscape:connector-curvature="0" /> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4358)" + inkscape:connector-curvature="0" + id="path11626" + d="M 210,42.362183 V 192.36218" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4354)" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 225,42.362183 V 192.36218" + id="path11628" + inkscape:connector-curvature="0" /> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4350)" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 240,42.362183 V 192.36218" + id="path11630" + inkscape:connector-curvature="0" /> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4346)" + inkscape:connector-curvature="0" + id="path11632" + d="M 255,42.362183 V 192.36218" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4342)" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + d="M 270,42.362183 V 192.36218" + id="path11634" + inkscape:connector-curvature="0" /> + <path + transform="translate(1330.4998,1480.5)" + clip-path="url(#clipPath4338)" + inkscape:connector-curvature="0" + id="path11636" + d="M 285,42.362183 V 192.36218" + style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <rect + y="1522.8622" + x="1505.5" + height="115" + width="115" + id="rect11638" + style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#transform2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11649" + width="115" + height="115" + x="1378" + y="207.36218" /> + <rect + y="337.36218" + x="1378" + height="115" + width="115" + id="rect11651" + style="fill:url(#transform4) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#transform7) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11653" + width="115" + height="115" + x="1378" + y="467.36218" /> + <rect + y="597.36218" + x="1378" + height="115" + width="115" + id="rect11655" + style="fill:url(#transform8) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#transform9) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11657" + width="115" + height="115" + x="1378" + y="727.36218" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="820" + y="112.36218" + id="text11659"><tspan + sodipodi:role="line" + id="tspan11661" + x="820" + y="112.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="transform1" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="820" + y="125.69556" + id="tspan11663" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,10 0,20"/></tspan><tspan + sodipodi:role="line" + x="820" + y="139.02893" + id="tspan11665" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="820" + y="240.36218" + id="text11667"><tspan + sodipodi:role="line" + id="tspan11669" + x="820" + y="240.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="transform2" hatchUnits="userSpaceOnUse" pitch="15" rotate="30"></tspan><tspan + sodipodi:role="line" + x="820" + y="253.69556" + id="tspan11671" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></tspan><tspan + sodipodi:role="line" + x="820" + y="267.02893" + id="tspan11673" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan><tspan + sodipodi:role="line" + x="820" + y="280.3623" + id="tspan11675" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="820" + y="368.36218" + id="text11677"><tspan + sodipodi:role="line" + id="tspan11679" + x="820" + y="368.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="transform4" hatchUnits="userSpaceOnUse" pitch="15" rotate="45"></tspan><tspan + sodipodi:role="line" + x="820" + y="381.69556" + id="tspan11681" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></tspan><tspan + sodipodi:role="line" + x="820" + y="395.02893" + id="tspan11683" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="820" + y="496.36218" + id="text11685"><tspan + sodipodi:role="line" + id="tspan11687" + x="820" + y="496.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="transform7" hatchUnits="userSpaceOnUse" pitch="15" x="-5" y="-10"></tspan><tspan + sodipodi:role="line" + x="820" + y="509.69556" + id="tspan11689" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></tspan><tspan + sodipodi:role="line" + x="820" + y="523.02893" + id="tspan11691" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="820" + y="624.36218" + id="text11693"><tspan + sodipodi:role="line" + id="tspan11695" + x="820" + y="624.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="transform8" hatchUnits="userSpaceOnUse" pitch="15" x="-5" y="-10" rotate="30"></tspan><tspan + sodipodi:role="line" + x="820" + y="637.69556" + id="tspan11697" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></tspan><tspan + sodipodi:role="line" + x="820" + y="651.02893" + id="tspan11699" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="820" + y="752.36218" + id="text11701"><tspan + sodipodi:role="line" + x="820" + y="752.36218" + id="tspan11707" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="transform9" hatchUnits="userSpaceOnUse" pitch="15" rotate="30" x="-5" y="-10" </tspan><tspan + sodipodi:role="line" + x="820" + y="765.69556" + id="tspan11718" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">hatchTransform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-8.4757068,43.273395)"></tspan><tspan + sodipodi:role="line" + x="820" + y="779.02893" + id="tspan11714" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,20"/></tspan><tspan + sodipodi:role="line" + x="820" + y="792.3623" + id="tspan11716" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <rect + y="212.36218" + x="2245" + height="115" + width="115" + id="rect11720" + style="fill:url(#multiple2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#multiple3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11722" + width="115" + height="115" + x="2245" + y="342.36218" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1804" + y="120.46635" + id="text11724"><tspan + sodipodi:role="line" + id="tspan11726" + x="1804" + y="120.46635" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="multiple1" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="1804" + y="133.79973" + id="tspan11728" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5"/></tspan><tspan + sodipodi:role="line" + x="1804" + y="147.1331" + id="tspan11730" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#32ff3f" offset="10"/></tspan><tspan + sodipodi:role="line" + x="1804" + y="160.46648" + id="tspan11732" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1804" + y="250.46635" + id="text11734"><tspan + sodipodi:role="line" + id="tspan11736" + x="1804" + y="250.46635" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="multiple2" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="1804" + y="263.79974" + id="tspan11738" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5"/></tspan><tspan + sodipodi:role="line" + x="1804" + y="277.13312" + id="tspan11740" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10"/></tspan><tspan + sodipodi:role="line" + x="1804" + y="290.46649" + id="tspan11742" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1814" + y="380.46634" + id="text11744"><tspan + sodipodi:role="line" + id="tspan11746" + x="1814" + y="380.46634" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="multiple3" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="1814" + y="393.79971" + id="tspan11748" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,17" /></tspan><tspan + sodipodi:role="line" + x="1814" + y="407.13309" + id="tspan11750" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10"/></tspan><tspan + sodipodi:role="line" + x="1814" + y="420.46646" + id="tspan11752" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <rect + y="602.36218" + x="2245" + height="115" + width="115" + id="rect11754" + style="fill:url(#ref1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#ref2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11756" + width="115" + height="115" + x="2245" + y="732.36218" /> + <rect + y="862.36218" + x="2245" + height="115" + width="115" + id="rect11758" + style="fill:url(#ref3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1834" + y="640.46637" + id="text11760"><tspan + sodipodi:role="line" + id="tspan11762" + x="1834" + y="640.46637" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="ref1" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="1834" + y="653.79974" + id="tspan11764" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5"/></tspan><tspan + sodipodi:role="line" + x="1834" + y="667.13312" + id="tspan11766" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1834" + y="752.36218" + id="text11768"><tspan + sodipodi:role="line" + id="tspan11770" + x="1834" + y="752.36218" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="ref2" xlink:href="#ref1"></tspan><tspan + sodipodi:role="line" + x="1834" + y="765.69556" + id="tspan11772" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1832.8698" + y="900.46637" + id="text11774"><tspan + sodipodi:role="line" + id="tspan11776" + x="1832.8698" + y="900.46637" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="ref3" xlink:href="#ref1" pitch="45"></tspan><tspan + sodipodi:role="line" + x="1832.8698" + y="913.79974" + id="tspan11778" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <rect + y="1132.3622" + x="435" + height="115" + width="115" + id="rect11780" + style="fill:url(#stroke1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="60" + y="1172.3622" + id="text11782"><tspan + sodipodi:role="line" + id="tspan11784" + x="60" + y="1172.3622" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="stroke1" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="60" + y="1185.6956" + id="tspan11786" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" stroke-width="5" </tspan><tspan + sodipodi:role="line" + x="60" + y="1199.0289" + id="tspan11792" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> stroke-dasharray="10 4 2 4"/></tspan><tspan + sodipodi:role="line" + x="60" + y="1212.3623" + id="tspan11788" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan><tspan + sodipodi:role="line" + x="60" + y="1225.6957" + id="tspan11790" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text> + <rect + y="1132.3622" + x="1375" + height="115" + width="115" + id="rect11794" + style="fill:url(#overflow1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#overflow2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11796" + width="115" + height="115" + x="1375" + y="1262.3622" /> + <rect + y="1392.3622" + x="1375" + height="115" + width="115" + id="rect11798" + style="fill:url(#overflow3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#overflow4) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11800" + width="115" + height="115" + x="1375" + y="1522.3622" /> + <rect + style="fill:url(#degenerate1) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11802" + width="115" + height="115" + x="2235" + y="1132.3622" /> + <rect + y="1262.3622" + x="2235" + height="115" + width="115" + id="rect11804" + style="fill:url(#degenerate2) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <rect + style="fill:url(#degenerate3) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11806" + width="115" + height="115" + x="2235" + y="1392.3622" /> + <rect + y="1522.3622" + x="2235" + height="115" + width="115" + id="rect11808" + style="fill:url(#degenerate4) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="870" + y="1160.4663" + id="text11810"><tspan + sodipodi:role="line" + id="tspan11812" + x="870" + y="1160.4663" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="overflow1" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + rotate="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0" + sodipodi:role="line" + x="870" + y="1173.7997" + id="tspan11814" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="5" d="L 0,0 5,5 -5,15, 0,20"/></tspan><tspan + sodipodi:role="line" + x="870" + y="1187.1331" + id="tspan11816" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="860" + y="1300.4663" + id="text11818"><tspan + sodipodi:role="line" + id="tspan11820" + x="860" + y="1300.4663" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="overflow2" style="overflow:hidden" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="860" + y="1313.7997" + id="tspan11822" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="0" d="L 0,0 5,5 -5,15, 0,20"/></tspan><tspan + sodipodi:role="line" + x="860" + y="1327.1331" + id="tspan11824" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="860" + y="1422.3622" + id="text11826"><tspan + sodipodi:role="line" + id="tspan11828" + x="860" + y="1422.3622" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="overflow3" style="overflow:visible" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="860" + y="1435.6956" + id="tspan11830" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="0" d="L 0,0 5,5 -5,15, 0,20"/></tspan><tspan + sodipodi:role="line" + x="860" + y="1449.0289" + id="tspan11832" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan><tspan + sodipodi:role="line" + x="860" + y="1462.3623" + id="tspan11834" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="860" + y="1561.3726" + id="text11836"><tspan + sodipodi:role="line" + id="tspan11838" + x="860" + y="1561.3726" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="overflow4" style="overflow:visible" hatchUnits="userSpaceOnUse" pitch="15"></tspan><tspan + sodipodi:role="line" + x="860" + y="1574.7059" + id="tspan11840" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#32ff3f" offset="5" ></tspan><tspan + sodipodi:role="line" + x="860" + y="1588.0393" + id="tspan11842" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#ff0000" offset="20" ></tspan><tspan + sodipodi:role="line" + x="860" + y="1601.3727" + id="tspan11844" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1849.0837" + y="1191.0269" + id="text11846"><tspan + sodipodi:role="line" + id="tspan11848" + x="1849.0837" + y="1191.0269" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="degenerate1" pitch="45"></tspan><tspan + sodipodi:role="line" + x="1849.0837" + y="1204.3602" + id="tspan11850" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1840" + y="1312.3622" + id="text11852"><tspan + sodipodi:role="line" + id="tspan11854" + x="1840" + y="1312.3622" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="degenerate2" xlink:href="#nonexisting" pitch="45"></tspan><tspan + sodipodi:role="line" + x="1840" + y="1325.6956" + id="tspan11856" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1840" + y="1440.4663" + id="text11858"><tspan + sodipodi:role="line" + id="tspan11860" + x="1840" + y="1440.4663" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="degenerate3" pitch="30"></tspan><tspan + sodipodi:role="line" + x="1840" + y="1453.7997" + id="tspan11862" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 0,15"/></tspan><tspan + sodipodi:role="line" + x="1840" + y="1467.1331" + id="tspan11864" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1840" + y="1572.3622" + id="text11866"><tspan + sodipodi:role="line" + id="tspan11868" + x="1840" + y="1572.3622" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"><hatch id="degenerate4" pitch="30"></tspan><tspan + sodipodi:role="line" + x="1840" + y="1585.6956" + id="tspan11870" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> <hatchPath stroke="#a080ff" offset="10" d="L 0,0 5,10 -5,15"/></tspan><tspan + sodipodi:role="line" + x="1840" + y="1599.0289" + id="tspan11872" + style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"></hatch></tspan></text> + <text + id="text11874" + y="30.596558" + x="818.41797" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + xml:space="preserve"><tspan + style="font-size:24px;line-height:1.25;font-family:sans-serif" + y="30.596558" + x="818.41797" + id="tspan11876" + sodipodi:role="line">Hatch transforms</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1817.6445" + y="52.022339" + id="text11878"><tspan + sodipodi:role="line" + id="tspan11880" + x="1817.6445" + y="52.022339" + style="font-size:24px;line-height:1.25;font-family:sans-serif">Multiple hatch paths</tspan></text> + <text + id="text11882" + y="547.37" + x="1827.6445" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + xml:space="preserve"><tspan + style="font-size:24px;line-height:1.25;font-family:sans-serif" + y="547.37" + x="1827.6445" + id="tspan11884" + sodipodi:role="line">Hatch linking</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="64.304688" + y="1090.5966" + id="text11886"><tspan + sodipodi:role="line" + id="tspan11888" + x="64.304688" + y="1090.5966" + style="font-size:24px;line-height:1.25;font-family:sans-serif">Stroke style</tspan></text> + <text + id="text11890" + y="1090.5966" + x="884.30469" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + xml:space="preserve"><tspan + style="font-size:24px;line-height:1.25;font-family:sans-serif" + y="1090.5966" + x="884.30469" + id="tspan11892" + sodipodi:role="line">Overflow property</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none" + x="1844.3047" + y="1090.5966" + id="text11894"><tspan + sodipodi:role="line" + id="tspan11896" + x="1844.3047" + y="1090.5966" + style="font-size:24px;line-height:1.25;font-family:sans-serif">Degenerate cases</tspan></text> + <rect + style="fill:url(#linearGradient11916);fill-opacity:1;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" + id="rect11898" + width="300" + height="100" + x="180" + y="-267.63782" /> + </g> + <script + xlink:href="../hatch.js" + type="text/javascript" + id="script1986" /> +</svg> diff --git a/src/extension/internal/polyfill/mesh.js b/src/extension/internal/polyfill/mesh.js new file mode 100644 index 0000000..d3ba65f --- /dev/null +++ b/src/extension/internal/polyfill/mesh.js @@ -0,0 +1,1188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Use Canvas to render a mesh gradient, passing the rendering to an image via a data stream. +// Copyright: Tavmjong Bah 2018 +// Contributor: Valentin Ionita 2019 +// Distributed under GNU General Public License version 2 or later. See <http://fsf.org/>. + +(function () { + // Name spaces ----------------------------------- + const svgNS = 'http://www.w3.org/2000/svg'; + const xlinkNS = 'http://www.w3.org/1999/xlink'; + const xhtmlNS = 'http://www.w3.org/1999/xhtml'; + /* + * Maximum threshold for Bezier step size + * Larger values leave holes, smaller take longer to render. + */ + const maxBezierStep = 2.0; + + // Test if mesh gradients are supported. + if (document.createElementNS(svgNS, 'meshgradient').x) { + return; + } + + /* + * Utility functions ----------------------------- + */ + // Split Bezier using de Casteljau's method. + const splitBezier = (p0, p1, p2, p3) => { + let tmp = new Point((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5); + let p01 = new Point((p0.x + p1.x) * 0.5, (p0.y + p1.y) * 0.5); + let p12 = new Point((p2.x + p3.x) * 0.5, (p2.y + p3.y) * 0.5); + let p02 = new Point((tmp.x + p01.x) * 0.5, (tmp.y + p01.y) * 0.5); + let p11 = new Point((tmp.x + p12.x) * 0.5, (tmp.y + p12.y) * 0.5); + let p03 = new Point((p02.x + p11.x) * 0.5, (p02.y + p11.y) * 0.5); + + return ([ + [p0, p01, p02, p03], + [p03, p11, p12, p3] + ]); + }; + + // See Cairo: cairo-mesh-pattern-rasterizer.c + const bezierStepsSquared = (points) => { + let tmp0 = points[0].distSquared(points[1]); + let tmp1 = points[2].distSquared(points[3]); + let tmp2 = points[0].distSquared(points[2]) * 0.25; + let tmp3 = points[1].distSquared(points[3]) * 0.25; + + let max1 = tmp0 > tmp1 ? tmp0 : tmp1; + + let max2 = tmp2 > tmp3 ? tmp2 : tmp3; + + let max = max1 > max2 ? max1 : max2; + + return max * 18; + }; + + // Euclidean distance + const distance = (p0, p1) => Math.sqrt(p0.distSquared(p1)); + + // Weighted average to find Bezier points for linear sides. + const wAvg = (p0, p1) => p0.scale(2.0 / 3.0).add(p1.scale(1.0 / 3.0)); + + // Browsers return a string rather than a transform list for gradientTransform! + const parseTransform = (t) => { + let affine = new Affine(); + let trans, scale, radian, tan, skewx, skewy, rotate; + let transforms = t.match(/(\w+\(\s*[^)]+\))+/g); + + transforms.forEach((i) => { + let c = i.match(/[\w.-]+/g); + let type = c.shift(); + + switch (type) { + case 'translate': + if (c.length === 2) { + trans = new Affine(1, 0, 0, 1, c[0], c[1]); + } else { + console.error('mesh.js: translate does not have 2 arguments!'); + trans = new Affine(1, 0, 0, 1, 0, 0); + } + affine = affine.append(trans); + break; + + case 'scale': + if (c.length === 1) { + scale = new Affine(c[0], 0, 0, c[0], 0, 0); + } else if (c.length === 2) { + scale = new Affine(c[0], 0, 0, c[1], 0, 0); + } else { + console.error('mesh.js: scale does not have 1 or 2 arguments!'); + scale = new Affine(1, 0, 0, 1, 0, 0); + } + affine = affine.append(scale); + break; + + case 'rotate': + if (c.length === 3) { + trans = new Affine(1, 0, 0, 1, c[1], c[2]); + affine = affine.append(trans); + } + if (c[0]) { + radian = c[0] * Math.PI / 180.0; + let cos = Math.cos(radian); + let sin = Math.sin(radian); + if (Math.abs(cos) < 1e-16) { // I hate rounding errors... + cos = 0; + } + if (Math.abs(sin) < 1e-16) { // I hate rounding errors... + sin = 0; + } + rotate = new Affine(cos, sin, -sin, cos, 0, 0); + affine = affine.append(rotate); + } else { + console.error('math.js: No argument to rotate transform!'); + } + if (c.length === 3) { + trans = new Affine(1, 0, 0, 1, -c[1], -c[2]); + affine = affine.append(trans); + } + break; + + case 'skewX': + if (c[0]) { + radian = c[0] * Math.PI / 180.0; + tan = Math.tan(radian); + skewx = new Affine(1, 0, tan, 1, 0, 0); + affine = affine.append(skewx); + } else { + console.error('math.js: No argument to skewX transform!'); + } + break; + + case 'skewY': + if (c[0]) { + radian = c[0] * Math.PI / 180.0; + tan = Math.tan(radian); + skewy = new Affine(1, tan, 0, 1, 0, 0); + affine = affine.append(skewy); + } else { + console.error('math.js: No argument to skewY transform!'); + } + break; + + case 'matrix': + if (c.length === 6) { + affine = affine.append(new Affine(...c)); + } else { + console.error('math.js: Incorrect number of arguments for matrix!'); + } + break; + + default: + console.error('mesh.js: Unhandled transform type: ' + type); + break; + } + }); + + return affine; + }; + + const parsePoints = (s) => { + let points = []; + let values = s.split(/[ ,]+/); + for (let i = 0, imax = values.length - 1; i < imax; i += 2) { + points.push(new Point(parseFloat(values[i]), parseFloat(values[i + 1]))); + } + return points; + }; + + // Set multiple attributes to an element + const setAttributes = (el, attrs) => { + for (let key in attrs) { + el.setAttribute(key, attrs[key]); + } + }; + + // Find the slope of point p_k by the values in p_k-1 and p_k+1 + const finiteDifferences = (c0, c1, c2, d01, d12) => { + let slope = [0, 0, 0, 0]; + let slow, shigh; + + for (let k = 0; k < 3; ++k) { + if ((c1[k] < c0[k] && c1[k] < c2[k]) || (c0[k] < c1[k] && c2[k] < c1[k])) { + slope[k] = 0; + } else { + slope[k] = 0.5 * ((c1[k] - c0[k]) / d01 + (c2[k] - c1[k]) / d12); + slow = Math.abs(3.0 * (c1[k] - c0[k]) / d01); + shigh = Math.abs(3.0 * (c2[k] - c1[k]) / d12); + + if (slope[k] > slow) { + slope[k] = slow; + } else if (slope[k] > shigh) { + slope[k] = shigh; + } + } + } + + return slope; + }; + + // Coefficient matrix used for solving linear system + const A = [ + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [-3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0], + [-3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0], + [9, -9, -9, 9, 6, 3, -6, -3, 6, -6, 3, -3, 4, 2, 2, 1], + [-6, 6, 6, -6, -3, -3, 3, 3, -4, 4, -2, 2, -2, -2, -1, -1], + [2, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 2, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1, 0], + [-6, 6, 6, -6, -4, -2, 4, 2, -3, 3, -3, 3, -2, -1, -2, -1], + [4, -4, -4, 4, 2, 2, -2, -2, 2, -2, 2, -2, 1, 1, 1, 1] + ]; + + // Solve the linear system for bicubic interpolation + const solveLinearSystem = (v) => { + let alpha = []; + + for (let i = 0; i < 16; ++i) { + alpha[i] = 0; + for (let j = 0; j < 16; ++j) { + alpha[i] += A[i][j] * v[j]; + } + } + + return alpha; + }; + + // Evaluate the interpolation parameters at (y, x) + const evaluateSolution = (alpha, x, y) => { + const xx = x * x; + const yy = y * y; + const xxx = x * x * x; + const yyy = y * y * y; + + let result = + alpha[0] + + alpha[1] * x + + alpha[2] * xx + + alpha[3] * xxx + + alpha[4] * y + + alpha[5] * y * x + + alpha[6] * y * xx + + alpha[7] * y * xxx + + alpha[8] * yy + + alpha[9] * yy * x + + alpha[10] * yy * xx + + alpha[11] * yy * xxx + + alpha[12] * yyy + + alpha[13] * yyy * x + + alpha[14] * yyy * xx + + alpha[15] * yyy * xxx; + + return result; + }; + + // Split a patch into 8x8 smaller patches + const splitPatch = (patch) => { + let yPatches = []; + let xPatches = []; + let patches = []; + + // Horizontal splitting + for (let i = 0; i < 4; ++i) { + yPatches[i] = []; + yPatches[i][0] = splitBezier( + patch[0][i], patch[1][i], + patch[2][i], patch[3][i] + ); + + yPatches[i][1] = []; + yPatches[i][1].push(...splitBezier(...yPatches[i][0][0])); + yPatches[i][1].push(...splitBezier(...yPatches[i][0][1])); + + yPatches[i][2] = []; + yPatches[i][2].push(...splitBezier(...yPatches[i][1][0])); + yPatches[i][2].push(...splitBezier(...yPatches[i][1][1])); + yPatches[i][2].push(...splitBezier(...yPatches[i][1][2])); + yPatches[i][2].push(...splitBezier(...yPatches[i][1][3])); + } + + // Vertical splitting + for (let i = 0; i < 8; ++i) { + xPatches[i] = []; + + for (let j = 0; j < 4; ++j) { + xPatches[i][j] = []; + xPatches[i][j][0] = splitBezier( + yPatches[0][2][i][j], yPatches[1][2][i][j], + yPatches[2][2][i][j], yPatches[3][2][i][j] + ); + + xPatches[i][j][1] = []; + xPatches[i][j][1].push(...splitBezier(...xPatches[i][j][0][0])); + xPatches[i][j][1].push(...splitBezier(...xPatches[i][j][0][1])); + + xPatches[i][j][2] = []; + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][0])); + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][1])); + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][2])); + xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][3])); + } + } + + for (let i = 0; i < 8; ++i) { + patches[i] = []; + + for (let j = 0; j < 8; ++j) { + patches[i][j] = []; + + patches[i][j][0] = xPatches[i][0][2][j]; + patches[i][j][1] = xPatches[i][1][2][j]; + patches[i][j][2] = xPatches[i][2][2][j]; + patches[i][j][3] = xPatches[i][3][2][j]; + } + } + + return patches; + }; + + // Point class ----------------------------------- + class Point { + constructor (x, y) { + this.x = x || 0; + this.y = y || 0; + } + + toString () { + return `(x=${this.x}, y=${this.y})`; + } + + clone () { + return new Point(this.x, this.y); + } + + add (v) { + return new Point(this.x + v.x, this.y + v.y); + } + + scale (v) { + if (v.x === undefined) { + return new Point(this.x * v, this.y * v); + } + return new Point(this.x * v.x, this.y * v.y); + } + + distSquared (v) { + let x = this.x - v.x; + let y = this.y - v.y; + return (x * x + y * y); + } + + // Transform by affine + transform (affine) { + let x = this.x * affine.a + this.y * affine.c + affine.e; + let y = this.x * affine.b + this.y * affine.d + affine.f; + return new Point(x, y); + } + } + + /* + * Affine class ------------------------------------- + * + * As defined in the SVG spec + * | a c e | + * | b d f | + * | 0 0 1 | + * + */ + + class Affine { + constructor (a, b, c, d, e, f) { + if (a === undefined) { + this.a = 1; + this.b = 0; + this.c = 0; + this.d = 1; + this.e = 0; + this.f = 0; + } else { + this.a = a; + this.b = b; + this.c = c; + this.d = d; + this.e = e; + this.f = f; + } + } + + toString () { + return `affine: ${this.a} ${this.c} ${this.e} \n\ + ${this.b} ${this.d} ${this.f}`; + } + + append (v) { + if (!(v instanceof Affine)) { + console.error(`mesh.js: argument to Affine.append is not affine!`); + } + let a = this.a * v.a + this.c * v.b; + let b = this.b * v.a + this.d * v.b; + let c = this.a * v.c + this.c * v.d; + let d = this.b * v.c + this.d * v.d; + let e = this.a * v.e + this.c * v.f + this.e; + let f = this.b * v.e + this.d * v.f + this.f; + return new Affine(a, b, c, d, e, f); + } + } + + // Curve class -------------------------------------- + class Curve { + constructor (nodes, colors) { + this.nodes = nodes; // 4 Bezier points + this.colors = colors; // 2 x 4 colors (two ends x R+G+B+A) + } + + /* + * Paint a Bezier curve + * w is canvas.width + * h is canvas.height + */ + paintCurve (v, w) { + // If inside, see if we need to split + if (bezierStepsSquared(this.nodes) > maxBezierStep) { + const beziers = splitBezier(...this.nodes); + // ([start][end]) + let colors0 = [[], []]; + let colors1 = [[], []]; + + /* + * Linear horizontal interpolation of the middle value for every + * patch exceeding thereshold + */ + for (let i = 0; i < 4; ++i) { + colors0[0][i] = this.colors[0][i]; + colors0[1][i] = (this.colors[0][i] + this.colors[1][i]) / 2; + colors1[0][i] = colors0[1][i]; + colors1[1][i] = this.colors[1][i]; + } + let curve0 = new Curve(beziers[0], colors0); + let curve1 = new Curve(beziers[1], colors1); + curve0.paintCurve(v, w); + curve1.paintCurve(v, w); + } else { + // Directly write data + let x = Math.round(this.nodes[0].x); + if (x >= 0 && x < w) { + let index = (~~this.nodes[0].y * w + x) * 4; + v[index] = Math.round(this.colors[0][0]); + v[index + 1] = Math.round(this.colors[0][1]); + v[index + 2] = Math.round(this.colors[0][2]); + v[index + 3] = Math.round(this.colors[0][3]); // Alpha + } + } + } + } + + // Patch class ------------------------------------- + class Patch { + constructor (nodes, colors) { + this.nodes = nodes; // 4x4 array of points + this.colors = colors; // 2x2x4 colors (four corners x R+G+B+A) + } + + // Split patch horizontally into two patches. + split () { + let nodes0 = [[], [], [], []]; + let nodes1 = [[], [], [], []]; + let colors0 = [ + [[], []], + [[], []] + ]; + let colors1 = [ + [[], []], + [[], []] + ]; + + for (let i = 0; i < 4; ++i) { + const beziers = splitBezier( + this.nodes[0][i], this.nodes[1][i], + this.nodes[2][i], this.nodes[3][i] + ); + + nodes0[0][i] = beziers[0][0]; + nodes0[1][i] = beziers[0][1]; + nodes0[2][i] = beziers[0][2]; + nodes0[3][i] = beziers[0][3]; + nodes1[0][i] = beziers[1][0]; + nodes1[1][i] = beziers[1][1]; + nodes1[2][i] = beziers[1][2]; + nodes1[3][i] = beziers[1][3]; + } + + /* + * Linear vertical interpolation of the middle value for every + * patch exceeding thereshold + */ + for (let i = 0; i < 4; ++i) { + colors0[0][0][i] = this.colors[0][0][i]; + colors0[0][1][i] = this.colors[0][1][i]; + colors0[1][0][i] = (this.colors[0][0][i] + this.colors[1][0][i]) / 2; + colors0[1][1][i] = (this.colors[0][1][i] + this.colors[1][1][i]) / 2; + colors1[0][0][i] = colors0[1][0][i]; + colors1[0][1][i] = colors0[1][1][i]; + colors1[1][0][i] = this.colors[1][0][i]; + colors1[1][1][i] = this.colors[1][1][i]; + } + + return ([new Patch(nodes0, colors0), new Patch(nodes1, colors1)]); + } + + paint (v, w) { + // Check if we need to split + let larger = false; + let step; + for (let i = 0; i < 4; ++i) { + step = bezierStepsSquared([ + this.nodes[0][i], this.nodes[1][i], + this.nodes[2][i], this.nodes[3][i] + ]); + + if (step > maxBezierStep) { + larger = true; + break; + } + } + + if (larger) { + let patches = this.split(); + patches[0].paint(v, w); + patches[1].paint(v, w); + } else { + /* + * Paint a Bezier curve using just the top of the patch. If + * the patch is thin enough this should work. We leave this + * function here in case we want to do something more fancy. + */ + let curve = new Curve([...this.nodes[0]], [...this.colors[0]]); + curve.paintCurve(v, w); + } + } + } + + // Mesh class --------------------------------------- + class Mesh { + constructor (mesh) { + this.readMesh(mesh); + this.type = mesh.getAttribute('type') || 'bilinear'; + } + + // Function to parse an SVG mesh and set the nodes (points) and colors + readMesh (mesh) { + let nodes = [[]]; + let colors = [[]]; + + let x = Number(mesh.getAttribute('x')); + let y = Number(mesh.getAttribute('y')); + nodes[0][0] = new Point(x, y); + + let rows = mesh.children; + for (let i = 0, imax = rows.length; i < imax; ++i) { + // Need to validate if meshrow... + nodes[3 * i + 1] = []; // Need three extra rows for each meshrow. + nodes[3 * i + 2] = []; + nodes[3 * i + 3] = []; + colors[i + 1] = []; // Need one more row than number of meshrows. + + let patches = rows[i].children; + for (let j = 0, jmax = patches.length; j < jmax; ++j) { + let stops = patches[j].children; + for (let k = 0, kmax = stops.length; k < kmax; ++k) { + let l = k; + if (i !== 0) { + ++l; // There is no top if row isn't first row. + } + let path = stops[k].getAttribute('path'); + let parts; + + let type = 'l'; // We need to still find mid-points even if no path. + if (path != null) { + parts = path.match(/\s*([lLcC])\s*(.*)/); + type = parts[1]; + } + let stopNodes = parsePoints(parts[2]); + + switch (type) { + case 'l': + if (l === 0) { // Top + nodes[3 * i][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j]); + nodes[3 * i][3 * j + 1] = wAvg(nodes[3 * i][3 * j], nodes[3 * i][3 * j + 3]); + nodes[3 * i][3 * j + 2] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i][3 * j]); + } else if (l === 1) { // Right + nodes[3 * i + 3][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j + 3]); + nodes[3 * i + 1][3 * j + 3] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 2][3 * j + 3] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i][3 * j + 3]); + } else if (l === 2) { // Bottom + if (j === 0) { + nodes[3 * i + 3][3 * j + 0] = stopNodes[0].add(nodes[3 * i + 3][3 * j + 3]); + } + nodes[3 * i + 3][3 * j + 1] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 3][3 * j + 2] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i + 3][3 * j]); + } else { // Left + nodes[3 * i + 1][3 * j] = wAvg(nodes[3 * i][3 * j], nodes[3 * i + 3][3 * j]); + nodes[3 * i + 2][3 * j] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i][3 * j]); + } + break; + case 'L': + if (l === 0) { // Top + nodes[3 * i][3 * j + 3] = stopNodes[0]; + nodes[3 * i][3 * j + 1] = wAvg(nodes[3 * i][3 * j], nodes[3 * i][3 * j + 3]); + nodes[3 * i][3 * j + 2] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i][3 * j]); + } else if (l === 1) { // Right + nodes[3 * i + 3][3 * j + 3] = stopNodes[0]; + nodes[3 * i + 1][3 * j + 3] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 2][3 * j + 3] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i][3 * j + 3]); + } else if (l === 2) { // Bottom + if (j === 0) { + nodes[3 * i + 3][3 * j + 0] = stopNodes[0]; + } + nodes[3 * i + 3][3 * j + 1] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 3][3 * j + 2] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i + 3][3 * j]); + } else { // Left + nodes[3 * i + 1][3 * j] = wAvg(nodes[3 * i][3 * j], nodes[3 * i + 3][3 * j]); + nodes[3 * i + 2][3 * j] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i][3 * j]); + } + break; + case 'c': + if (l === 0) { // Top + nodes[3 * i][3 * j + 1] = stopNodes[0].add(nodes[3 * i][3 * j]); + nodes[3 * i][3 * j + 2] = stopNodes[1].add(nodes[3 * i][3 * j]); + nodes[3 * i][3 * j + 3] = stopNodes[2].add(nodes[3 * i][3 * j]); + } else if (l === 1) { // Right + nodes[3 * i + 1][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j + 3]); + nodes[3 * i + 2][3 * j + 3] = stopNodes[1].add(nodes[3 * i][3 * j + 3]); + nodes[3 * i + 3][3 * j + 3] = stopNodes[2].add(nodes[3 * i][3 * j + 3]); + } else if (l === 2) { // Bottom + nodes[3 * i + 3][3 * j + 2] = stopNodes[0].add(nodes[3 * i + 3][3 * j + 3]); + nodes[3 * i + 3][3 * j + 1] = stopNodes[1].add(nodes[3 * i + 3][3 * j + 3]); + if (j === 0) { + nodes[3 * i + 3][3 * j + 0] = stopNodes[2].add(nodes[3 * i + 3][3 * j + 3]); + } + } else { // Left + nodes[3 * i + 2][3 * j] = stopNodes[0].add(nodes[3 * i + 3][3 * j]); + nodes[3 * i + 1][3 * j] = stopNodes[1].add(nodes[3 * i + 3][3 * j]); + } + break; + case 'C': + if (l === 0) { // Top + nodes[3 * i][3 * j + 1] = stopNodes[0]; + nodes[3 * i][3 * j + 2] = stopNodes[1]; + nodes[3 * i][3 * j + 3] = stopNodes[2]; + } else if (l === 1) { // Right + nodes[3 * i + 1][3 * j + 3] = stopNodes[0]; + nodes[3 * i + 2][3 * j + 3] = stopNodes[1]; + nodes[3 * i + 3][3 * j + 3] = stopNodes[2]; + } else if (l === 2) { // Bottom + nodes[3 * i + 3][3 * j + 2] = stopNodes[0]; + nodes[3 * i + 3][3 * j + 1] = stopNodes[1]; + if (j === 0) { + nodes[3 * i + 3][3 * j + 0] = stopNodes[2]; + } + } else { // Left + nodes[3 * i + 2][3 * j] = stopNodes[0]; + nodes[3 * i + 1][3 * j] = stopNodes[1]; + } + break; + default: + console.error('mesh.js: ' + type + ' invalid path type.'); + } + + if ((i === 0 && j === 0) || k > 0) { + let colorRaw = window.getComputedStyle(stops[k]).stopColor + .match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i); + let alphaRaw = window.getComputedStyle(stops[k]).stopOpacity; + let alpha = 255; + if (alphaRaw) { + alpha = Math.floor(alphaRaw * 255); + } + + if (colorRaw) { + if (l === 0) { // upper left corner + colors[i][j] = []; + colors[i][j][0] = Math.floor(colorRaw[1]); + colors[i][j][1] = Math.floor(colorRaw[2]); + colors[i][j][2] = Math.floor(colorRaw[3]); + colors[i][j][3] = alpha; // Alpha + } else if (l === 1) { // upper right corner + colors[i][j + 1] = []; + colors[i][j + 1][0] = Math.floor(colorRaw[1]); + colors[i][j + 1][1] = Math.floor(colorRaw[2]); + colors[i][j + 1][2] = Math.floor(colorRaw[3]); + colors[i][j + 1][3] = alpha; // Alpha + } else if (l === 2) { // lower right corner + colors[i + 1][j + 1] = []; + colors[i + 1][j + 1][0] = Math.floor(colorRaw[1]); + colors[i + 1][j + 1][1] = Math.floor(colorRaw[2]); + colors[i + 1][j + 1][2] = Math.floor(colorRaw[3]); + colors[i + 1][j + 1][3] = alpha; // Alpha + } else if (l === 3) { // lower left corner + colors[i + 1][j] = []; + colors[i + 1][j][0] = Math.floor(colorRaw[1]); + colors[i + 1][j][1] = Math.floor(colorRaw[2]); + colors[i + 1][j][2] = Math.floor(colorRaw[3]); + colors[i + 1][j][3] = alpha; // Alpha + } + } + } + } + + // SVG doesn't use tensor points but we need them for rendering. + nodes[3 * i + 1][3 * j + 1] = new Point(); + nodes[3 * i + 1][3 * j + 2] = new Point(); + nodes[3 * i + 2][3 * j + 1] = new Point(); + nodes[3 * i + 2][3 * j + 2] = new Point(); + + nodes[3 * i + 1][3 * j + 1].x = + (-4.0 * nodes[3 * i][3 * j].x + + 6.0 * (nodes[3 * i][3 * j + 1].x + nodes[3 * i + 1][3 * j].x) + + -2.0 * (nodes[3 * i][3 * j + 3].x + nodes[3 * i + 3][3 * j].x) + + 3.0 * (nodes[3 * i + 3][3 * j + 1].x + nodes[3 * i + 1][3 * j + 3].x) + + -1.0 * nodes[3 * i + 3][3 * j + 3].x) / 9.0; + nodes[3 * i + 1][3 * j + 2].x = + (-4.0 * nodes[3 * i][3 * j + 3].x + + 6.0 * (nodes[3 * i][3 * j + 2].x + nodes[3 * i + 1][3 * j + 3].x) + + -2.0 * (nodes[3 * i][3 * j].x + nodes[3 * i + 3][3 * j + 3].x) + + 3.0 * (nodes[3 * i + 3][3 * j + 2].x + nodes[3 * i + 1][3 * j].x) + + -1.0 * nodes[3 * i + 3][3 * j].x) / 9.0; + nodes[3 * i + 2][3 * j + 1].x = + (-4.0 * nodes[3 * i + 3][3 * j].x + + 6.0 * (nodes[3 * i + 3][3 * j + 1].x + nodes[3 * i + 2][3 * j].x) + + -2.0 * (nodes[3 * i + 3][3 * j + 3].x + nodes[3 * i][3 * j].x) + + 3.0 * (nodes[3 * i][3 * j + 1].x + nodes[3 * i + 2][3 * j + 3].x) + + -1.0 * nodes[3 * i][3 * j + 3].x) / 9.0; + nodes[3 * i + 2][3 * j + 2].x = + (-4.0 * nodes[3 * i + 3][3 * j + 3].x + + 6.0 * (nodes[3 * i + 3][3 * j + 2].x + nodes[3 * i + 2][3 * j + 3].x) + + -2.0 * (nodes[3 * i + 3][3 * j].x + nodes[3 * i][3 * j + 3].x) + + 3.0 * (nodes[3 * i][3 * j + 2].x + nodes[3 * i + 2][3 * j].x) + + -1.0 * nodes[3 * i][3 * j].x) / 9.0; + + nodes[3 * i + 1][3 * j + 1].y = + (-4.0 * nodes[3 * i][3 * j].y + + 6.0 * (nodes[3 * i][3 * j + 1].y + nodes[3 * i + 1][3 * j].y) + + -2.0 * (nodes[3 * i][3 * j + 3].y + nodes[3 * i + 3][3 * j].y) + + 3.0 * (nodes[3 * i + 3][3 * j + 1].y + nodes[3 * i + 1][3 * j + 3].y) + + -1.0 * nodes[3 * i + 3][3 * j + 3].y) / 9.0; + nodes[3 * i + 1][3 * j + 2].y = + (-4.0 * nodes[3 * i][3 * j + 3].y + + 6.0 * (nodes[3 * i][3 * j + 2].y + nodes[3 * i + 1][3 * j + 3].y) + + -2.0 * (nodes[3 * i][3 * j].y + nodes[3 * i + 3][3 * j + 3].y) + + 3.0 * (nodes[3 * i + 3][3 * j + 2].y + nodes[3 * i + 1][3 * j].y) + + -1.0 * nodes[3 * i + 3][3 * j].y) / 9.0; + nodes[3 * i + 2][3 * j + 1].y = + (-4.0 * nodes[3 * i + 3][3 * j].y + + 6.0 * (nodes[3 * i + 3][3 * j + 1].y + nodes[3 * i + 2][3 * j].y) + + -2.0 * (nodes[3 * i + 3][3 * j + 3].y + nodes[3 * i][3 * j].y) + + 3.0 * (nodes[3 * i][3 * j + 1].y + nodes[3 * i + 2][3 * j + 3].y) + + -1.0 * nodes[3 * i][3 * j + 3].y) / 9.0; + nodes[3 * i + 2][3 * j + 2].y = + (-4.0 * nodes[3 * i + 3][3 * j + 3].y + + 6.0 * (nodes[3 * i + 3][3 * j + 2].y + nodes[3 * i + 2][3 * j + 3].y) + + -2.0 * (nodes[3 * i + 3][3 * j].y + nodes[3 * i][3 * j + 3].y) + + 3.0 * (nodes[3 * i][3 * j + 2].y + nodes[3 * i + 2][3 * j].y) + + -1.0 * nodes[3 * i][3 * j].y) / 9.0; + } + } + + this.nodes = nodes; // (m*3+1) x (n*3+1) points + this.colors = colors; // (m+1) x (n+1) x 4 colors (R+G+B+A) + } + + // Extracts out each patch and then paints it + paintMesh (v, w) { + let imax = (this.nodes.length - 1) / 3; + let jmax = (this.nodes[0].length - 1) / 3; + + if (this.type === 'bilinear' || imax < 2 || jmax < 2) { + let patch; + + for (let i = 0; i < imax; ++i) { + for (let j = 0; j < jmax; ++j) { + let sliceNodes = []; + for (let k = i * 3, kmax = (i * 3) + 4; k < kmax; ++k) { + sliceNodes.push(this.nodes[k].slice(j * 3, (j * 3) + 4)); + } + + let sliceColors = []; + sliceColors.push(this.colors[i].slice(j, j + 2)); + sliceColors.push(this.colors[i + 1].slice(j, j + 2)); + + patch = new Patch(sliceNodes, sliceColors); + patch.paint(v, w); + } + } + } else { + // Reference: + // https://en.wikipedia.org/wiki/Bicubic_interpolation#Computation + let d01, d12, patch, sliceNodes, nodes, f, alpha; + const ilast = imax; + const jlast = jmax; + imax++; + jmax++; + + /* + * d = the interpolation data + * d[i][j] = a node record (Point, color_array, color_dx, color_dy) + * d[i][j][0] : Point + * d[i][j][1] : [RGBA] + * d[i][j][2] = dx [RGBA] + * d[i][j][3] = dy [RGBA] + * d[i][j][][k] : color channel k + */ + let d = new Array(imax); + + // Setting the node and the colors + for (let i = 0; i < imax; ++i) { + d[i] = new Array(jmax); + for (let j = 0; j < jmax; ++j) { + d[i][j] = []; + d[i][j][0] = this.nodes[3 * i][3 * j]; + d[i][j][1] = this.colors[i][j]; + } + } + + // Calculate the inner derivatives + for (let i = 0; i < imax; ++i) { + for (let j = 0; j < jmax; ++j) { + // dx + if (i !== 0 && i !== ilast) { + d01 = distance(d[i - 1][j][0], d[i][j][0]); + d12 = distance(d[i + 1][j][0], d[i][j][0]); + d[i][j][2] = finiteDifferences(d[i - 1][j][1], d[i][j][1], + d[i + 1][j][1], d01, d12); + } + + // dy + if (j !== 0 && j !== jlast) { + d01 = distance(d[i][j - 1][0], d[i][j][0]); + d12 = distance(d[i][j + 1][0], d[i][j][0]); + d[i][j][3] = finiteDifferences(d[i][j - 1][1], d[i][j][1], + d[i][j + 1][1], d01, d12); + } + + // dxy is, by standard, set to 0 + } + } + + /* + * Calculate the exterior derivatives + * We fit the exterior derivatives onto parabolas generated by + * the point and the interior derivatives. + */ + for (let j = 0; j < jmax; ++j) { + d[0][j][2] = []; + d[ilast][j][2] = []; + + for (let k = 0; k < 4; ++k) { + d01 = distance(d[1][j][0], d[0][j][0]); + d12 = distance(d[ilast][j][0], d[ilast - 1][j][0]); + + if (d01 > 0) { + d[0][j][2][k] = 2.0 * (d[1][j][1][k] - d[0][j][1][k]) / d01 - + d[1][j][2][k]; + } else { + console.log(`0 was 0! (j: ${j}, k: ${k})`); + d[0][j][2][k] = 0; + } + + if (d12 > 0) { + d[ilast][j][2][k] = 2.0 * (d[ilast][j][1][k] - d[ilast - 1][j][1][k]) / + d12 - d[ilast - 1][j][2][k]; + } else { + console.log(`last was 0! (j: ${j}, k: ${k})`); + d[ilast][j][2][k] = 0; + } + } + } + + for (let i = 0; i < imax; ++i) { + d[i][0][3] = []; + d[i][jlast][3] = []; + + for (let k = 0; k < 4; ++k) { + d01 = distance(d[i][1][0], d[i][0][0]); + d12 = distance(d[i][jlast][0], d[i][jlast - 1][0]); + + if (d01 > 0) { + d[i][0][3][k] = 2.0 * (d[i][1][1][k] - d[i][0][1][k]) / d01 - + d[i][1][3][k]; + } else { + console.log(`0 was 0! (i: ${i}, k: ${k})`); + d[i][0][3][k] = 0; + } + + if (d12 > 0) { + d[i][jlast][3][k] = 2.0 * (d[i][jlast][1][k] - d[i][jlast - 1][1][k]) / + d12 - d[i][jlast - 1][3][k]; + } else { + console.log(`last was 0! (i: ${i}, k: ${k})`); + d[i][jlast][3][k] = 0; + } + } + } + + // Fill patches + for (let i = 0; i < ilast; ++i) { + for (let j = 0; j < jlast; ++j) { + let dLeft = distance(d[i][j][0], d[i + 1][j][0]); + let dRight = distance(d[i][j + 1][0], d[i + 1][j + 1][0]); + let dTop = distance(d[i][j][0], d[i][j + 1][0]); + let dBottom = distance(d[i + 1][j][0], d[i + 1][j + 1][0]); + let r = [[], [], [], []]; + + for (let k = 0; k < 4; ++k) { + f = []; + + f[0] = d[i][j][1][k]; + f[1] = d[i + 1][j][1][k]; + f[2] = d[i][j + 1][1][k]; + f[3] = d[i + 1][j + 1][1][k]; + f[4] = d[i][j][2][k] * dLeft; + f[5] = d[i + 1][j][2][k] * dLeft; + f[6] = d[i][j + 1][2][k] * dRight; + f[7] = d[i + 1][j + 1][2][k] * dRight; + f[8] = d[i][j][3][k] * dTop; + f[9] = d[i + 1][j][3][k] * dBottom; + f[10] = d[i][j + 1][3][k] * dTop; + f[11] = d[i + 1][j + 1][3][k] * dBottom; + f[12] = 0; // dxy + f[13] = 0; // dxy + f[14] = 0; // dxy + f[15] = 0; // dxy + + // get alpha values + alpha = solveLinearSystem(f); + + for (let l = 0; l < 9; ++l) { + r[k][l] = []; + + for (let m = 0; m < 9; ++m) { + // evaluation + r[k][l][m] = evaluateSolution(alpha, l / 8, m / 8); + + if (r[k][l][m] > 255) { + r[k][l][m] = 255; + } else if (r[k][l][m] < 0.0) { + r[k][l][m] = 0.0; + } + } + } + } + + // split the bezier patch into 8x8 patches + sliceNodes = []; + for (let k = i * 3, kmax = (i * 3) + 4; k < kmax; ++k) { + sliceNodes.push(this.nodes[k].slice(j * 3, (j * 3) + 4)); + } + + nodes = splitPatch(sliceNodes); + + // Create patches and paint the bilinearliy + for (let l = 0; l < 8; ++l) { + for (let m = 0; m < 8; ++m) { + patch = new Patch( + nodes[l][m], + [[ + [r[0][l][m], r[1][l][m], r[2][l][m], r[3][l][m]], + [r[0][l][m + 1], r[1][l][m + 1], r[2][l][m + 1], r[3][l][m + 1]] + ], [ + [r[0][l + 1][m], r[1][l + 1][m], r[2][l + 1][m], r[3][l + 1][m]], + [r[0][l + 1][m + 1], r[1][l + 1][m + 1], r[2][l + 1][m + 1], r[3][l + 1][m + 1]] + ]] + ); + + patch.paint(v, w); + } + } + } + } + } + } + + // Transforms mesh into coordinate space of canvas (t is either Point or Affine). + transform (t) { + if (t instanceof Point) { + for (let i = 0, imax = this.nodes.length; i < imax; ++i) { + for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) { + this.nodes[i][j] = this.nodes[i][j].add(t); + } + } + } else if (t instanceof Affine) { + for (let i = 0, imax = this.nodes.length; i < imax; ++i) { + for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) { + this.nodes[i][j] = this.nodes[i][j].transform(t); + } + } + } + } + + // Scale mesh into coordinate space of canvas (t is a Point). + scale (t) { + for (let i = 0, imax = this.nodes.length; i < imax; ++i) { + for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) { + this.nodes[i][j] = this.nodes[i][j].scale(t); + } + } + } + } + + // Start of document processing --------------------- + const shapes = document.querySelectorAll('rect,circle,ellipse,path,text'); + + shapes.forEach((shape, i) => { + // Get id. If no id, create one. + let shapeId = shape.getAttribute('id'); + if (!shapeId) { + shapeId = 'patchjs_shape' + i; + shape.setAttribute('id', shapeId); + } + + const fillURL = shape.style.fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/); + const strokeURL = shape.style.stroke.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/); + + if (fillURL && fillURL[1]) { + const mesh = document.getElementById(fillURL[1]); + + if (mesh && mesh.nodeName === 'meshgradient') { + const bbox = shape.getBBox(); + + // Create temporary canvas + let myCanvas = document.createElementNS(xhtmlNS, 'canvas'); + setAttributes(myCanvas, { + 'width': bbox.width, + 'height': bbox.height + }); + + const myContext = myCanvas.getContext('2d'); + let myCanvasImage = myContext.createImageData(bbox.width, bbox.height); + + // Draw a mesh + const myMesh = new Mesh(mesh); + + // Adjust for bounding box if necessary. + if (mesh.getAttribute('gradientUnits') === 'objectBoundingBox') { + myMesh.scale(new Point(bbox.width, bbox.height)); + } + + // Apply gradient transform. + const gradientTransform = mesh.getAttribute('gradientTransform'); + if (gradientTransform != null) { + myMesh.transform(parseTransform(gradientTransform)); + } + + // Position to Canvas coordinate. + if (mesh.getAttribute('gradientUnits') === 'userSpaceOnUse') { + myMesh.transform(new Point(-bbox.x, -bbox.y)); + } + + // Paint + myMesh.paintMesh(myCanvasImage.data, myCanvas.width); + myContext.putImageData(myCanvasImage, 0, 0); + + // Create image element of correct size + const myImage = document.createElementNS(svgNS, 'image'); + setAttributes(myImage, { + 'width': bbox.width, + 'height': bbox.height, + 'x': bbox.x, + 'y': bbox.y + }); + + // Set image to data url + let myPNG = myCanvas.toDataURL(); + myImage.setAttributeNS(xlinkNS, 'xlink:href', myPNG); + + // Insert image into document + shape.parentNode.insertBefore(myImage, shape); + shape.style.fill = 'none'; + + // Create clip referencing shape and insert into document + const use = document.createElementNS(svgNS, 'use'); + use.setAttributeNS(xlinkNS, 'xlink:href', '#' + shapeId); + + const clipId = 'patchjs_clip' + i; + const clip = document.createElementNS(svgNS, 'clipPath'); + clip.setAttribute('id', clipId); + clip.appendChild(use); + shape.parentElement.insertBefore(clip, shape); + myImage.setAttribute('clip-path', 'url(#' + clipId + ')'); + + // Force the Garbage Collector to free the space + myCanvasImage = null; + myCanvas = null; + myPNG = null; + } + } + + if (strokeURL && strokeURL[1]) { + const mesh = document.getElementById(strokeURL[1]); + + if (mesh && mesh.nodeName === 'meshgradient') { + const strokeWidth = parseFloat(shape.style.strokeWidth.slice(0, -2)); + const strokeMiterlimit = parseFloat(shape.style.strokeMiterlimit) || + parseFloat(shape.getAttribute('stroke-miterlimit')) || 1; + const phase = strokeWidth * strokeMiterlimit; + + const bbox = shape.getBBox(); + const boxWidth = Math.trunc(bbox.width + phase); + const boxHeight = Math.trunc(bbox.height + phase); + const boxX = Math.trunc(bbox.x - phase / 2); + const boxY = Math.trunc(bbox.y - phase / 2); + + // Create temporary canvas + let myCanvas = document.createElementNS(xhtmlNS, 'canvas'); + setAttributes(myCanvas, { + 'width': boxWidth, + 'height': boxHeight + }); + + const myContext = myCanvas.getContext('2d'); + let myCanvasImage = myContext.createImageData(boxWidth, boxHeight); + + // Draw a mesh + const myMesh = new Mesh(mesh); + + // Adjust for bounding box if necessary. + if (mesh.getAttribute('gradientUnits') === 'objectBoundingBox') { + myMesh.scale(new Point(boxWidth, boxHeight)); + } + + // Apply gradient transform. + const gradientTransform = mesh.getAttribute('gradientTransform'); + if (gradientTransform != null) { + myMesh.transform(parseTransform(gradientTransform)); + } + + // Position to Canvas coordinate. + if (mesh.getAttribute('gradientUnits') === 'userSpaceOnUse') { + myMesh.transform(new Point(-boxX, -boxY)); + } + + // Paint + myMesh.paintMesh(myCanvasImage.data, myCanvas.width); + myContext.putImageData(myCanvasImage, 0, 0); + + // Create image element of correct size + const myImage = document.createElementNS(svgNS, 'image'); + setAttributes(myImage, { + 'width': boxWidth, + 'height': boxHeight, + 'x': 0, + 'y': 0 + }); + + // Set image to data url + let myPNG = myCanvas.toDataURL(); + myImage.setAttributeNS(xlinkNS, 'xlink:href', myPNG); + + // Create pattern to hold the stroke image + const patternId = 'pattern_clip' + i; + const myPattern = document.createElementNS(svgNS, 'pattern'); + setAttributes(myPattern, { + 'id': patternId, + 'patternUnits': 'userSpaceOnUse', + 'width': boxWidth, + 'height': boxHeight, + 'x': boxX, + 'y': boxY + }); + myPattern.appendChild(myImage); + + // Insert image into document + mesh.parentNode.appendChild(myPattern); + shape.style.stroke = 'url(#' + patternId + ')'; + + // Force the Garbage Collector to free the space + myCanvasImage = null; + myCanvas = null; + myPNG = null; + } + } + }); +})(); diff --git a/src/extension/internal/polyfill/mesh_compressed.include b/src/extension/internal/polyfill/mesh_compressed.include new file mode 100644 index 0000000..c6eeca0 --- /dev/null +++ b/src/extension/internal/polyfill/mesh_compressed.include @@ -0,0 +1,4 @@ +//SPDX-License-Identifier: GPL-2.0-or-later +R"=====( +!function(){const t="http://www.w3.org/2000/svg",e="http://www.w3.org/1999/xlink",s="http://www.w3.org/1999/xhtml",r=2;if(document.createElementNS(t,"meshgradient").x)return;const n=(t,e,s,r)=>{let n=new x(.5*(e.x+s.x),.5*(e.y+s.y)),o=new x(.5*(t.x+e.x),.5*(t.y+e.y)),i=new x(.5*(s.x+r.x),.5*(s.y+r.y)),a=new x(.5*(n.x+o.x),.5*(n.y+o.y)),h=new x(.5*(n.x+i.x),.5*(n.y+i.y)),l=new x(.5*(a.x+h.x),.5*(a.y+h.y));return[[t,o,a,l],[l,h,i,r]]},o=t=>{let e=t[0].distSquared(t[1]),s=t[2].distSquared(t[3]),r=.25*t[0].distSquared(t[2]),n=.25*t[1].distSquared(t[3]),o=e>s?e:s,i=r>n?r:n;return 18*(o>i?o:i)},i=(t,e)=>Math.sqrt(t.distSquared(e)),a=(t,e)=>t.scale(2/3).add(e.scale(1/3)),h=t=>{let e,s,r,n,o,i,a,h=new g;return t.match(/(\w+\(\s*[^)]+\))+/g).forEach(t=>{let l=t.match(/[\w.-]+/g),d=l.shift();switch(d){case"translate":2===l.length?e=new g(1,0,0,1,l[0],l[1]):(console.error("mesh.js: translate does not have 2 arguments!"),e=new g(1,0,0,1,0,0)),h=h.append(e);break;case"scale":1===l.length?s=new g(l[0],0,0,l[0],0,0):2===l.length?s=new g(l[0],0,0,l[1],0,0):(console.error("mesh.js: scale does not have 1 or 2 arguments!"),s=new g(1,0,0,1,0,0)),h=h.append(s);break;case"rotate":if(3===l.length&&(e=new g(1,0,0,1,l[1],l[2]),h=h.append(e)),l[0]){r=l[0]*Math.PI/180;let t=Math.cos(r),e=Math.sin(r);Math.abs(t)<1e-16&&(t=0),Math.abs(e)<1e-16&&(e=0),a=new g(t,e,-e,t,0,0),h=h.append(a)}else console.error("math.js: No argument to rotate transform!");3===l.length&&(e=new g(1,0,0,1,-l[1],-l[2]),h=h.append(e));break;case"skewX":l[0]?(r=l[0]*Math.PI/180,n=Math.tan(r),o=new g(1,0,n,1,0,0),h=h.append(o)):console.error("math.js: No argument to skewX transform!");break;case"skewY":l[0]?(r=l[0]*Math.PI/180,n=Math.tan(r),i=new g(1,n,0,1,0,0),h=h.append(i)):console.error("math.js: No argument to skewY transform!");break;case"matrix":6===l.length?h=h.append(new g(...l)):console.error("math.js: Incorrect number of arguments for matrix!");break;default:console.error("mesh.js: Unhandled transform type: "+d)}}),h},l=t=>{let e=[],s=t.split(/[ ,]+/);for(let t=0,r=s.length-1;t<r;t+=2)e.push(new x(parseFloat(s[t]),parseFloat(s[t+1])));return e},d=(t,e)=>{for(let s in e)t.setAttribute(s,e[s])},c=(t,e,s,r,n)=>{let o,i,a=[0,0,0,0];for(let h=0;h<3;++h)e[h]<t[h]&&e[h]<s[h]||t[h]<e[h]&&s[h]<e[h]?a[h]=0:(a[h]=.5*((e[h]-t[h])/r+(s[h]-e[h])/n),o=Math.abs(3*(e[h]-t[h])/r),i=Math.abs(3*(s[h]-e[h])/n),a[h]>o?a[h]=o:a[h]>i&&(a[h]=i));return a},u=[[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],[-3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0],[2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0],[0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0],[-3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0],[0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0],[9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1],[-6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1],[2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0],[0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0],[-6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1],[4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1]],f=t=>{let e=[];for(let s=0;s<16;++s){e[s]=0;for(let r=0;r<16;++r)e[s]+=u[s][r]*t[r]}return e},p=(t,e,s)=>{const r=e*e,n=s*s,o=e*e*e,i=s*s*s;return t[0]+t[1]*e+t[2]*r+t[3]*o+t[4]*s+t[5]*s*e+t[6]*s*r+t[7]*s*o+t[8]*n+t[9]*n*e+t[10]*n*r+t[11]*n*o+t[12]*i+t[13]*i*e+t[14]*i*r+t[15]*i*o},y=t=>{let e=[],s=[],r=[];for(let s=0;s<4;++s)e[s]=[],e[s][0]=n(t[0][s],t[1][s],t[2][s],t[3][s]),e[s][1]=[],e[s][1].push(...n(...e[s][0][0])),e[s][1].push(...n(...e[s][0][1])),e[s][2]=[],e[s][2].push(...n(...e[s][1][0])),e[s][2].push(...n(...e[s][1][1])),e[s][2].push(...n(...e[s][1][2])),e[s][2].push(...n(...e[s][1][3]));for(let t=0;t<8;++t){s[t]=[];for(let r=0;r<4;++r)s[t][r]=[],s[t][r][0]=n(e[0][2][t][r],e[1][2][t][r],e[2][2][t][r],e[3][2][t][r]),s[t][r][1]=[],s[t][r][1].push(...n(...s[t][r][0][0])),s[t][r][1].push(...n(...s[t][r][0][1])),s[t][r][2]=[],s[t][r][2].push(...n(...s[t][r][1][0])),s[t][r][2].push(...n(...s[t][r][1][1])),s[t][r][2].push(...n(...s[t][r][1][2])),s[t][r][2].push(...n(...s[t][r][1][3]))}for(let t=0;t<8;++t){r[t]=[];for(let e=0;e<8;++e)r[t][e]=[],r[t][e][0]=s[t][0][2][e],r[t][e][1]=s[t][1][2][e],r[t][e][2]=s[t][2][2][e],r[t][e][3]=s[t][3][2][e]}return r};class x{constructor(t,e){this.x=t||0,this.y=e||0}toString(){return`(x=${this.x}, y=${this.y})`}clone(){return new x(this.x,this.y)}add(t){return new x(this.x+t.x,this.y+t.y)}scale(t){return void 0===t.x?new x(this.x*t,this.y*t):new x(this.x*t.x,this.y*t.y)}distSquared(t){let e=this.x-t.x,s=this.y-t.y;return e*e+s*s}transform(t){let e=this.x*t.a+this.y*t.c+t.e,s=this.x*t.b+this.y*t.d+t.f;return new x(e,s)}}class g{constructor(t,e,s,r,n,o){void 0===t?(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0):(this.a=t,this.b=e,this.c=s,this.d=r,this.e=n,this.f=o)}toString(){return`affine: ${this.a} ${this.c} ${this.e} \n ${this.b} ${this.d} ${this.f}`}append(t){t instanceof g||console.error("mesh.js: argument to Affine.append is not affine!");let e=this.a*t.a+this.c*t.b,s=this.b*t.a+this.d*t.b,r=this.a*t.c+this.c*t.d,n=this.b*t.c+this.d*t.d,o=this.a*t.e+this.c*t.f+this.e,i=this.b*t.e+this.d*t.f+this.f;return new g(e,s,r,n,o,i)}}class w{constructor(t,e){this.nodes=t,this.colors=e}paintCurve(t,e){if(o(this.nodes)>r){const s=n(...this.nodes);let r=[[],[]],o=[[],[]];for(let t=0;t<4;++t)r[0][t]=this.colors[0][t],r[1][t]=(this.colors[0][t]+this.colors[1][t])/2,o[0][t]=r[1][t],o[1][t]=this.colors[1][t];let i=new w(s[0],r),a=new w(s[1],o);i.paintCurve(t,e),a.paintCurve(t,e)}else{let s=Math.round(this.nodes[0].x);if(s>=0&&s<e){let r=4*(~~this.nodes[0].y*e+s);t[r]=Math.round(this.colors[0][0]),t[r+1]=Math.round(this.colors[0][1]),t[r+2]=Math.round(this.colors[0][2]),t[r+3]=Math.round(this.colors[0][3])}}}}class m{constructor(t,e){this.nodes=t,this.colors=e}split(){let t=[[],[],[],[]],e=[[],[],[],[]],s=[[[],[]],[[],[]]],r=[[[],[]],[[],[]]];for(let s=0;s<4;++s){const r=n(this.nodes[0][s],this.nodes[1][s],this.nodes[2][s],this.nodes[3][s]);t[0][s]=r[0][0],t[1][s]=r[0][1],t[2][s]=r[0][2],t[3][s]=r[0][3],e[0][s]=r[1][0],e[1][s]=r[1][1],e[2][s]=r[1][2],e[3][s]=r[1][3]}for(let t=0;t<4;++t)s[0][0][t]=this.colors[0][0][t],s[0][1][t]=this.colors[0][1][t],s[1][0][t]=(this.colors[0][0][t]+this.colors[1][0][t])/2,s[1][1][t]=(this.colors[0][1][t]+this.colors[1][1][t])/2,r[0][0][t]=s[1][0][t],r[0][1][t]=s[1][1][t],r[1][0][t]=this.colors[1][0][t],r[1][1][t]=this.colors[1][1][t];return[new m(t,s),new m(e,r)]}paint(t,e){let s,n=!1;for(let t=0;t<4;++t)if((s=o([this.nodes[0][t],this.nodes[1][t],this.nodes[2][t],this.nodes[3][t]]))>r){n=!0;break}if(n){let s=this.split();s[0].paint(t,e),s[1].paint(t,e)}else{new w([...this.nodes[0]],[...this.colors[0]]).paintCurve(t,e)}}}class b{constructor(t){this.readMesh(t),this.type=t.getAttribute("type")||"bilinear"}readMesh(t){let e=[[]],s=[[]],r=Number(t.getAttribute("x")),n=Number(t.getAttribute("y"));e[0][0]=new x(r,n);let o=t.children;for(let t=0,r=o.length;t<r;++t){e[3*t+1]=[],e[3*t+2]=[],e[3*t+3]=[],s[t+1]=[];let r=o[t].children;for(let n=0,o=r.length;n<o;++n){let o=r[n].children;for(let r=0,i=o.length;r<i;++r){let i=r;0!==t&&++i;let h,d=o[r].getAttribute("path"),c="l";null!=d&&(c=(h=d.match(/\s*([lLcC])\s*(.*)/))[1]);let u=l(h[2]);switch(c){case"l":0===i?(e[3*t][3*n+3]=u[0].add(e[3*t][3*n]),e[3*t][3*n+1]=a(e[3*t][3*n],e[3*t][3*n+3]),e[3*t][3*n+2]=a(e[3*t][3*n+3],e[3*t][3*n])):1===i?(e[3*t+3][3*n+3]=u[0].add(e[3*t][3*n+3]),e[3*t+1][3*n+3]=a(e[3*t][3*n+3],e[3*t+3][3*n+3]),e[3*t+2][3*n+3]=a(e[3*t+3][3*n+3],e[3*t][3*n+3])):2===i?(0===n&&(e[3*t+3][3*n+0]=u[0].add(e[3*t+3][3*n+3])),e[3*t+3][3*n+1]=a(e[3*t+3][3*n],e[3*t+3][3*n+3]),e[3*t+3][3*n+2]=a(e[3*t+3][3*n+3],e[3*t+3][3*n])):(e[3*t+1][3*n]=a(e[3*t][3*n],e[3*t+3][3*n]),e[3*t+2][3*n]=a(e[3*t+3][3*n],e[3*t][3*n]));break;case"L":0===i?(e[3*t][3*n+3]=u[0],e[3*t][3*n+1]=a(e[3*t][3*n],e[3*t][3*n+3]),e[3*t][3*n+2]=a(e[3*t][3*n+3],e[3*t][3*n])):1===i?(e[3*t+3][3*n+3]=u[0],e[3*t+1][3*n+3]=a(e[3*t][3*n+3],e[3*t+3][3*n+3]),e[3*t+2][3*n+3]=a(e[3*t+3][3*n+3],e[3*t][3*n+3])):2===i?(0===n&&(e[3*t+3][3*n+0]=u[0]),e[3*t+3][3*n+1]=a(e[3*t+3][3*n],e[3*t+3][3*n+3]),e[3*t+3][3*n+2]=a(e[3*t+3][3*n+3],e[3*t+3][3*n])):(e[3*t+1][3*n]=a(e[3*t][3*n],e[3*t+3][3*n]),e[3*t+2][3*n]=a(e[3*t+3][3*n],e[3*t][3*n]));break;case"c":0===i?(e[3*t][3*n+1]=u[0].add(e[3*t][3*n]),e[3*t][3*n+2]=u[1].add(e[3*t][3*n]),e[3*t][3*n+3]=u[2].add(e[3*t][3*n])):1===i?(e[3*t+1][3*n+3]=u[0].add(e[3*t][3*n+3]),e[3*t+2][3*n+3]=u[1].add(e[3*t][3*n+3]),e[3*t+3][3*n+3]=u[2].add(e[3*t][3*n+3])):2===i?(e[3*t+3][3*n+2]=u[0].add(e[3*t+3][3*n+3]),e[3*t+3][3*n+1]=u[1].add(e[3*t+3][3*n+3]),0===n&&(e[3*t+3][3*n+0]=u[2].add(e[3*t+3][3*n+3]))):(e[3*t+2][3*n]=u[0].add(e[3*t+3][3*n]),e[3*t+1][3*n]=u[1].add(e[3*t+3][3*n]));break;case"C":0===i?(e[3*t][3*n+1]=u[0],e[3*t][3*n+2]=u[1],e[3*t][3*n+3]=u[2]):1===i?(e[3*t+1][3*n+3]=u[0],e[3*t+2][3*n+3]=u[1],e[3*t+3][3*n+3]=u[2]):2===i?(e[3*t+3][3*n+2]=u[0],e[3*t+3][3*n+1]=u[1],0===n&&(e[3*t+3][3*n+0]=u[2])):(e[3*t+2][3*n]=u[0],e[3*t+1][3*n]=u[1]);break;default:console.error("mesh.js: "+c+" invalid path type.")}if(0===t&&0===n||r>0){let e=window.getComputedStyle(o[r]).stopColor.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i),a=window.getComputedStyle(o[r]).stopOpacity,h=255;a&&(h=Math.floor(255*a)),e&&(0===i?(s[t][n]=[],s[t][n][0]=Math.floor(e[1]),s[t][n][1]=Math.floor(e[2]),s[t][n][2]=Math.floor(e[3]),s[t][n][3]=h):1===i?(s[t][n+1]=[],s[t][n+1][0]=Math.floor(e[1]),s[t][n+1][1]=Math.floor(e[2]),s[t][n+1][2]=Math.floor(e[3]),s[t][n+1][3]=h):2===i?(s[t+1][n+1]=[],s[t+1][n+1][0]=Math.floor(e[1]),s[t+1][n+1][1]=Math.floor(e[2]),s[t+1][n+1][2]=Math.floor(e[3]),s[t+1][n+1][3]=h):3===i&&(s[t+1][n]=[],s[t+1][n][0]=Math.floor(e[1]),s[t+1][n][1]=Math.floor(e[2]),s[t+1][n][2]=Math.floor(e[3]),s[t+1][n][3]=h))}}e[3*t+1][3*n+1]=new x,e[3*t+1][3*n+2]=new x,e[3*t+2][3*n+1]=new x,e[3*t+2][3*n+2]=new x,e[3*t+1][3*n+1].x=(-4*e[3*t][3*n].x+6*(e[3*t][3*n+1].x+e[3*t+1][3*n].x)+-2*(e[3*t][3*n+3].x+e[3*t+3][3*n].x)+3*(e[3*t+3][3*n+1].x+e[3*t+1][3*n+3].x)+-1*e[3*t+3][3*n+3].x)/9,e[3*t+1][3*n+2].x=(-4*e[3*t][3*n+3].x+6*(e[3*t][3*n+2].x+e[3*t+1][3*n+3].x)+-2*(e[3*t][3*n].x+e[3*t+3][3*n+3].x)+3*(e[3*t+3][3*n+2].x+e[3*t+1][3*n].x)+-1*e[3*t+3][3*n].x)/9,e[3*t+2][3*n+1].x=(-4*e[3*t+3][3*n].x+6*(e[3*t+3][3*n+1].x+e[3*t+2][3*n].x)+-2*(e[3*t+3][3*n+3].x+e[3*t][3*n].x)+3*(e[3*t][3*n+1].x+e[3*t+2][3*n+3].x)+-1*e[3*t][3*n+3].x)/9,e[3*t+2][3*n+2].x=(-4*e[3*t+3][3*n+3].x+6*(e[3*t+3][3*n+2].x+e[3*t+2][3*n+3].x)+-2*(e[3*t+3][3*n].x+e[3*t][3*n+3].x)+3*(e[3*t][3*n+2].x+e[3*t+2][3*n].x)+-1*e[3*t][3*n].x)/9,e[3*t+1][3*n+1].y=(-4*e[3*t][3*n].y+6*(e[3*t][3*n+1].y+e[3*t+1][3*n].y)+-2*(e[3*t][3*n+3].y+e[3*t+3][3*n].y)+3*(e[3*t+3][3*n+1].y+e[3*t+1][3*n+3].y)+-1*e[3*t+3][3*n+3].y)/9,e[3*t+1][3*n+2].y=(-4*e[3*t][3*n+3].y+6*(e[3*t][3*n+2].y+e[3*t+1][3*n+3].y)+-2*(e[3*t][3*n].y+e[3*t+3][3*n+3].y)+3*(e[3*t+3][3*n+2].y+e[3*t+1][3*n].y)+-1*e[3*t+3][3*n].y)/9,e[3*t+2][3*n+1].y=(-4*e[3*t+3][3*n].y+6*(e[3*t+3][3*n+1].y+e[3*t+2][3*n].y)+-2*(e[3*t+3][3*n+3].y+e[3*t][3*n].y)+3*(e[3*t][3*n+1].y+e[3*t+2][3*n+3].y)+-1*e[3*t][3*n+3].y)/9,e[3*t+2][3*n+2].y=(-4*e[3*t+3][3*n+3].y+6*(e[3*t+3][3*n+2].y+e[3*t+2][3*n+3].y)+-2*(e[3*t+3][3*n].y+e[3*t][3*n+3].y)+3*(e[3*t][3*n+2].y+e[3*t+2][3*n].y)+-1*e[3*t][3*n].y)/9}}this.nodes=e,this.colors=s}paintMesh(t,e){let s=(this.nodes.length-1)/3,r=(this.nodes[0].length-1)/3;if("bilinear"===this.type||s<2||r<2){let n;for(let o=0;o<s;++o)for(let s=0;s<r;++s){let r=[];for(let t=3*o,e=3*o+4;t<e;++t)r.push(this.nodes[t].slice(3*s,3*s+4));let i=[];i.push(this.colors[o].slice(s,s+2)),i.push(this.colors[o+1].slice(s,s+2)),(n=new m(r,i)).paint(t,e)}}else{let n,o,a,h,l,d,u;const x=s,g=r;s++,r++;let w=new Array(s);for(let t=0;t<s;++t){w[t]=new Array(r);for(let e=0;e<r;++e)w[t][e]=[],w[t][e][0]=this.nodes[3*t][3*e],w[t][e][1]=this.colors[t][e]}for(let t=0;t<s;++t)for(let e=0;e<r;++e)0!==t&&t!==x&&(n=i(w[t-1][e][0],w[t][e][0]),o=i(w[t+1][e][0],w[t][e][0]),w[t][e][2]=c(w[t-1][e][1],w[t][e][1],w[t+1][e][1],n,o)),0!==e&&e!==g&&(n=i(w[t][e-1][0],w[t][e][0]),o=i(w[t][e+1][0],w[t][e][0]),w[t][e][3]=c(w[t][e-1][1],w[t][e][1],w[t][e+1][1],n,o));for(let t=0;t<r;++t){w[0][t][2]=[],w[x][t][2]=[];for(let e=0;e<4;++e)n=i(w[1][t][0],w[0][t][0]),o=i(w[x][t][0],w[x-1][t][0]),w[0][t][2][e]=n>0?2*(w[1][t][1][e]-w[0][t][1][e])/n-w[1][t][2][e]:0,w[x][t][2][e]=o>0?2*(w[x][t][1][e]-w[x-1][t][1][e])/o-w[x-1][t][2][e]:0}for(let t=0;t<s;++t){w[t][0][3]=[],w[t][g][3]=[];for(let e=0;e<4;++e)n=i(w[t][1][0],w[t][0][0]),o=i(w[t][g][0],w[t][g-1][0]),w[t][0][3][e]=n>0?2*(w[t][1][1][e]-w[t][0][1][e])/n-w[t][1][3][e]:0,w[t][g][3][e]=o>0?2*(w[t][g][1][e]-w[t][g-1][1][e])/o-w[t][g-1][3][e]:0}for(let s=0;s<x;++s)for(let r=0;r<g;++r){let n=i(w[s][r][0],w[s+1][r][0]),o=i(w[s][r+1][0],w[s+1][r+1][0]),c=i(w[s][r][0],w[s][r+1][0]),x=i(w[s+1][r][0],w[s+1][r+1][0]),g=[[],[],[],[]];for(let t=0;t<4;++t){(d=[])[0]=w[s][r][1][t],d[1]=w[s+1][r][1][t],d[2]=w[s][r+1][1][t],d[3]=w[s+1][r+1][1][t],d[4]=w[s][r][2][t]*n,d[5]=w[s+1][r][2][t]*n,d[6]=w[s][r+1][2][t]*o,d[7]=w[s+1][r+1][2][t]*o,d[8]=w[s][r][3][t]*c,d[9]=w[s+1][r][3][t]*x,d[10]=w[s][r+1][3][t]*c,d[11]=w[s+1][r+1][3][t]*x,d[12]=0,d[13]=0,d[14]=0,d[15]=0,u=f(d);for(let e=0;e<9;++e){g[t][e]=[];for(let s=0;s<9;++s)g[t][e][s]=p(u,e/8,s/8),g[t][e][s]>255?g[t][e][s]=255:g[t][e][s]<0&&(g[t][e][s]=0)}}h=[];for(let t=3*s,e=3*s+4;t<e;++t)h.push(this.nodes[t].slice(3*r,3*r+4));l=y(h);for(let s=0;s<8;++s)for(let r=0;r<8;++r)(a=new m(l[s][r],[[[g[0][s][r],g[1][s][r],g[2][s][r],g[3][s][r]],[g[0][s][r+1],g[1][s][r+1],g[2][s][r+1],g[3][s][r+1]]],[[g[0][s+1][r],g[1][s+1][r],g[2][s+1][r],g[3][s+1][r]],[g[0][s+1][r+1],g[1][s+1][r+1],g[2][s+1][r+1],g[3][s+1][r+1]]]])).paint(t,e)}}}transform(t){if(t instanceof x)for(let e=0,s=this.nodes.length;e<s;++e)for(let s=0,r=this.nodes[0].length;s<r;++s)this.nodes[e][s]=this.nodes[e][s].add(t);else if(t instanceof g)for(let e=0,s=this.nodes.length;e<s;++e)for(let s=0,r=this.nodes[0].length;s<r;++s)this.nodes[e][s]=this.nodes[e][s].transform(t)}scale(t){for(let e=0,s=this.nodes.length;e<s;++e)for(let s=0,r=this.nodes[0].length;s<r;++s)this.nodes[e][s]=this.nodes[e][s].scale(t)}}document.querySelectorAll("rect,circle,ellipse,path,text").forEach((r,n)=>{let o=r.getAttribute("id");o||(o="patchjs_shape"+n,r.setAttribute("id",o));const i=r.style.fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/),a=r.style.stroke.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);if(i&&i[1]){const a=document.getElementById(i[1]);if(a&&"meshgradient"===a.nodeName){const i=r.getBBox();let l=document.createElementNS(s,"canvas");d(l,{width:i.width,height:i.height});const c=l.getContext("2d");let u=c.createImageData(i.width,i.height);const f=new b(a);"objectBoundingBox"===a.getAttribute("gradientUnits")&&f.scale(new x(i.width,i.height));const p=a.getAttribute("gradientTransform");null!=p&&f.transform(h(p)),"userSpaceOnUse"===a.getAttribute("gradientUnits")&&f.transform(new x(-i.x,-i.y)),f.paintMesh(u.data,l.width),c.putImageData(u,0,0);const y=document.createElementNS(t,"image");d(y,{width:i.width,height:i.height,x:i.x,y:i.y});let g=l.toDataURL();y.setAttributeNS(e,"xlink:href",g),r.parentNode.insertBefore(y,r),r.style.fill="none";const w=document.createElementNS(t,"use");w.setAttributeNS(e,"xlink:href","#"+o);const m="patchjs_clip"+n,M=document.createElementNS(t,"clipPath");M.setAttribute("id",m),M.appendChild(w),r.parentElement.insertBefore(M,r),y.setAttribute("clip-path","url(#"+m+")"),u=null,l=null,g=null}}if(a&&a[1]){const o=document.getElementById(a[1]);if(o&&"meshgradient"===o.nodeName){const i=parseFloat(r.style.strokeWidth.slice(0,-2))*(parseFloat(r.style.strokeMiterlimit)||parseFloat(r.getAttribute("stroke-miterlimit"))||1),a=r.getBBox(),l=Math.trunc(a.width+i),c=Math.trunc(a.height+i),u=Math.trunc(a.x-i/2),f=Math.trunc(a.y-i/2);let p=document.createElementNS(s,"canvas");d(p,{width:l,height:c});const y=p.getContext("2d");let g=y.createImageData(l,c);const w=new b(o);"objectBoundingBox"===o.getAttribute("gradientUnits")&&w.scale(new x(l,c));const m=o.getAttribute("gradientTransform");null!=m&&w.transform(h(m)),"userSpaceOnUse"===o.getAttribute("gradientUnits")&&w.transform(new x(-u,-f)),w.paintMesh(g.data,p.width),y.putImageData(g,0,0);const M=document.createElementNS(t,"image");d(M,{width:l,height:c,x:0,y:0});let S=p.toDataURL();M.setAttributeNS(e,"xlink:href",S);const k="pattern_clip"+n,A=document.createElementNS(t,"pattern");d(A,{id:k,patternUnits:"userSpaceOnUse",width:l,height:c,x:u,y:f}),A.appendChild(M),o.parentNode.appendChild(A),r.style.stroke="url(#"+k+")",g=null,p=null,S=null}}})}(); +)=====" diff --git a/src/extension/internal/pov-out.cpp b/src/extension/internal/pov-out.cpp new file mode 100644 index 0000000..e9d0a0c --- /dev/null +++ b/src/extension/internal/pov-out.cpp @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A simple utility for exporting Inkscape svg Shapes as PovRay bezier + * prisms. Note that this is output-only, and would thus seem to be + * better placed as an 'export' rather than 'output'. However, Export + * handles all or partial documents, while this outputs ALL shapes in + * the current SVG document. + * + * For information on the PovRay file format, see: + * http://www.povray.org + * + * Authors: + * Bob Jamison <ishmal@inkscape.org> + * Abhishek Sharma + * + * Copyright (C) 2004-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "pov-out.h" +#include <inkscape.h> +#include <inkscape-version.h> +#include <display/curve.h> +#include <extension/system.h> +#include <2geom/pathvector.h> +#include <2geom/rect.h> +#include <2geom/curves.h> +#include "helper/geom.h" +#include "helper/geom-curves.h" +#include <io/sys.h> + +#include "object/sp-root.h" +#include "object/sp-path.h" +#include "style.h" + +#include <string> +#include <cstdio> +#include <cstdarg> +#include "document.h" +#include "extension/extension.h" + + +namespace Inkscape +{ +namespace Extension +{ +namespace Internal +{ + + +//######################################################################## +//# M E S S A G E S +//######################################################################## + +static void err(const char *fmt, ...) +{ + va_list args; + g_log(nullptr, G_LOG_LEVEL_WARNING, "Pov-out err: "); + va_start(args, fmt); + g_logv(nullptr, G_LOG_LEVEL_WARNING, fmt, args); + va_end(args); + g_log(nullptr, G_LOG_LEVEL_WARNING, "\n"); +} + + + + +//######################################################################## +//# U T I L I T Y +//######################################################################## + + + +static double effective_opacity(SPItem const *item) +{ + // TODO investigate this. The early return seems that it would abort early. + // Plus is will emit a warning, which may not be proper here. + double ret = 1.0; + for (SPObject const *obj = item; obj; obj = obj->parent) { + g_return_val_if_fail(obj->style, ret); + ret *= SP_SCALE24_TO_FLOAT(obj->style->opacity.value); + } + return ret; +} + + + + + +//######################################################################## +//# OUTPUT FORMATTING +//######################################################################## + +PovOutput::PovOutput() : + outbuf (), + nrNodes (0), + nrSegments (0), + nrShapes (0), + idIndex (0), + minx (0), + miny (0), + maxx (0), + maxy (0) +{ +} + +/** + * We want to control floating output format + */ +static PovOutput::String dstr(double d) +{ + char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1]; + g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE, + "%.8f", (gdouble)d); + PovOutput::String s = dbuf; + return s; +} + +#define DSTR(d) (dstr(d).c_str()) + + +/** + * Output data to the buffer, printf()-style + */ +void PovOutput::out(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + gchar *output = g_strdup_vprintf(fmt, args); + va_end(args); + outbuf.append(output); + g_free(output); +} + + + + + +/** + * Output a 2d vector + */ +void PovOutput::vec2(double a, double b) +{ + out("<%s, %s>", DSTR(a), DSTR(b)); +} + + + +/** + * Output a 3d vector + */ +void PovOutput::vec3(double a, double b, double c) +{ + out("<%s, %s, %s>", DSTR(a), DSTR(b), DSTR(c)); +} + + + +/** + * Output a v4d ector + */ +void PovOutput::vec4(double a, double b, double c, double d) +{ + out("<%s, %s, %s, %s>", DSTR(a), DSTR(b), DSTR(c), DSTR(d)); +} + + + +/** + * Output an rgbf color vector + */ +void PovOutput::rgbf(double r, double g, double b, double f) +{ + //"rgbf < %1.3f, %1.3f, %1.3f %1.3f>" + out("rgbf "); + vec4(r, g, b, f); +} + + + +/** + * Output one bezier's start, start-control, end-control, and end nodes + */ +void PovOutput::segment(int segNr, + double startX, double startY, + double startCtrlX, double startCtrlY, + double endCtrlX, double endCtrlY, + double endX, double endY) +{ + //" /*%4d*/ <%f, %f>, <%f, %f>, <%f,%f>, <%f,%f>" + out(" /*%4d*/ ", segNr); + vec2(startX, startY); + out(", "); + vec2(startCtrlX, startCtrlY); + out(", "); + vec2(endCtrlX, endCtrlY); + out(", "); + vec2(endX, endY); +} + + + + + +/** + * Output the file header + */ +bool PovOutput::doHeader() +{ + time_t tim = time(nullptr); + out("/*###################################################################\n"); + out("### This PovRay document was generated by Inkscape\n"); + out("### http://www.inkscape.org\n"); + out("### Created: %s", ctime(&tim)); + out("### Version: %s\n", Inkscape::version_string); + out("#####################################################################\n"); + out("### NOTES:\n"); + out("### ============\n"); + out("### POVRay information can be found at\n"); + out("### http://www.povray.org\n"); + out("###\n"); + out("### The 'AllShapes' objects at the bottom are provided as a\n"); + out("### preview of how the output would look in a trace. However,\n"); + out("### the main intent of this file is to provide the individual\n"); + out("### shapes for inclusion in a POV project.\n"); + out("###\n"); + out("### For an example of how to use this file, look at\n"); + out("### share/examples/istest.pov\n"); + out("###\n"); + out("### If you have any problems with this output, please see the\n"); + out("### Inkscape project at http://www.inkscape.org, or visit\n"); + out("### the #inkscape channel on irc.freenode.net . \n"); + out("###\n"); + out("###################################################################*/\n"); + out("\n\n"); + out("/*###################################################################\n"); + out("## Exports in this file\n"); + out("##==========================\n"); + out("## Shapes : %d\n", nrShapes); + out("## Segments : %d\n", nrSegments); + out("## Nodes : %d\n", nrNodes); + out("###################################################################*/\n"); + out("\n\n\n"); + return true; +} + + + +/** + * Output the file footer + */ +bool PovOutput::doTail() +{ + out("\n\n"); + out("/*###################################################################\n"); + out("### E N D F I L E\n"); + out("###################################################################*/\n"); + out("\n\n"); + return true; +} + + + +/** + * Output the curve data to buffer + */ +bool PovOutput::doCurve(SPItem *item, const String &id) +{ + using Geom::X; + using Geom::Y; + + //### Get the Shape + if (!SP_IS_SHAPE(item))//Bulia's suggestion. Allow all shapes + return true; + + SPShape *shape = SP_SHAPE(item); + if (shape->_curve->is_empty()) { + return true; + } + + nrShapes++; + + PovShapeInfo shapeInfo; + shapeInfo.id = id; + shapeInfo.color = ""; + + //Try to get the fill color of the shape + SPStyle *style = shape->style; + /* fixme: Handle other fill types, even if this means translating gradients to a single + flat colour. */ + if (style) + { + if (style->fill.isColor()) + { + // see color.h for how to parse SPColor + float rgb[3]; + style->fill.value.color.get_rgb_floatv(rgb); + double const dopacity = ( SP_SCALE24_TO_FLOAT(style->fill_opacity.value) + * effective_opacity(shape) ); + //gchar *str = g_strdup_printf("rgbf < %1.3f, %1.3f, %1.3f %1.3f>", + // rgb[0], rgb[1], rgb[2], 1.0 - dopacity); + String rgbf = "rgbf <"; + rgbf.append(dstr(rgb[0])); rgbf.append(", "); + rgbf.append(dstr(rgb[1])); rgbf.append(", "); + rgbf.append(dstr(rgb[2])); rgbf.append(", "); + rgbf.append(dstr(1.0 - dopacity)); rgbf.append(">"); + shapeInfo.color += rgbf; + } + } + + povShapes.push_back(shapeInfo); //passed all tests. save the info + + // convert the path to only lineto's and cubic curveto's: + Geom::Affine tf = item->i2dt_affine(); + Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers( shape->_curve->get_pathvector() * tf ); + + /* + * We need to know the number of segments (NR_CURVETOs/LINETOs, including + * closing line segment) before we write out segment data. Since we are + * going to skip degenerate (zero length) paths, we need to loop over all + * subpaths and segments first. + */ + int segmentCount = 0; + /** + * For all Subpaths in the <path> + */ + for (const auto & pit : pathv) + { + /** + * For all segments in the subpath, including extra closing segment defined by 2geom + */ + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_closed(); ++cit) + { + + // Skip zero length segments. + if( !cit->isDegenerate() ) ++segmentCount; + } + } + + out("/*###################################################\n"); + out("### PRISM: %s\n", id.c_str()); + out("###################################################*/\n"); + out("#declare %s = prism {\n", id.c_str()); + out(" linear_sweep\n"); + out(" bezier_spline\n"); + out(" 1.0, //top\n"); + out(" 0.0, //bottom\n"); + out(" %d //nr points\n", segmentCount * 4); + int segmentNr = 0; + + nrSegments += segmentCount; + + /** + * at moment of writing, 2geom lacks proper initialization of empty intervals in rect... + */ + Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() ); + + + /** + * For all Subpaths in the <path> + */ + for (const auto & pit : pathv) + { + + cminmax.expandTo(pit.initialPoint()); + + /** + * For all segments in the subpath, including extra closing segment defined by 2geom + */ + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_closed(); ++cit) + { + + // Skip zero length segments + if( cit->isDegenerate() ) + continue; + + if( is_straight_curve(*cit) ) + { + Geom::Point p0 = cit->initialPoint(); + Geom::Point p1 = cit->finalPoint(); + segment(segmentNr++, + p0[X], p0[Y], p0[X], p0[Y], p1[X], p1[Y], p1[X], p1[Y] ); + nrNodes += 8; + } + else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) + { + std::vector<Geom::Point> points = cubic->controlPoints(); + Geom::Point p0 = points[0]; + Geom::Point p1 = points[1]; + Geom::Point p2 = points[2]; + Geom::Point p3 = points[3]; + segment(segmentNr++, + p0[X],p0[Y], p1[X],p1[Y], p2[X],p2[Y], p3[X],p3[Y]); + nrNodes += 8; + } + else + { + err("logical error, because pathv_to_linear_and_cubic_beziers was used"); + return false; + } + + if (segmentNr < segmentCount) + out(",\n"); + else + out("\n"); + if (segmentNr > segmentCount) + { + err("Too many segments"); + return false; + } + + cminmax.expandTo(cit->finalPoint()); + + } + } + + out("}\n"); + + double cminx = cminmax.min()[X]; + double cmaxx = cminmax.max()[X]; + double cminy = cminmax.min()[Y]; + double cmaxy = cminmax.max()[Y]; + + out("#declare %s_MIN_X = %s;\n", id.c_str(), DSTR(cminx)); + out("#declare %s_CENTER_X = %s;\n", id.c_str(), DSTR((cmaxx+cminx)/2.0)); + out("#declare %s_MAX_X = %s;\n", id.c_str(), DSTR(cmaxx)); + out("#declare %s_WIDTH = %s;\n", id.c_str(), DSTR(cmaxx-cminx)); + out("#declare %s_MIN_Y = %s;\n", id.c_str(), DSTR(cminy)); + out("#declare %s_CENTER_Y = %s;\n", id.c_str(), DSTR((cmaxy+cminy)/2.0)); + out("#declare %s_MAX_Y = %s;\n", id.c_str(), DSTR(cmaxy)); + out("#declare %s_HEIGHT = %s;\n", id.c_str(), DSTR(cmaxy-cminy)); + if (shapeInfo.color.length()>0) + out("#declare %s_COLOR = %s;\n", + id.c_str(), shapeInfo.color.c_str()); + out("/*###################################################\n"); + out("### end %s\n", id.c_str()); + out("###################################################*/\n\n\n\n"); + + if (cminx < minx) + minx = cminx; + if (cmaxx > maxx) + maxx = cmaxx; + if (cminy < miny) + miny = cminy; + if (cmaxy > maxy) + maxy = cmaxy; + + return true; +} + +/** + * Descend the svg tree recursively, translating data + */ +bool PovOutput::doTreeRecursive(SPDocument *doc, SPObject *obj) +{ + + String id; + if (!obj->getId()) + { + char buf[16]; + sprintf(buf, "id%d", idIndex++); + id = buf; + } + else + { + id = obj->getId(); + } + + if (SP_IS_ITEM(obj)) + { + SPItem *item = SP_ITEM(obj); + if (!doCurve(item, id)) + return false; + } + + /** + * Descend into children + */ + for (auto &child: obj->children) + { + if (!doTreeRecursive(doc, &child)) + return false; + } + + return true; +} + +/** + * Output the curve data to buffer + */ +bool PovOutput::doTree(SPDocument *doc) +{ + double bignum = 1000000.0; + minx = bignum; + maxx = -bignum; + miny = bignum; + maxy = -bignum; + + if (!doTreeRecursive(doc, doc->getRoot())) + return false; + + //## Let's make a union of all of the Shapes + if (!povShapes.empty()) + { + String id = "AllShapes"; + char *pfx = (char *)id.c_str(); + out("/*###################################################\n"); + out("### UNION OF ALL SHAPES IN DOCUMENT\n"); + out("###################################################*/\n"); + out("\n\n"); + out("/**\n"); + out(" * Allow the user to redefine the finish{}\n"); + out(" * by declaring it before #including this file\n"); + out(" */\n"); + out("#ifndef (%s_Finish)\n", pfx); + out("#declare %s_Finish = finish {\n", pfx); + out(" phong 0.5\n"); + out(" reflection 0.3\n"); + out(" specular 0.5\n"); + out("}\n"); + out("#end\n"); + out("\n\n"); + out("#declare %s = union {\n", id.c_str()); + for (auto & povShape : povShapes) + { + out(" object { %s\n", povShape.id.c_str()); + out(" texture { \n"); + if (povShape.color.length()>0) + out(" pigment { %s }\n", povShape.color.c_str()); + else + out(" pigment { rgb <0,0,0> }\n"); + out(" finish { %s_Finish }\n", pfx); + out(" } \n"); + out(" } \n"); + } + out("}\n\n\n\n"); + + + double zinc = 0.2 / (double)povShapes.size(); + out("/*#### Same union, but with Z-diffs (actually Y in pov) ####*/\n"); + out("\n\n"); + out("/**\n"); + out(" * Allow the user to redefine the Z-Increment\n"); + out(" */\n"); + out("#ifndef (AllShapes_Z_Increment)\n"); + out("#declare AllShapes_Z_Increment = %s;\n", DSTR(zinc)); + out("#end\n"); + out("\n"); + out("#declare AllShapes_Z_Scale = 1.0;\n"); + out("\n\n"); + out("#declare %s_Z = union {\n", pfx); + + for (auto & povShape : povShapes) + { + out(" object { %s\n", povShape.id.c_str()); + out(" texture { \n"); + if (povShape.color.length()>0) + out(" pigment { %s }\n", povShape.color.c_str()); + else + out(" pigment { rgb <0,0,0> }\n"); + out(" finish { %s_Finish }\n", pfx); + out(" } \n"); + out(" scale <1, %s_Z_Scale, 1>\n", pfx); + out(" } \n"); + out("#declare %s_Z_Scale = %s_Z_Scale + %s_Z_Increment;\n\n", + pfx, pfx, pfx); + } + + out("}\n"); + + out("#declare %s_MIN_X = %s;\n", pfx, DSTR(minx)); + out("#declare %s_CENTER_X = %s;\n", pfx, DSTR((maxx+minx)/2.0)); + out("#declare %s_MAX_X = %s;\n", pfx, DSTR(maxx)); + out("#declare %s_WIDTH = %s;\n", pfx, DSTR(maxx-minx)); + out("#declare %s_MIN_Y = %s;\n", pfx, DSTR(miny)); + out("#declare %s_CENTER_Y = %s;\n", pfx, DSTR((maxy+miny)/2.0)); + out("#declare %s_MAX_Y = %s;\n", pfx, DSTR(maxy)); + out("#declare %s_HEIGHT = %s;\n", pfx, DSTR(maxy-miny)); + out("/*##############################################\n"); + out("### end %s\n", id.c_str()); + out("##############################################*/\n"); + out("\n\n"); + } + + return true; +} + + +//######################################################################## +//# M A I N O U T P U T +//######################################################################## + + + +/** + * Set values back to initial state + */ +void PovOutput::reset() +{ + nrNodes = 0; + nrSegments = 0; + nrShapes = 0; + idIndex = 0; + outbuf.clear(); + povShapes.clear(); +} + + + +/** + * Saves the Shapes of an Inkscape SVG file as PovRay spline definitions + */ +void PovOutput::saveDocument(SPDocument *doc, gchar const *filename_utf8) +{ + reset(); + + //###### SAVE IN POV FORMAT TO BUFFER + //# Lets do the curves first, to get the stats + if (!doTree(doc)) + { + err("Could not output curves for %s", filename_utf8); + return; + } + + String curveBuf = outbuf; + outbuf.clear(); + + if (!doHeader()) + { + err("Could not write header for %s", filename_utf8); + return; + } + + outbuf.append(curveBuf); + + if (!doTail()) + { + err("Could not write footer for %s", filename_utf8); + return; + } + + + + + //###### WRITE TO FILE + Inkscape::IO::dump_fopen_call(filename_utf8, "L"); + FILE *f = Inkscape::IO::fopen_utf8name(filename_utf8, "w"); + if (!f) + return; + + for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); ++iter) + { + int ch = *iter; + fputc(ch, f); + } + + fclose(f); +} + + + + +//######################################################################## +//# EXTENSION API +//######################################################################## + + + +#include "clear-n_.h" + + + +/** + * API call to save document +*/ +void +PovOutput::save(Inkscape::Extension::Output */*mod*/, + SPDocument *doc, gchar const *filename_utf8) +{ + /* See comments in JavaFSOutput::save re the name `filename_utf8'. */ + saveDocument(doc, filename_utf8); +} + + + +/** + * Make sure that we are in the database + */ +bool PovOutput::check (Inkscape::Extension::Extension */*module*/) +{ + /* We don't need a Key + if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV)) + return FALSE; + */ + + return true; +} + + + +/** + * This is the definition of PovRay output. This function just + * calls the extension system with the memory allocated XML that + * describes the data. +*/ +void +PovOutput::init() +{ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("PovRay Output") "</name>\n" + "<id>org.inkscape.output.pov</id>\n" + "<output>\n" + "<extension>.pov</extension>\n" + "<mimetype>text/x-povray-script</mimetype>\n" + "<filetypename>" N_("PovRay (*.pov) (paths and shapes only)") "</filetypename>\n" + "<filetypetooltip>" N_("PovRay Raytracer File") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", + new PovOutput()); +} + + + + + +} // namespace Internal +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/pov-out.h b/src/extension/internal/pov-out.h new file mode 100644 index 0000000..3dee88b --- /dev/null +++ b/src/extension/internal/pov-out.h @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * A simple utility for exporting Inkscape svg Shapes as PovRay bezier + * prisms. Note that this is output-only, and would thus seem to be + * better placed as an 'export' rather than 'output'. However, Export + * handles all or partial documents, while this outputs ALL shapes in + * the current SVG document. + * + * Authors: + * Bob Jamison <ishmal@inkscape.org> + * + * Copyright (C) 2004-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef EXTENSION_INTERNAL_POV_OUT_H +#define EXTENSION_INTERNAL_POV_OUT_H + +#include <glib.h> +#include "extension/implementation/implementation.h" + +class SPObject; +class SPItem; + +namespace Inkscape +{ +namespace Extension +{ +namespace Internal +{ + + + +/** + * Output bezier splines in POVRay format. + * + * For information, @see: + * http://www.povray.org + */ +class PovOutput : public Inkscape::Extension::Implementation::Implementation +{ + + +public: + + PovOutput(); + + /** + * Our internal String definition + */ + typedef Glib::ustring String; + + + /** + * Check whether we can actually output using this module + */ + bool check (Inkscape::Extension::Extension * module) override; + + /** + * API call to perform the output to a file + */ + void save(Inkscape::Extension::Output *mod, + SPDocument *doc, gchar const *filename) override; + + /** + * Inkscape runtime startup call. + */ + static void init(); + + /** + * Reset variables to initial state + */ + void reset(); + +private: + + /** + * Format text to our output buffer + */ + void out(const char *fmt, ...) G_GNUC_PRINTF(2,3); + + /** + * Output a 2d vector + */ + void vec2(double a, double b); + + /** + * Output a 3d vector + */ + void vec3(double a, double b, double c); + + /** + * Output a 4d vector + */ + void vec4(double a, double b, double c, double d); + + /** + * Output an rgbf color vector + */ + void rgbf(double r, double g, double b, double f); + + /** + * Output one bezier's start, start-control, + * end-control, and end nodes + */ + void segment(int segNr, double a0, double a1, + double b0, double b1, + double c0, double c1, + double d0, double d1); + + + /** + * Output the file header + */ + bool doHeader(); + + /** + * Output the file footer + */ + bool doTail(); + + /** + * Output the SVG document's curve data as POV curves + */ + bool doCurve(SPItem *item, const String &id); + bool doTreeRecursive(SPDocument *doc, SPObject *obj); + bool doTree(SPDocument *doc); + + /** + * Actual method to save document + */ + void saveDocument(SPDocument *doc, gchar const *filename); + + + /** + * used for saving information about shapes + */ + class PovShapeInfo + { + public: + PovShapeInfo() + = default; + PovShapeInfo(const PovShapeInfo &other) + { assign(other); } + PovShapeInfo& operator=(const PovShapeInfo &other) + { assign(other); return *this; } + virtual ~PovShapeInfo() + = default; + String id; + String color; + + private: + void assign(const PovShapeInfo &other) + { + id = other.id; + color = other.color; + } + }; + + //A list for saving information about the shapes + std::vector<PovShapeInfo> povShapes; + + //For formatted output + String outbuf; + + //For statistics + int nrNodes; + int nrSegments; + int nrShapes; + int idIndex; + + double minx; + double miny; + double maxx; + double maxy; + +}; + + + + +} // namespace Internal +} // namespace Extension +} // namespace Inkscape + + + +#endif /* EXTENSION_INTERNAL_POV_OUT_H */ + diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp new file mode 100644 index 0000000..6eb7863 --- /dev/null +++ b/src/extension/internal/svg.cpp @@ -0,0 +1,1045 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is the code that moves all of the SVG loading and saving into + * the module format. Really Inkscape is built to handle these formats + * internally, so this is just calling those internal functions. + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ted Gould <ted@gould.cx> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2002-2003 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm.h> + +#include <giomm/file.h> +#include <giomm/file.h> + +#include "document.h" +#include "inkscape.h" +#include "preferences.h" +#include "extension/output.h" +#include "extension/input.h" +#include "extension/system.h" +#include "file.h" +#include "svg.h" +#include "file.h" +#include "display/cairo-utils.h" +#include "extension/system.h" +#include "extension/output.h" +#include "xml/attribute-record.h" +#include "xml/simple-document.h" + +#include "object/sp-image.h" +#include "object/sp-root.h" +#include "object/sp-text.h" + +#include "util/units.h" +#include "selection-chemistry.h" + +// TODO due to internal breakage in glibmm headers, this must be last: +#include <glibmm/i18n.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { + +#include "clear-n_.h" + + +using Inkscape::Util::List; +using Inkscape::XML::AttributeRecord; +using Inkscape::XML::Node; + +/* + * Removes all sodipodi and inkscape elements and attributes from an xml tree. + * used to make plain svg output. + */ +static void pruneExtendedNamespaces( Inkscape::XML::Node *repr ) +{ + if (repr) { + if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) { + std::vector<gchar const*> attrsRemoved; + for ( List<AttributeRecord const> it = repr->attributeList(); it; ++it ) { + const gchar* attrName = g_quark_to_string(it->key); + if ((strncmp("inkscape:", attrName, 9) == 0) || (strncmp("sodipodi:", attrName, 9) == 0)) { + attrsRemoved.push_back(attrName); + } + } + // Can't change the set we're iterating over while we are iterating. + for (auto & it : attrsRemoved) { + repr->removeAttribute(it); + } + } + + std::vector<Inkscape::XML::Node *> nodesRemoved; + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + if((strncmp("inkscape:", child->name(), 9) == 0) || strncmp("sodipodi:", child->name(), 9) == 0) { + nodesRemoved.push_back(child); + } else { + pruneExtendedNamespaces(child); + } + } + for (auto & it : nodesRemoved) { + repr->removeChild(it); + } + } +} + +/* + * Similar to the above sodipodi and inkscape prune, but used on all documents + * to remove problematic elements (for example Adobe's i:pgf tag) only removes + * known garbage tags. + */ +static void pruneProprietaryGarbage( Inkscape::XML::Node *repr ) +{ + if (repr) { + std::vector<Inkscape::XML::Node *> nodesRemoved; + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + if((strncmp("i:pgf", child->name(), 5) == 0)) { + nodesRemoved.push_back(child); + g_warning( "An Adobe proprietary tag was found which is known to cause issues. It was removed before saving."); + } else { + pruneProprietaryGarbage(child); + } + } + for (auto & it : nodesRemoved) { + repr->removeChild(it); + } + } +} + +/** + * \return None + * + * \brief Create new markers where necessary to simulate the SVG 2 marker attribute 'orient' + * value 'auto-start-reverse'. + * + * \param repr The current element to check. + * \param defs A pointer to the <defs> element. + * \param css The properties of the element to check. + * \param property Which property to check, either 'marker' or 'marker-start'. + * + */ +static void remove_marker_auto_start_reverse(Inkscape::XML::Node *repr, + Inkscape::XML::Node *defs, + SPCSSAttr *css, + Glib::ustring const &property) +{ + Glib::ustring value = sp_repr_css_property (css, property.c_str(), ""); + + if (!value.empty()) { + + // Find reference <marker> + static Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("url\\(#([A-z0-9#]*)\\)"); + Glib::MatchInfo matchInfo; + regex->match(value, matchInfo); + + if (matchInfo.matches()) { + + std::string marker_name = matchInfo.fetch(1); + Inkscape::XML::Node *marker = sp_repr_lookup_child (defs, "id", marker_name.c_str()); + if (marker) { + + // Does marker use "auto-start-reverse"? + if (strncmp(marker->attribute("orient"), "auto-start-reverse", 17)==0) { + + // See if a reversed marker already exists. + Glib::ustring marker_name_reversed = marker_name + "_reversed"; + Inkscape::XML::Node *marker_reversed = + sp_repr_lookup_child (defs, "id", marker_name_reversed.c_str()); + + if (!marker_reversed) { + + // No reversed marker, need to create! + marker_reversed = repr->document()->createElement("svg:marker"); + + // Copy attributes + for (List<AttributeRecord const> iter = marker->attributeList(); + iter ; ++iter) { + marker_reversed->setAttribute(g_quark_to_string(iter->key), iter->value); + } + + // Override attributes + marker_reversed->setAttribute("id", marker_name_reversed); + marker_reversed->setAttribute("orient", "auto"); + + // Find transform + const char* refX = marker_reversed->attribute("refX"); + const char* refY = marker_reversed->attribute("refY"); + std::string transform = "rotate(180"; + if (refX) { + transform += ","; + transform += refX; + + if (refY) { + if (refX) { + transform += ","; + transform += refY; + } else { + transform += ",0,"; + transform += refY; + } + } + } + transform += ")"; + + // We can't set a transform on a marker... must create group first. + Inkscape::XML::Node *group = repr->document()->createElement("svg:g"); + group->setAttribute("transform", transform); + marker_reversed->addChild(group, nullptr); + + // Copy all marker content to group. + for (auto child = marker->firstChild() ; child != nullptr ; child = child->next() ) { + auto new_child = child->duplicate(repr->document()); + group->addChild(new_child, nullptr); + new_child->release(); + } + + // Add new marker to <defs>. + defs->addChild(marker_reversed, marker); + marker_reversed->release(); + } + + // Change url to reference reversed marker. + std::string marker_url("url(#" + marker_name_reversed + ")"); + sp_repr_css_set_property(css, "marker-start", marker_url.c_str()); + + // Also fix up if property is marker shorthand. + if (property == "marker") { + std::string marker_old_url("url(#" + marker_name + ")"); + sp_repr_css_unset_property(css, "marker"); + sp_repr_css_set_property(css, "marker-mid", marker_old_url.c_str()); + sp_repr_css_set_property(css, "marker-end", marker_old_url.c_str()); + } + + sp_repr_css_set(repr, css, "style"); + + } // Uses auto-start-reverse + } + } + } +} + +// Called by remove_marker_context_paint() for each property value ("marker", "marker-start", ...). +static void remove_marker_context_paint (Inkscape::XML::Node *repr, + Inkscape::XML::Node *defs, + Glib::ustring property) +{ + // Value of 'marker', 'marker-start', ... property. + std::string value("url(#"); + value += repr->attribute("id"); + value += ")"; + + // Generate a list of elements that reference this marker. + std::vector<Inkscape::XML::Node *> to_fix_fill_stroke = + sp_repr_lookup_property_many(repr->root(), property, value); + + for (auto it: to_fix_fill_stroke) { + + // Figure out value of fill... could be inherited. + SPCSSAttr* css = sp_repr_css_attr_inherited (it, "style"); + Glib::ustring fill = sp_repr_css_property (css, "fill", ""); + Glib::ustring stroke = sp_repr_css_property (css, "stroke", ""); + + // Name of new marker. + Glib::ustring marker_fixed_id = repr->attribute("id"); + if (!fill.empty()) { + marker_fixed_id += "_F" + fill; + } + if (!stroke.empty()) { + marker_fixed_id += "_S" + stroke; + } + + // See if a fixed marker already exists. + // Could be more robust, assumes markers are direct children of <defs>. + Inkscape::XML::Node* marker_fixed = sp_repr_lookup_child(defs, "id", marker_fixed_id.c_str()); + + if (!marker_fixed) { + + // Need to create new marker. + + marker_fixed = repr->duplicate(repr->document()); + marker_fixed->setAttribute("id", marker_fixed_id); + + // This needs to be turned into a function that fixes all descendents. + for (auto child = marker_fixed->firstChild() ; child != nullptr ; child = child->next()) { + // Find style. + SPCSSAttr* css = sp_repr_css_attr ( child, "style" ); + + Glib::ustring fill2 = sp_repr_css_property (css, "fill", ""); + if (fill2 == "context-fill" ) { + sp_repr_css_set_property (css, "fill", fill.c_str()); + } + if (fill2 == "context-stroke" ) { + sp_repr_css_set_property (css, "fill", stroke.c_str()); + } + + Glib::ustring stroke2 = sp_repr_css_property (css, "stroke", ""); + if (stroke2 == "context-fill" ) { + sp_repr_css_set_property (css, "stroke", fill.c_str()); + } + if (stroke2 == "context-stroke" ) { + sp_repr_css_set_property (css, "stroke", stroke.c_str()); + } + + sp_repr_css_set(child, css, "style"); + sp_repr_css_attr_unref(css); + } + + defs->addChild(marker_fixed, repr); + marker_fixed->release(); + } + + Glib::ustring marker_value = "url(#" + marker_fixed_id + ")"; + sp_repr_css_set_property (css, property.c_str(), marker_value.c_str()); + sp_repr_css_set (it, css, "style"); + sp_repr_css_attr_unref(css); + } +} + +static void remove_marker_context_paint (Inkscape::XML::Node *repr, + Inkscape::XML::Node *defs) +{ + if (strncmp("svg:marker", repr->name(), 10) == 0) { + + if (!repr->attribute("id")) { + + std::cerr << "remove_marker_context_paint: <marker> without 'id'!" << std::endl; + + } else { + + // First see if we need to do anything. + bool need_to_fix = false; + + // This needs to be turned into a function that searches all descendents. + for (auto child = repr->firstChild() ; child != nullptr ; child = child->next()) { + + // Find style. + SPCSSAttr* css = sp_repr_css_attr ( child, "style" ); + Glib::ustring fill = sp_repr_css_property (css, "fill", ""); + Glib::ustring stroke = sp_repr_css_property (css, "stroke", ""); + if (fill == "context-fill" || + fill == "context-stroke" || + stroke == "context-fill" || + stroke == "context-stroke" ) { + need_to_fix = true; + break; + } + sp_repr_css_attr_unref(css); + } + + if (need_to_fix) { + + // Now we need to search document for all elements that use this marker. + remove_marker_context_paint (repr, defs, "marker"); + remove_marker_context_paint (repr, defs, "marker-start"); + remove_marker_context_paint (repr, defs, "marker-mid"); + remove_marker_context_paint (repr, defs, "marker-end"); + } + } + } +} + +/* + * Recursively insert SVG 1.1 fallback for SVG 2 text (ignored by SVG 2 renderers including ours). + * Notes: + * Text must have been layed out. Access via old document. + */ +static void insert_text_fallback( Inkscape::XML::Node *repr, SPDocument *original_doc, Inkscape::XML::Node *defs = nullptr ) +{ + if (repr) { + + if (strncmp("svg:text", repr->name(), 8) == 0) { + + auto id = repr->attribute("id"); + // std::cout << "insert_text_fallback: found text! id: " << (id?id:"null") << std::endl; + + // We need to get original SPText object to access layout. + SPText* text = static_cast<SPText *>(original_doc->getObjectById( id )); + if (text == nullptr) { + std::cerr << "insert_text_fallback: bad cast" << std::endl; + return; + } + + if (!text->has_inline_size() && + !text->has_shape_inside()) { + // No SVG 2 text, nothing to do. + return; + } + + // We will keep this text node but replace all children. + + // For text in a shape, We need to unset 'text-anchor' or SVG 1.1 fallback won't work. + // Note 'text' here refers to original document while 'repr' refers to new document copy. + if (text->has_shape_inside()) { + SPCSSAttr *css = sp_repr_css_attr(repr, "style" ); + sp_repr_css_unset_property(css, "text-anchor"); + sp_repr_css_set(repr, css, "style"); + sp_repr_css_attr_unref(css); + } + + // We need to put trailing white space into it's own tspan for inline size so + // it is excluded during calculation of line position in SVG 1.1 renderers. + bool trim = text->has_inline_size() && + !(text->style->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START); + + // Make a list of children to delete at end: + std::vector<Inkscape::XML::Node *> old_children; + for (auto child = repr->firstChild(); child; child = child->next()) { + old_children.push_back(child); + } + + // For round-tripping, xml:space (or 'white-space:pre') must be set. + repr->setAttribute("xml:space", "preserve"); + + Geom::Point text_anchor_point = text->layout.characterAnchorPoint(text->layout.begin()); + // std::cout << " text_anchor_point: " << text_anchor_point << std::endl; + + double text_x = 0.0; + double text_y = 0.0; + sp_repr_get_double(repr, "x", &text_x); + sp_repr_get_double(repr, "y", &text_y); + // std::cout << "text_x: " << text_x << " text_y: " << text_y << std::endl; + + // Loop over all lines in layout. + for (auto it = text->layout.begin() ; it != text->layout.end() ; ) { + + // Create a <tspan> with 'x' and 'y' for each line. + Inkscape::XML::Node *line_tspan = repr->document()->createElement("svg:tspan"); + + // This could be useful if one wants to edit in an old version of Inkscape but we + // need to check if it breaks anything: + // line_tspan->setAttribute("sodipodi:role", "line"); + + // Hide overflow tspan (one line of text). + if (text->layout.isHidden(it)) { + line_tspan->setAttribute("style", "visibility:hidden"); + } + + Geom::Point line_anchor_point = text->layout.characterAnchorPoint(it); + double line_x = line_anchor_point[Geom::X]; + double line_y = line_anchor_point[Geom::Y]; + if (!text->is_horizontal()) { + std::swap(line_x, line_y); // Anchor points rotated & y inverted in vertical layout. + } + + // std::cout << " line_anchor_point: " << line_anchor_point << std::endl; + if (line_tspan->childCount() == 0) { + if (text->is_horizontal()) { + // std::cout << " horizontal: " << text_x << " " << line_anchor_point[Geom::Y] << std::endl; + if (text->has_inline_size()) { + // We use text_x as this is the reference for 'text-anchor' + // (line_x is the start of the line which gives wrong position when 'text-anchor' not start). + sp_repr_set_svg_double(line_tspan, "x", text_x); + } else { + // shape-inside (we don't have to worry about 'text-anchor'). + sp_repr_set_svg_double(line_tspan, "x", line_x); + } + sp_repr_set_svg_double(line_tspan, "y", line_y); // FIXME: this will pick up the wrong end of counter-directional runs + } else { + // std::cout << " vertical: " << line_anchor_point[Geom::X] << " " << text_y << std::endl; + sp_repr_set_svg_double(line_tspan, "x", line_x); // FIXME: this will pick up the wrong end of counter-directional runs + if (text->has_inline_size()) { + sp_repr_set_svg_double(line_tspan, "y", text_y); + } else { + sp_repr_set_svg_double(line_tspan, "y", line_y); + } + } + } + + // Inside line <tspan>, create <tspan>s for each change of style or shift. (No shifts in SVG 2 flowed text.) + // For simple lines, this creates an unneeded <tspan> but so be it. + Inkscape::Text::Layout::iterator it_line_end = it; + it_line_end.nextStartOfLine(); + + // Find last span in line so we can put trailing whitespace in its own tspan for SVG 1.1 fallback. + Inkscape::Text::Layout::iterator it_last_span = it; + it_last_span.nextStartOfLine(); + it_last_span.prevStartOfSpan(); + + Glib::ustring trailing_whitespace; + + // Loop over chunks in line + while (it != it_line_end) { + + Inkscape::XML::Node *span_tspan = repr->document()->createElement("svg:tspan"); + + // use kerning to simulate justification and whatnot + Inkscape::Text::Layout::iterator it_span_end = it; + it_span_end.nextStartOfSpan(); + Inkscape::Text::Layout::OptionalTextTagAttrs attrs; + text->layout.simulateLayoutUsingKerning(it, it_span_end, &attrs); + + // 'dx' and 'dy' attributes are used to simulated justified text. + if (!text->is_horizontal()) { + std::swap(attrs.dx, attrs.dy); + } + TextTagAttributes(attrs).writeTo(span_tspan); + SPObject *source_obj = nullptr; + Glib::ustring::iterator span_text_start_iter; + text->layout.getSourceOfCharacter(it, &source_obj, &span_text_start_iter); + + // Set tspan style + Glib::ustring style_text = (dynamic_cast<SPString *>(source_obj) ? source_obj->parent : source_obj)->style->write( SP_STYLE_FLAG_IFDIFF, SP_STYLE_SRC_UNSET, text->style); + if (!style_text.empty()) { + span_tspan->setAttributeOrRemoveIfEmpty("style", style_text); + } + + // Add text node + SPString *str = dynamic_cast<SPString *>(source_obj); + if (str) { + Glib::ustring *string = &(str->string); // TODO fixme: dangerous, unsafe premature-optimization + SPObject *span_end_obj = nullptr; + Glib::ustring::iterator span_text_end_iter; + text->layout.getSourceOfCharacter(it_span_end, &span_end_obj, &span_text_end_iter); + if (span_end_obj != source_obj) { + if (it_span_end == text->layout.end()) { + span_text_end_iter = span_text_start_iter; + for (int i = text->layout.iteratorToCharIndex(it_span_end) - text->layout.iteratorToCharIndex(it) ; i ; --i) + ++span_text_end_iter; + } else + span_text_end_iter = string->end(); // spans will never straddle a source boundary + } + + if (span_text_start_iter != span_text_end_iter) { + Glib::ustring new_string; + while (span_text_start_iter != span_text_end_iter) + new_string += *span_text_start_iter++; // grr. no substr() with iterators + + if (it == it_last_span && trim) { + // Found last span in line + const auto s = new_string.find_last_not_of(" \t"); // Any other white space characters needed? + trailing_whitespace = new_string.substr(s+1, new_string.length()); + new_string.erase(s+1); + } + + Inkscape::XML::Node *new_text = repr->document()->createTextNode(new_string.c_str()); + span_tspan->appendChild(new_text); + Inkscape::GC::release(new_text); + // std::cout << " new_string: |" << new_string << "|" << std::endl; + } + } + it = it_span_end; + + // Add tspan to document + line_tspan->appendChild(span_tspan); + Inkscape::GC::release(span_tspan); + } + + // Add line tspan to document + repr->appendChild(line_tspan); + Inkscape::GC::release(line_tspan); + + // For center and end justified text, we need to remove any spaces and put them + // into a separate tspan (alignment is done by "text chunk" and spaces at ends of + // line will mess this up). + if (trim && trailing_whitespace.length() != 0) { + + Inkscape::XML::Node *space_tspan = repr->document()->createElement("svg:tspan"); + // Set either 'x' or 'y' to force a new text chunk. To do: this really should + // be positioned at the end of the line (overhanging). + if (text->is_horizontal()) { + sp_repr_set_svg_double(space_tspan, "y", line_y); + } else { + sp_repr_set_svg_double(space_tspan, "x", line_x); + } + Inkscape::XML::Node *space = repr->document()->createTextNode(trailing_whitespace.c_str()); + space_tspan->appendChild(space); + Inkscape::GC::release(space); + line_tspan->appendChild(space_tspan); + Inkscape::GC::release(space_tspan); + } + + } + + for (auto i: old_children) { + repr->removeChild (i); + } + + return; // No need to look at children of <text> + } + + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + insert_text_fallback (child, original_doc, defs); + } + } +} + + +static void insert_mesh_polyfill( Inkscape::XML::Node *repr ) +{ + if (repr) { + + Inkscape::XML::Node *defs = sp_repr_lookup_name (repr, "svg:defs"); + + if (defs == nullptr) { + // We always put meshes in <defs>, no defs -> no mesh. + return; + } + + bool has_mesh = false; + for ( Node *child = defs->firstChild(); child; child = child->next() ) { + if (strncmp("svg:meshgradient", child->name(), 16) == 0) { + has_mesh = true; + break; + } + } + + Inkscape::XML::Node *script = sp_repr_lookup_child (repr, "id", "mesh_polyfill"); + + if (has_mesh && script == nullptr) { + + script = repr->document()->createElement("svg:script"); + script->setAttribute ("id", "mesh_polyfill"); + script->setAttribute ("type", "text/javascript"); + repr->root()->appendChild(script); // Must be last + + // Insert JavaScript via raw string literal. + Glib::ustring js = +#include "polyfill/mesh_compressed.include" +; + + Inkscape::XML::Node *script_text = repr->document()->createTextNode(js.c_str()); + script->appendChild(script_text); + } + } +} + + +static void insert_hatch_polyfill( Inkscape::XML::Node *repr ) +{ + if (repr) { + + Inkscape::XML::Node *defs = sp_repr_lookup_name (repr, "svg:defs"); + + if (defs == nullptr) { + // We always put meshes in <defs>, no defs -> no mesh. + return; + } + + bool has_hatch = false; + for ( Node *child = defs->firstChild(); child; child = child->next() ) { + if (strncmp("svg:hatch", child->name(), 16) == 0) { + has_hatch = true; + break; + } + } + + Inkscape::XML::Node *script = sp_repr_lookup_child (repr, "id", "hatch_polyfill"); + + if (has_hatch && script == nullptr) { + + script = repr->document()->createElement("svg:script"); + script->setAttribute ("id", "hatch_polyfill"); + script->setAttribute ("type", "text/javascript"); + repr->root()->appendChild(script); // Must be last + + // Insert JavaScript via raw string literal. + Glib::ustring js = +#include "polyfill/hatch_compressed.include" +; + + Inkscape::XML::Node *script_text = repr->document()->createTextNode(js.c_str()); + script->appendChild(script_text); + } + } +} + +/* + * Recursively transform SVG 2 to SVG 1.1, if possible. + */ +static void transform_2_to_1( Inkscape::XML::Node *repr, Inkscape::XML::Node *defs = nullptr ) +{ + if (repr) { + + // std::cout << "transform_2_to_1: " << repr->name() << std::endl; + + // Things we do once per node. ----------------------- + + // Find defs, if does not exist, create. + if (defs == nullptr) { + defs = sp_repr_lookup_name (repr, "svg:defs"); + } + if (defs == nullptr) { + defs = repr->document()->createElement("svg:defs"); + repr->root()->addChild(defs, nullptr); + } + + // Find style. + SPCSSAttr* css = sp_repr_css_attr ( repr, "style" ); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // Individual items ---------------------------------- + + // SVG 2 marker attribute orient:auto-start-reverse: + if ( prefs->getBool("/options/svgexport/marker_autostartreverse", false) ) { + // Do "marker-start" first for efficiency reasons. + remove_marker_auto_start_reverse(repr, defs, css, "marker-start"); + remove_marker_auto_start_reverse(repr, defs, css, "marker"); + } + + // SVG 2 paint values 'context-fill', 'context-stroke': + if ( prefs->getBool("/options/svgexport/marker_contextpaint", false) ) { + remove_marker_context_paint(repr, defs); + } + + // *** To Do *** + // Context fill & stroke outside of markers + // Paint-Order + // Meshes + // Hatches + + for ( Node *child = repr->firstChild(); child; child = child->next() ) { + transform_2_to_1 (child, defs); + } + + sp_repr_css_attr_unref(css); + } +} + + + + +/** + \return None + \brief What would an SVG editor be without loading/saving SVG + files. This function sets that up. + + For each module there is a call to Inkscape::Extension::build_from_mem + with a rather large XML file passed in. This is a constant string + that describes the module. At the end of this call a module is + returned that is basically filled out. The one thing that it doesn't + have is the key function for the operation. And that is linked at + the end of each call. +*/ +void +Svg::init() +{ + /* SVG in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVG Input") "</name>\n" + "<id>" SP_MODULE_KEY_INPUT_SVG "</id>\n" + SVG_COMMON_INPUT_PARAMS + "<input>\n" + "<extension>.svg</extension>\n" + "<mimetype>image/svg+xml</mimetype>\n" + "<filetypename>" N_("Scalable Vector Graphic (*.svg)") "</filetypename>\n" + "<filetypetooltip>" N_("Inkscape native file format and W3C standard") "</filetypetooltip>\n" + "<output_extension>" SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE "</output_extension>\n" + "</input>\n" + "</inkscape-extension>", new Svg()); + + /* SVG out Inkscape */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVG Output Inkscape") "</name>\n" + "<id>" SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE "</id>\n" + "<output>\n" + "<extension>.svg</extension>\n" + "<mimetype>image/x-inkscape-svg</mimetype>\n" + "<filetypename>" N_("Inkscape SVG (*.svg)") "</filetypename>\n" + "<filetypetooltip>" N_("SVG format with Inkscape extensions") "</filetypetooltip>\n" + "<dataloss>false</dataloss>\n" + "</output>\n" + "</inkscape-extension>", new Svg()); + + /* SVG out */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVG Output") "</name>\n" + "<id>" SP_MODULE_KEY_OUTPUT_SVG "</id>\n" + "<output>\n" + "<extension>.svg</extension>\n" + "<mimetype>image/svg+xml</mimetype>\n" + "<filetypename>" N_("Plain SVG (*.svg)") "</filetypename>\n" + "<filetypetooltip>" N_("Scalable Vector Graphics format as defined by the W3C") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new Svg()); + + return; +} + + +/** + \return A new document just for you! + \brief This function takes in a filename of a SVG document and + turns it into a SPDocument. + \param mod Module to use + \param uri The path or URI to the file (UTF-8) + + This function is really simple, it just calls sp_document_new... + That's BS, it does all kinds of things for importing documents + that probably should be in a separate function. + + Most of the import code was copied from gdkpixpuf-input.cpp. +*/ +SPDocument * +Svg::open (Inkscape::Extension::Input *mod, const gchar *uri) +{ + // This is only used at the end... but it should go here once uri stuff is fixed. + auto file = Gio::File::create_for_commandline_arg(uri); + const auto path = file->get_path(); + + // Fixing this means fixing a whole string of things. + // if (path.empty()) { + // // We lied, the uri wasn't a uri, try as path. + // file = Gio::File::create_for_path(uri); + // } + + // std::cout << "Svg::open: uri in: " << uri << std::endl; + // std::cout << " : uri: " << file->get_uri() << std::endl; + // std::cout << " : scheme: " << file->get_uri_scheme() << std::endl; + // std::cout << " : path: " << file->get_path() << std::endl; + // std::cout << " : parse: " << file->get_parse_name() << std::endl; + // std::cout << " : base: " << file->get_basename() << std::endl; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + + // Get import preferences. + bool ask_svg = prefs->getBool( "/dialogs/import/ask_svg"); + Glib::ustring import_mode_svg = prefs->getString("/dialogs/import/import_mode_svg"); + Glib::ustring scale = prefs->getString("/dialogs/import/scale"); + + // If we popped up a window asking about import preferences, get values from + // there and update preferences. + if(mod->get_gui() && ask_svg) { + ask_svg = !mod->get_param_bool("do_not_ask"); + import_mode_svg = mod->get_param_optiongroup("import_mode_svg"); + scale = mod->get_param_optiongroup("scale"); + + prefs->setBool( "/dialogs/import/ask_svg", ask_svg); + prefs->setString("/dialogs/import/import_mode_svg", import_mode_svg ); + prefs->setString("/dialogs/import/scale", scale ); + } + + // Do we "import" as <image>? + if (prefs->getBool("/options/onimport", false) && import_mode_svg != "include") { + // We import! + + // New wrapper document. + SPDocument * doc = SPDocument::createNewDoc (nullptr, true, true); + + // Imported document + // SPDocument * ret = SPDocument::createNewDoc(file->get_uri().c_str(), true); + SPDocument * ret = SPDocument::createNewDoc(uri, true); + + // Create image node + Inkscape::XML::Document *xml_doc = doc->getReprDoc(); + Inkscape::XML::Node *image_node = xml_doc->createElement("svg:image"); + + // Set default value as we honor "preserveAspectRatio". + image_node->setAttribute("preserveAspectRatio", "none"); + + double svgdpi = mod->get_param_float("svgdpi"); + image_node->setAttribute("inkscape:svg-dpi", Glib::ustring::format(svgdpi)); + + // What is display unit doing here? + Glib::ustring display_unit = doc->getDisplayUnit()->abbr; + double width = ret->getWidth().value(display_unit); + double height = ret->getHeight().value(display_unit); + image_node->setAttribute("width", Glib::ustring::format(width)); + image_node->setAttribute("height", Glib::ustring::format(height)); + + // This is actually "image-rendering" + Glib::ustring scale = prefs->getString("/dialogs/import/scale"); + if( scale != "auto") { + SPCSSAttr *css = sp_repr_css_attr_new(); + sp_repr_css_set_property(css, "image-rendering", scale.c_str()); + sp_repr_css_set(image_node, css, "style"); + sp_repr_css_attr_unref( css ); + } + + // Do we embed or link? + if (import_mode_svg == "embed") { + std::unique_ptr<Inkscape::Pixbuf> pb(Inkscape::Pixbuf::create_from_file(uri, svgdpi)); + if(pb) { + sp_embed_svg(image_node, uri); + } + } else { + // Convert filename to uri (why do we need to do this, we claimed it was already a uri). + gchar* _uri = g_filename_to_uri(uri, nullptr, nullptr); + if(_uri) { + // if (strcmp(_uri, uri) != 0) { + // std::cout << "Svg::open: _uri != uri! " << _uri << ":" << uri << std::endl; + // } + image_node->setAttribute("xlink:href", _uri); + g_free(_uri); + } else { + image_node->setAttribute("xlink:href", uri); + } + } + + // Add the image to a layer. + Inkscape::XML::Node *layer_node = xml_doc->createElement("svg:g"); + layer_node->setAttribute("inkscape:groupmode", "layer"); + layer_node->setAttribute("inkscape:label", "Image"); + doc->getRoot()->appendChildRepr(layer_node); + layer_node->appendChild(image_node); + Inkscape::GC::release(image_node); + Inkscape::GC::release(layer_node); + fit_canvas_to_drawing(doc); + + // Set viewBox if it doesn't exist. What is display unit doing here? + if (!doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit()))); + } + return doc; + } + + // We are not importing as <image>. Open as new document. + + // Try to open non-local file (when does this occur?). + if (!file->get_uri_scheme().empty()) { + if (path.empty()) { + try { + char *contents; + gsize length; + file->load_contents(contents, length); + return SPDocument::createNewDocFromMem(contents, length, true); + } catch (Gio::Error &e) { + g_warning("Could not load contents of non-local URI %s\n", uri); + return nullptr; + } + } else { + // Do we ever get here and does this actually work? + uri = path.c_str(); + } + } + + SPDocument *doc = SPDocument::createNewDoc(uri, true); + // SPDocument *doc = SPDocument::createNewDoc(file->get_uri().c_str(), true); + return doc; +} + +/** + \return None + \brief This is the function that does all of the SVG saves in + Inkscape. It detects whether it should do a Inkscape + namespace save internally. + \param mod Extension to use. + \param doc Document to save. + \param uri The filename to save the file to. + + This function first checks its parameters, and makes sure that + we're getting good data. It also checks the module ID of the + incoming module to figure out whether this save should include + the Inkscape namespace stuff or not. The result of that comparison + is stored in the exportExtensions variable. + + If there is not to be Inkscape name spaces a new document is created + without. (I think, I'm not sure on this code) + + All of the internally referenced imageins are also set to relative + paths in the file. And the file is saved. + + This really needs to be fleshed out more, but I don't quite understand + all of this code. I just stole it. +*/ +void +Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) +{ + g_return_if_fail(doc != nullptr); + g_return_if_fail(filename != nullptr); + Inkscape::XML::Document *rdoc = doc->getReprDoc(); + + bool const exportExtensions = ( !mod->get_id() + || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE) + || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE)); + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool const transform_2_to_1_flag = + prefs->getBool("/dialogs/save_as/enable_svgexport", false); + + bool const insert_text_fallback_flag = + prefs->getBool("/options/svgexport/text_insertfallback", true); + bool const insert_mesh_polyfill_flag = + prefs->getBool("/options/svgexport/mesh_insertpolyfill", true); + bool const insert_hatch_polyfill_flag = + prefs->getBool("/options/svgexport/hatch_insertpolyfill", true); + + bool createNewDoc = + !exportExtensions || + transform_2_to_1_flag || + insert_text_fallback_flag || + insert_mesh_polyfill_flag || + insert_hatch_polyfill_flag; + + // We prune the in-use document and deliberately loose data, because there + // is no known use for this data at the present time. + pruneProprietaryGarbage(rdoc->root()); + + if (createNewDoc) { + + // We make a duplicate document so we don't prune the in-use document + // and loose data. Perhaps the user intends to save as inkscape-svg next. + Inkscape::XML::Document *new_rdoc = new Inkscape::XML::SimpleDocument(); + + // Comments and PI nodes are not included in this duplication + // TODO: Move this code into xml/document.h and duplicate rdoc instead of root. + new_rdoc->setAttribute("standalone", "no"); + new_rdoc->setAttribute("version", "2.0"); + + // Get a new xml repr for the svg root node + Inkscape::XML::Node *root = rdoc->root()->duplicate(new_rdoc); + + // Add the duplicated svg node as the document's rdoc + new_rdoc->appendChild(root); + Inkscape::GC::release(root); + + if (!exportExtensions) { + pruneExtendedNamespaces(root); + } + + if (transform_2_to_1_flag) { + transform_2_to_1 (root); + new_rdoc->setAttribute("version", "1.1"); + } + + if (insert_text_fallback_flag) { + insert_text_fallback (root, doc); + } + + if (insert_mesh_polyfill_flag) { + insert_mesh_polyfill (root); + } + + if (insert_hatch_polyfill_flag) { + insert_hatch_polyfill (root); + } + + rdoc = new_rdoc; + } + + if (!sp_repr_save_rebased_file(rdoc, filename, SP_SVG_NS_URI, + doc->getDocumentBase(), // + m_detachbase ? nullptr : filename)) { + throw Inkscape::Extension::Output::save_failed(); + } + + if (createNewDoc) { + Inkscape::GC::release(rdoc); + } + + return; +} + +} } } /* namespace inkscape, module, implementation */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/svg.h b/src/extension/internal/svg.h new file mode 100644 index 0000000..a7adba4 --- /dev/null +++ b/src/extension/internal/svg.h @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is the code that moves all of the SVG loading and saving into + * the module format. Really Sodipodi is built to handle these formats + * internally, so this is just calling those internal functions. + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2003 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __SVG_H__ +#define __SVG_H__ + +#include "../implementation/implementation.h" + +#define SVG_COMMON_INPUT_PARAMS \ + "<param name='import_mode_svg' type='optiongroup' gui-text='" N_("SVG Image Import Type:") "' >\n" \ + "<option value='include' >" N_("Include SVG image as editable object(s) in the current file") "</option>\n" \ + "<option value='embed' >" N_("Embed the SVG file in a image tag (not editable in this document)") "</option>\n" \ + "<option value='link' >" N_("Link the SVG file in a image tag (not editable in this document).") "</option>\n" \ + "</param>\n" \ + "<param name='svgdpi' type='float' precision='2' min='1' max='999999' gui-text='DPI for rendered SVG'>96.00</param>\n" \ + "<param name='scale' appearance='combo' type='optiongroup' gui-text='" N_("Image Rendering Mode:") "' gui-description='" N_("When an image is upscaled, apply smoothing or keep blocky (pixelated). (Will not work in all browsers.)") "' >\n" \ + "<option value='auto' >" N_("None (auto)") "</option>\n" \ + "<option value='optimizeQuality' >" N_("Smooth (optimizeQuality)") "</option>\n" \ + "<option value='optimizeSpeed' >" N_("Blocky (optimizeSpeed)") "</option>\n" \ + "</param>\n" \ + "<param name=\"do_not_ask\" gui-description='" N_("Hide the dialog next time and always apply the same actions.") "' gui-text=\"" N_("Don't ask again") "\" type=\"bool\" >false</param>\n" + + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class Svg : public Inkscape::Extension::Implementation::Implementation { + bool m_detachbase = false; + +public: + void setDetachBase(bool detach) override { m_detachbase = detach; } + + void save( Inkscape::Extension::Output *mod, + SPDocument *doc, + gchar const *filename ) override; + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ) override; + static void init( ); + +}; + +} } } /* namespace Inkscape, Extension, Implementation */ +#endif /* __SVG_H__ */ + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/svgz.cpp b/src/extension/internal/svgz.cpp new file mode 100644 index 0000000..ea00f94 --- /dev/null +++ b/src/extension/internal/svgz.cpp @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Code to handle compressed SVG loading and saving. Almost identical to svg + * routines, but separated for simpler extension maintenance. + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ted Gould <ted@gould.cx> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2002-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "svgz.h" +#include "extension/extension.h" +#include "extension/system.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +#include "clear-n_.h" + +/** + \return None + \brief What would an SVG editor be without loading/saving SVG + files. This function sets that up. + + For each module there is a call to Inkscape::Extension::build_from_mem + with a rather large XML file passed in. This is a constant string + that describes the module. At the end of this call a module is + returned that is basically filled out. The one thing that it doesn't + have is the key function for the operation. And that is linked at + the end of each call. +*/ +void +Svgz::init() +{ + /* SVGZ in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVGZ Input") "</name>\n" + "<id>" SP_MODULE_KEY_INPUT_SVGZ "</id>\n" + "<dependency type=\"extension\">" SP_MODULE_KEY_INPUT_SVG "</dependency>\n" + SVG_COMMON_INPUT_PARAMS + "<input>\n" + "<extension>.svgz</extension>\n" + "<mimetype>image/svg+xml-compressed</mimetype>\n" + "<filetypename>" N_("Compressed Inkscape SVG (*.svgz)") "</filetypename>\n" + "<filetypetooltip>" N_("SVG file format compressed with GZip") "</filetypetooltip>\n" + "<output_extension>" SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE "</output_extension>\n" + "</input>\n" + "</inkscape-extension>", new Svgz()); + + /* SVGZ out Inkscape */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVGZ Output") "</name>\n" + "<id>" SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE "</id>\n" + "<output>\n" + "<extension>.svgz</extension>\n" + "<mimetype>image/x-inkscape-svg-compressed</mimetype>\n" + "<filetypename>" N_("Compressed Inkscape SVG (*.svgz)") "</filetypename>\n" + "<filetypetooltip>" N_("Inkscape's native file format compressed with GZip") "</filetypetooltip>\n" + "<dataloss>false</dataloss>\n" + "</output>\n" + "</inkscape-extension>", new Svgz()); + + /* SVGZ out */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("SVGZ Output") "</name>\n" + "<id>" SP_MODULE_KEY_OUTPUT_SVGZ "</id>\n" + "<output>\n" + "<extension>.svgz</extension>\n" + "<mimetype>image/svg+xml-compressed</mimetype>\n" + "<filetypename>" N_("Compressed plain SVG (*.svgz)") "</filetypename>\n" + "<filetypetooltip>" N_("Scalable Vector Graphics format compressed with GZip") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>\n", new Svgz()); + + return; +} + + +} } } // namespace inkscape, module, implementation + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/svgz.h b/src/extension/internal/svgz.h new file mode 100644 index 0000000..e923c4c --- /dev/null +++ b/src/extension/internal/svgz.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Code to handle compressed SVG loading and saving. Almost identical to svg + * routines, but separated for simpler extension maintenance. + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Ted Gould <ted@gould.cx> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2002-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_SVGZ_H +#define SEEN_SVGZ_H + +#include "svg.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class Svgz : public Svg { +public: + static void init( ); +}; + +} } } // namespace Inkscape, Extension, Implementation +#endif // SEEN_SVGZ_H + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/text_reassemble.c b/src/extension/internal/text_reassemble.c new file mode 100644 index 0000000..9fdd0a5 --- /dev/null +++ b/src/extension/internal/text_reassemble.c @@ -0,0 +1,2975 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * text_reassemble.c from libTERE + *//* + * Authors: see below + * + * + * Copyright (C) 2018 Authors + * Released under GNU GPL v2.0+, read the file 'COPYING' for more information. + */ + + +/** + @file text_reassemble.c + +\verbatim +Method: + 1. For all ordered text objects which are sequential and share the same esc. + 2. For the first only pull x,y,esc and save, these define origin and rotation. + 3. Save the text object. + 4. Phase I: For all saved text objects construct lines. + 5. Check for allowed overlaps on sequential saved text object bounding rectangles. + 6 If found merge second with first, check next one. + 7. If not found, start a new complex (line). + 8. Phase II; for all lines construct paragraphs. + 9. Check alignment and line spacing of preceding line with current line. + 10. if alignment is the same, and line spacing is compatible merge current line into + current paragraph. Reaverage line spacing over all lines in paragraph. Check next one. + 11. If alignment does not match start a new paragraph. + (Test program) + 12. Over all phase II paragraphs + 13. Over all phase I lines in each paragraph. + 14. Over all text objects in each line. + Emit SVG corresponding to this construct to a file dump.svg. + (Conversion to other file types would be modeled on this example.) + 15. Clean up. + (General program) + Like for the Test program, but final representation may not be SVG. + Text object and bounding rectangle memory would all be released. If another set of + text will be processed then hang onto both Freetype and Fontconfig structures. If no + other text will be processed here, then also release Freetype structures. If the caller uses + Fontconfig elsewhere then do not release it, otherwise, do so. + +NOTE ON COORDINATES: x is positive to the right, y is positive down. So (0,0) is the upper left corner, and the +lower left corner of a rectangle has a LARGER Y coordinate than the upper left. Ie, LL=(10,10) UR=(30,5) is typical. +\endverbatim +*/ + +/* + +Compilation of test program (with all debugging output, but not loop testing): +On Windows use: + + gcc -Wall -DWIN32 -DTEST -DDBG_TR_PARA -DDBG_TR_INPUT \ + -I. -I/c/progs/devlibs32/include -I/c/progs/devlibs32/include/freetype2\ + -o text_reassemble text_reassemble.c uemf_utf.c \ + -lfreetype6 -lfontconfig-1 -liconv -lm -L/c/progs/devlibs32/bin + +On Linux use: + + gcc -Wall -DTEST -DDBG_TR_PARA -DDBG_TR_INPUT -I. -I/usr/include/freetype2 -o text_reassemble text_reassemble.c uemf_utf.c -lfreetype -lfontconfig -lm + +Compilation of object file only (Windows): + + gcc -Wall -DWIN32 -c \ + -I. -I/c/progs/devlibs32/include -I/c/progs/devlibs32/include/freetype2\ + text_reassemble.c + +Compilation of object file only (Linux): + gcc -Wall -c -I. -I/usr/include/freetype2 text_reassemble.c + + +Optional compiler switches for development: + -DDBG_TR_PARA draw bounding rectangles for paragraphs in SVG output + -DDBG_TR_INPUT draw input text and their bounding rectangles in SVG output + -DTEST build the test program + -DDBG_LOOP force the test program to cycle 5 times. Useful for finding + memory leaks. Output file is overwritten each time. + + +File: text_reassemble.c +Version: 0.0.18 +Date: 11-MAR-2016 +Author: David Mathog, Biology Division, Caltech +email: mathog@caltech.edu +Copyright: 2016 David Mathog and California Institute of Technology (Caltech) +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "text_reassemble.h" +#include <3rdparty/libuemf/uemf_utf.h> /* For a couple of text functions. Exact copy from libUEMF. */ +#include <locale.h> +#include <float.h> + +/* Code generated by make_ucd_mn_table.c using: + cat mnlist.txt | ./make_ucd_mn_table >generated.c +*/ +#include <stdint.h> +int is_mn_unicode(int test){ +#define MN_TEST_LIMIT 921600 +#define N_SPAGES 225 +#define N_CPAGES 192 +#define N_CPDATA 344 +#define C_PER_S 16 + uint8_t superpages[N_SPAGES]={ + 0x01, 0x02, 0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x06, + 0x07, 0x08, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x0B}; + + uint8_t cpages[N_CPAGES]={ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x0E, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x00, 0x00, + 0x17, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x19, 0x00, 0x00, + 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x1C, 0x1D, 0x1E, 0x1F, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x21, 0x00, + 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x24, 0x25, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, + 0x00, 0x28, 0x29, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + uint32_t cpage_data[N_CPDATA]={ + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000F8, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFE0000, 0xBFFFFFFF, 0x000000B6, 0x00000000, + 0x07FF0000, 0x00000000, 0xFFFFF800, 0x00010000, 0x00000000, 0x00000000, 0x9FC00000, 0x00003D9F, + 0x00020000, 0xFFFF0000, 0x000007FF, 0x00000000, 0x00000000, 0x0001FFC0, 0x00000000, 0x000FF800, + 0xFBC00000, 0x00003EEF, 0x0E000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x7FFFFFF0, + 0x00000007, 0x14000000, 0x00FE21FE, 0x0000000C, 0x00000002, 0x10000000, 0x0000201E, 0x0000000C, + 0x00000006, 0x10000000, 0x00023986, 0x00230000, 0x00000006, 0x10000000, 0x000021BE, 0x0000000C, + 0x00000002, 0x90000000, 0x0040201E, 0x0000000C, 0x00000004, 0x00000000, 0x00002001, 0x00000000, + 0x00000000, 0xC0000000, 0x00603DC1, 0x0000000C, 0x00000000, 0x90000000, 0x00003040, 0x0000000C, + 0x00000000, 0x00000000, 0x0000201E, 0x0000000C, 0x00000000, 0x00000000, 0x005C0400, 0x00000000, + 0x00000000, 0x07F20000, 0x00007F80, 0x00000000, 0x00000000, 0x1BF20000, 0x00003F00, 0x00000000, + 0x03000000, 0x02A00000, 0x00000000, 0x7FFE0000, 0xFEFFE0DF, 0x1FFFFFFF, 0x00000040, 0x00000000, + 0x00000000, 0x66FDE000, 0xC3000000, 0x001E0001, 0x20002064, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0xE0000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x001C0000, 0x001C0000, 0x000C0000, 0x000C0000, 0x00000000, 0x3FB00000, 0x200FFE40, 0x00000000, + 0x00003800, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000200, 0x00000000, 0x00000000, + 0x00000000, 0x0E040187, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x01800000, 0x00000000, 0x7F400000, 0x9FF81FE5, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000000F, 0x17D00000, 0x00000004, 0x000FF800, 0x00000003, 0x00000B3C, 0x00000000, 0x0003A340, + 0x00000000, 0x00CFF000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFF70000, 0x001021FD, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, 0xF000007F, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x1FFF0000, 0x0001FFE2, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00038000, + 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0xFFFFFFFF, + 0x00000000, 0x00003C00, 0x00000000, 0x00000000, 0x06000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x3FF08000, 0x80000000, 0x00000000, 0x00000000, 0x00030000, + 0x00000844, 0x00000060, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000010, 0x0003FFFF, + 0x00000000, 0x00003FC0, 0x0003FF80, 0x00000000, 0x00000007, 0x13C80000, 0x00000000, 0x00000000, + 0x00000000, 0x00667E00, 0x00001008, 0x00000000, 0x00000000, 0xC19D0000, 0x00000002, 0x00403000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00002120, + 0x40000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x0000FFFF, 0x0000007F, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x20000000, + 0x0000F06E, 0x87000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0x00000002, 0xFF000000, 0x0000007F, 0x00000000, 0x00000003, 0x06780000, 0x00000000, 0x00000000, + 0x00000007, 0x001FEF80, 0x00000000, 0x00000000, 0x00000003, 0x7FC00000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00BF2800, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00078000, 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, 0xF8000380, 0x00000FE7, 0x00003C00, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x0000001C, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, + 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x0000FFFF}; + + int result=0; + + int spage_idx; + int cpage_row, cpage_column, cpage_idx; + int cd_row, cd_column, cd_idx, cd_bit; + + if(test<MN_TEST_LIMIT){ + spage_idx = test >> 12; + cpage_row = superpages[spage_idx]; + cpage_column = (test>>8) & 15; + cpage_idx = C_PER_S*cpage_row + cpage_column; + cd_row = cpages[cpage_idx]; + cd_column = test>>5 & 7; + cd_idx = 8*cd_row + cd_column; + cd_bit = test & 31; + result = cpage_data[cd_idx] & (1 << cd_bit); + } + return(result); +} + + + +/** + \brief Find a (sub)string in a caseinvariant manner, used for locating "Narrow" in font name + \return Returns -1 if no match, else returns the position (numbered from 0) of the first character of the match. + \param string Text to search + \param sub Text to find +*/ +int TR_findcasesub(const char *string, const char *sub){ + int i,j; + int match=0; + for(i=0; string[i]; i++){ + for(match=1,j=0; sub[j] && string[i+j]; j++){ + if(toupper(sub[j]) != toupper(string[i+j])){ + match=0; + break; + } + } + if(match && !sub[j])break; /* matched over the entire substring */ + } + return((match ? i : -1)); +} + +/** + \brief Constrouct a fontspec from a TCHUNK_SPECS and a fontname + \return Returns NULL on error, new fontspec on success + \param tsp pointer to TCHUNK_SPECS to use for information + \param fontname Fontname to use in the new fontspec +*/ + /* construct a font name */ +char *TR_construct_fontspec(const TCHUNK_SPECS *tsp, const char *fontname){ + int newlen = 128 + strlen(fontname); /* too big, but not by much */ + char *newfs = NULL; + newfs = (char *) malloc(newlen); + sprintf(newfs,"%s:slant=%d:weight=%d:size=%f:width=%d",fontname,tsp->italics,tsp->weight,tsp->fs,(tsp->co ? 75 : tsp->condensed)); + return(newfs); +} + + +/** + \brief Reconstrouct a fontspec by substituting a font name into an existing spec + \return Returns NULL on error, new fontspec on success + \param fontspec Original fontspec, only the name will be changed + \param fontname Fontname to substitute into the new fontspec +*/ + /* construct a font name */ +char *TR_reconstruct_fontspec(const char *fontspec, const char *fontname){ + int colon; + int newlen = strlen(fontspec) + strlen(fontname) + 1; /* too big, but not by much */ + char *newfs = NULL; + newfs = (char *) malloc(newlen); + colon = strcspn(fontspec,":"); + if(colon){ sprintf(newfs,"%s%s",fontname,&fontspec[colon]); } + return(newfs); +} + +/** + \brief Find a font in the list that has a glyph for this character, change alternate to match + \return Returns 0 if no match or an error, else returns the glyph index in the new alternate font + \param fti pointer to the FT_INFO structure, may be modified if alternate font is added + \param efsp Pointer to a Pointer to the original FNT_SPECS struct. On return contains the FNT_SPECS corresponding to the glyph_index.. + \param wc Current character (32 bit int) +*/ +int TR_find_alternate_font(FT_INFO *fti, FNT_SPECS **efsp, uint32_t wc){ + int glyph_index=0; /* this is the unknown character glyph */ + uint32_t i; + FcCharSet *cs; + FcResult result = FcResultMatch; + FcPattern *pattern, *fpat; + char *filename; + char *fontname; + char *newfontspec; + int fi_idx; + FNT_SPECS *fsp,*fsp2; + if(!fti || !efsp || !*efsp)return(0); + fsp = *efsp; + for(i=0;i<fsp->used;i++){ /* first check in alts */ + fsp2 = &fti->fonts[fsp->alts[i].fi_idx]; /* these are in order of descending previous usage */ + glyph_index = FT_Get_Char_Index( fsp2->face, wc); /* we have the face, might as well check that directly */ + if (glyph_index){ /* found a glyph for the character in this font */ + (void) fsp_alts_weight(fsp, i); + *efsp = fsp2; + return(glyph_index); + } + } + + /* it was not in alts, now go through fontset and see if it is in there */ + for(i=1; i< (unsigned int) fsp->fontset->nfont;i++){ /* already know the primary does not have this character */ + result = FcPatternGetCharSet(fsp->fontset->fonts[i], FC_CHARSET, 0, &cs); + if(result != FcResultMatch) return(0); /* some terrible problem, this should never happen */ + if (FcCharSetHasChar(cs, wc)){ /* found a glyph for the character in this font */ + glyph_index = i; + + /* Do a lot of work to find the filename corresponding to the fontset entry. + None of these should ever fail, but if one does, return 0 + */ + if( + !(pattern = FcNameParse((const FcChar8 *)&(fsp->fontspec))) || + !FcConfigSubstitute(NULL, pattern, FcMatchPattern) + )return(0); + FcDefaultSubstitute(pattern); + if( + !(fpat = FcFontRenderPrepare(NULL, pattern, fsp->fontset->fonts[i])) || + (FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch) || + (FcPatternGetString( fsp->fontset->fonts[i], FC_FULLNAME, 0, (FcChar8 **)&fontname) != FcResultMatch) + )return(0); + + /* find the font (added from an unrelated fontset, for instance) or insert it as new */ + fi_idx = ftinfo_find_loaded_by_src(fti, (uint8_t *) filename); + if(fi_idx < 0){ + newfontspec = TR_reconstruct_fontspec((char *) fsp->fontspec, fontname); + fi_idx = ftinfo_load_fontname(fti, newfontspec); + free(newfontspec); + if(fi_idx < 0)return(0); /* This could happen if we run out of memory*/ + } + + /* add the new font index to the alts list on the (current) fsp. */ + (void) fsp_alts_insert(fsp, fi_idx); + + /* release FC's own memory related to this call that does not need to be kept around so that face will work */ + FcPatternDestroy(pattern); + + *efsp = &(fti->fonts[fi_idx]); + return(glyph_index); + } + } + + return(0); +} + +/** + \brief Get the advance for the 32 bit character + + \return Returns -1 on error, or advance in units of 1/64th of a Point. + \param fti pointer to the FT_INFO structure, may be modified if alternate font is required + \param fsp Pointer to FNT_SPECS struct. + \param wc Current character (32 bit int) + \param pc Previous character + \param load_flags Controls internal advance: + FT_LOAD_NO_SCALE, internal advance is in 1/64th of a point. (kerning values are still scaled) + FT_LOAD_TARGET_NORMAL internal advance is in 1/64th of a point. The scale + factor seems to be (Font Size in points)*(DPI)/(32.0 pnts)*(72 dpi). + \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application. + \param ymin If the pointer is defined, the value is adjusted if ymin of wc character is less than the current value. + \param ymax If the pointer is defined, the value is adjusted if ymin of wc character is more than the current value. +*/ +int TR_getadvance(FT_INFO *fti, FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax){ + FT_Glyph glyph; + int glyph_index; + int advance=-1; + FT_BBox bbox; + + if(is_mn_unicode(wc))return(0); /* no advance on Unicode Mn characters */ + + glyph_index = FT_Get_Char_Index( fsp->face, wc); + if(!glyph_index){ /* not in primary font, check alternates */ + glyph_index = TR_find_alternate_font(fti, &fsp, wc); + } + if(glyph_index){ + if (!FT_Load_Glyph( fsp->face, glyph_index, load_flags )){ + if ( !FT_Get_Glyph( fsp->face->glyph, &glyph ) ) { + advance = fsp->face->glyph->advance.x; + FT_Glyph_Get_CBox( glyph, FT_GLYPH_BBOX_UNSCALED, &bbox ); + if(ymin && (bbox.yMin < *ymin))*ymin=bbox.yMin; + if(ymax && (bbox.yMax > *ymax))*ymax=bbox.yMax; + if(pc)advance += TR_getkern2(fsp, wc, pc, kern_mode); + FT_Done_Glyph(glyph); + } + } + } + /* If there was no way to determine the width, this returns the error value */ + return(advance); +} + +/** + \brief Get the kerning for a pair of 32 bit characters + \return Returns 0 on error, or kerning value (which may be 0) for the pair in units of 1/64th of a point. + \param fsp Pointer to FNT_SPECS struct. + \param wc Current character (32 bit int) + \param pc Previous character + \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application. +*/ +int TR_getkern2(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int kern_mode){ + int this_glyph_index; + int prev_glyph_index; + int kern=0; + FT_Vector akerning; + + this_glyph_index = FT_Get_Char_Index( fsp->face, wc); + prev_glyph_index = FT_Get_Char_Index( fsp->face, pc); + if(!FT_Get_Kerning( fsp->face, + prev_glyph_index, + this_glyph_index, + kern_mode, + &akerning )){ + kern = akerning.x; /* Is sign correct? */ + } + return(kern); +} + +/** + \brief Get the kerning for a pair of 32 bit characters, where one is the last character in the previous text block, and the other is the first in the current text block. + \return Returns 0 on error, or kerning value (which may be 0) for the pair in units of 1/64th of a point. + \param fsp Pointer to FNT_SPECS struct. + \param tsp current text object + \param ptsp previous text object + \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application. +*/ +int TR_kern_gap(FNT_SPECS *fsp, TCHUNK_SPECS *tsp, TCHUNK_SPECS *ptsp, int kern_mode){ + int kern=0; + uint32_t *text32=NULL; + uint32_t *ptxt32=NULL; + size_t tlen,plen; + while(ptsp && tsp){ + text32 = U_Utf8ToUtf32le((char *) tsp->string, 0, &tlen); + if(!text32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail + text32 = U_Latin1ToUtf32le((char *) tsp->string,0, &tlen); + if(!text32)break; + } + ptxt32 = U_Utf8ToUtf32le((char *) ptsp->string,0,&plen); + if(!ptxt32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail + ptxt32 = U_Latin1ToUtf32le((char *) ptsp->string,0, &plen); + if(!ptxt32)break; + } + kern = TR_getkern2(fsp, *text32, ptxt32[plen-1], kern_mode); + break; + } + if(text32)free(text32); + if(ptxt32)free(ptxt32); + return(kern); +} + + + + +/** + \brief Find baseline on Y axis of a complex. + If the complex is a TR_TEXT or TR_LINE find its baseline. + If the complex is TR_PARA_[UCLR]J find the baseline of the last line. + If there are multiple text elements in a TR_LINE, the baseline is that of the + element that uses the largest font. This will definitely give the wrong + result if that line starts with a super or subscript that is full font size, but + they are usually smaller. + \return Returns 0 if it cannot determine a baseline, else returns the baseline Y coordinate. + \param tri pointer to the TR_INFO structure holding all TR data + \param src index of the current complex + \param ymax If the pointer is defined, the value is adjusted if ymax of current complex is more than the current value. + \param ymin If the pointer is defined, the value is adjusted if ymin of current complex is less than the current value. +*/ +double TR_baseline(TR_INFO *tri, int src, double *ymax, double *ymin){ + double baseline=0; + volatile double tmp=0.0; /* This MUST be volatile */ + double yheight; + int last; + int i; + int trec; + CX_INFO *cxi=tri->cxi; + BR_INFO *bri=tri->bri; + TP_INFO *tpi=tri->tpi; + FT_INFO *fti=tri->fti; + FNT_SPECS *fsp; + last = cxi->cx[src].kids.used - 1; + switch (cxi->cx[src].type){ + case TR_TEXT: + trec = cxi->cx[src].kids.members[0]; /* for this complex type there is only ever one member */ + baseline = bri->rects[trec].yll - tpi->chunks[trec].boff; + fsp = &(fti->fonts[tpi->chunks[trec].fi_idx]); + yheight = fsp->face->bbox.yMax - fsp->face->bbox.yMin; + if(ymax){ + tmp = tpi->chunks[trec].fs * ((double)fsp->face->bbox.yMax/yheight); + if(*ymax <= tmp)*ymax = tmp; + } + else if(ymin){ + tmp = tpi->chunks[trec].fs * ((double)-fsp->face->bbox.yMin/yheight); /* yMin in face is negative */ + if(*ymin <= tmp)*ymin = tmp; + } + break; + case TR_LINE: + for(i=last;i>=0;i--){ /* here last is the count of text objects in the complex */ + trec = cxi->cx[src].kids.members[i]; + fsp = &(fti->fonts[tpi->chunks[trec].fi_idx]); + yheight = fsp->face->bbox.yMax - fsp->face->bbox.yMin; + if(ymax){ + tmp = tpi->chunks[trec].fs * (((double)fsp->face->bbox.yMax)/yheight); + /* gcc 4.6.3 had a bizarre optimization error for -O2 and -O3 where *ymax <= tmp was + not true when *ymax == tmp, as verified by examining the binary representations. + This was apparently due to retained excess precision. Making tmp volatile + forces it to be stored into a 64 bit location, dropping the extra 12 bits from + the 80 bit register. */ + if(*ymax <= tmp){ + *ymax = tmp; + baseline = bri->rects[trec].yll - tpi->chunks[trec].boff; + } + } + else if(ymin){ + tmp = tpi->chunks[trec].fs * (((double)-fsp->face->bbox.yMin)/yheight); /* yMin in face is negative */ + if(*ymin <= tmp){ + *ymin = tmp; + baseline = bri->rects[trec].yll - tpi->chunks[trec].boff; + } + } + } + break; + case TR_PARA_UJ: + case TR_PARA_LJ: + case TR_PARA_CJ: + case TR_PARA_RJ: + trec = cxi->cx[src].kids.members[last]; + baseline = TR_baseline(tri, trec, ymax, ymin); + break; + } + return(baseline); +} + +/** + \brief Check or set vertical advance on the growing complex relative to the current complex. + Vadvance is a multiplicative factor like 1.25. + The distance between successive baselines is vadvance * max(font_size), where the maximum + is over all text elements in src. + The growing complex is always the last one in the CX_INFO section of the TR_INFO structure. + If an existing vadvance does not match the one which would be required to fit the next complex + to add to the growing one, it terminates a growing complex. (Ie, starts a new paragraph.) + Find baseline on Y axis of a complex. + If the complex is a TR_TEXT or TR_LINE find its baseline. + If the complex is TR_PARA+* find the baseline of the last line. + If there are multiple text elements in a TR_LINE, the baseline is that of the + element that uses the largest font. This will definitely give the wrong + result if that line starts with a super or subscript that is full font size, but + they are usually smaller. + \return Returns 0 on success, !0 on failure. + \param tri pointer to the TR_INFO structure holding all TR data + \param src index of the current complex, to be added to the growing complex. + This lets the value of "src - lines" determine the weight to give to each new vadvance value + as it is merged into the running weighted average. This improves the accuracy of the vertical advance, + since there can be some noise introduced when lines have different maximum font sizes. + \param lines index of the first text block that was added to the growing complex. +*/ +int TR_check_set_vadvance(TR_INFO *tri, int src, int lines){ + int status = 0; + CX_INFO *cxi = tri->cxi; + TP_INFO *tpi = tri->tpi; + double ymax = DBL_MIN; + double ymin = DBL_MIN; + double prevbase; + double thisbase; + double weight; + int trec; + double newV; + int dst; + + dst = cxi->used-1; /* complex being grown */ + + prevbase = TR_baseline(tri, dst, NULL, &ymin); + thisbase = TR_baseline(tri, src, &ymax, NULL); + newV = (thisbase - prevbase)/(ymax + ymin); + trec = cxi->cx[dst].kids.members[0]; /* complex whose first text record holds vadvance for this complex */ + trec = cxi->cx[trec].kids.members[0]; /* text record that halds vadvance for this complex */ + if(tpi->chunks[trec].vadvance){ + /* already set on the first text (only place it is stored.) + See if the line to be added is compatible. + All text fields in a complex have the same advance, so just set/check the first one. + vadvance must be within 1% or do not add a new line */ + if(fabs(1.0 - (tpi->chunks[trec].vadvance/newV)) > 0.01){ + status = 1; + } + else { /* recalculate the weighted vadvance */ + weight = (1.0 / (double) (src - lines)); + tpi->chunks[trec].vadvance = tpi->chunks[trec].vadvance*(1.0-weight) + newV*weight; + } + } + else { /* only happens when src = lines + 1*/ + tpi->chunks[trec].vadvance = newV; + } + return(status); +} + + +/** + \brief Initialize an FT_INFO structure. Sets up a freetype library to use in this context. + \returns a pointer to the FT_INFO structure created, or NULL on error. +*/ +FT_INFO *ftinfo_init(void){ + FT_INFO *fti = NULL; + if(FcInit()){ + fti = (FT_INFO *)calloc(1,sizeof(FT_INFO)); + if(fti){ + if(!FT_Init_FreeType( &(fti->library))){ + fti->space=0; + fti->used=0; + + if(ftinfo_make_insertable(fti)){ + FT_Done_FreeType(fti->library); + free(fti); + fti=NULL; + } + } + else { + free(fti); + fti=NULL; + } + } + if(!fti)FcFini(); + } + return(fti); +} + +/** + \brief Make an FT_INFO structure insertable. Adds storage as needed. + \param fti pointer to the FT_INFO structure + \returns 0 on success, !0 on error. +*/ +int ftinfo_make_insertable(FT_INFO *fti){ + int status=0; + FNT_SPECS *tmp; + if(!fti)return(2); + if(fti->used >= fti->space){ + fti->space += ALLOCINFO_CHUNK; + tmp = (FNT_SPECS *) realloc(fti->fonts, fti->space * sizeof(FNT_SPECS) ); + if(tmp){ + fti->fonts = tmp; + memset(&fti->fonts[fti->used],0,(fti->space - fti->used)*sizeof(FNT_SPECS)); + } + else { + status=1; + } + } + return(status); +} + + +/** + \brief Insert a copy of a FNT_SPECS structure into the FT_INFO structure. + \param fti pointer to the FT_INFO structure. + \param fsp pointer to the FNT_SPECS structure. + \returns 0 on success, !0 on error. +*/ +int ftinfo_insert(FT_INFO *fti, FNT_SPECS *fsp){ + int status=1; + if(!fti)return(2); + if(!fsp)return(3); + if(!(status = ftinfo_make_insertable(fti))){ + memcpy(&(fti->fonts[fti->used]),fsp,sizeof(FNT_SPECS)); + fti->used++; + } + return(status); +} + + + +/** + \brief Release an FT_INFO structure. Release all associated memory. + Use like: fi_ptr = ftinfo_release(fi_ptr) + \param fti pointer to the FT_INFO structure. + \returns NULL. +*/ +FT_INFO *ftinfo_release(FT_INFO *fti){ + (void) ftinfo_clear(fti); + FcFini(); /* shut down FontConfig, release memory, patterns must have already been released or boom! */ + return NULL; +} + + +/** + \brief Clear an FT_INFO structure. Release all Freetype memory but does not release Fontconfig. + This would be called in preference to ftinfo_release() if some other part of the program needed + to continue using Fontconfig. + Use like: fi_ptr = ftinfo_clear(fi_ptr) + \param fti pointer to the FT_INFO structure. + \returns NULL. +*/ +FT_INFO *ftinfo_clear(FT_INFO *fti){ + uint32_t i; + FNT_SPECS *fsp; + if(fti){ + for(i=0;i<fti->used;i++){ + fsp = &(fti->fonts[i]); + FT_Done_Face(fsp->face); /* release memory for face controlled by FreeType */ + free(fsp->file); /* release memory holding copies of paths */ + free(fsp->fontspec); /* release memory holding copies of font names */ + FcPatternDestroy(fsp->fpat); /* release memory for FontConfig fpats */ + FcFontSetDestroy(fsp->fontset); + if(fsp->alts){ free(fsp->alts); } + } + free(fti->fonts); + FT_Done_FreeType(fti->library); /* release all other FreeType memory */ + free(fti); + } + return NULL; +} + + +/** + \brief Find the loaded font matching fontspec + \returns index of font on success, -1 if not found + \param tri pointer to the TR_INFO structure. + \param fontspec UTF-8 description of the font, as constructed in trinfo_load_fontname +*/ + +int ftinfo_find_loaded_by_spec(const FT_INFO *fti, const uint8_t *fontspec){ + uint32_t i; + int status = -1; + /* If it is already loaded, do not load it again */ + for(i=0;i<fti->used;i++){ + if(0==strcmp((char *) fti->fonts[i].fontspec, (char *)fontspec)){ + status=i; + break; + } + } + return(status); +} + +/** + \brief Find the loaded font matching the source file + \returns index of font on success, -1 if not found + \param tri pointer to the TR_INFO structure. + \param filename UTF-8 file name for the font +*/ + +int ftinfo_find_loaded_by_src(const FT_INFO *fti, const uint8_t *filename){ + uint32_t i; + int status = -1; + /* If it is already loaded, do not load it again */ + for(i=0;i<fti->used;i++){ + if(0==strcmp((char *) fti->fonts[i].file, (char *) filename)){ + status=i; + break; + } + } + return(status); +} + + +/** + \brief Load a (new) font by name into a TR_INFO structure or find it if it is already loaded + \returns fi_idx of inserted (or found) font on success, <0 on error. + \param fti pointer to the FT_INFO structure. + \param fontname UTF-8 font name + \param fontspec UTF-8 font specification used for query string. +*/ + +int ftinfo_load_fontname(FT_INFO *fti, const char *fontspec){ + FcPattern *pattern = NULL; + FcPattern *fpat = NULL; + FcFontSet *fontset = NULL; + FcResult result = FcResultMatch; + char *filename; + double fd; + FNT_SPECS *fsp; + int status; + int fi_idx; + + if(!fti)return(-1); + + /* If it is already loaded, do not load it again */ + status = ftinfo_find_loaded_by_spec(fti, (uint8_t *) fontspec); + if(status >= 0){ return(status); } + status = 0; /* was -1, reset to 0 */ + + ftinfo_make_insertable(fti); + fi_idx = fti->used; + + pattern = FcNameParse((const FcChar8 *)fontspec); + while(1) { /* this is NOT a loop, it uses breaks to avoid gotos and deep nesting */ + if(!(pattern)){ status = -2; break; } + if(!FcConfigSubstitute(NULL, pattern, FcMatchPattern)){ status = -3; break; }; + FcDefaultSubstitute(pattern); + /* get a fontset, trimmed to only those with new glyphs as needed, so that missing glyph's may be handled */ + if(!(fontset = FcFontSort (NULL,pattern, FcTrue, NULL, &result)) || (result != FcResultMatch)){ status = -4; break; } + if(!(fpat = FcFontRenderPrepare(NULL, pattern, fontset->fonts[0]))){ status = -405; break; } + if(FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch){ status = -5; break; } + if(FcPatternGetDouble( fpat, FC_SIZE, 0, &fd) != FcResultMatch){ status = -6; break; } + + /* copy these into memory for external use */ + fsp = &(fti->fonts[fti->used]); + fsp->fontset = fontset; + fsp->alts = NULL; /* Initially no links to alternate fonts */ + fsp->space = 0; + fsp->file = (uint8_t *) U_strdup((char *) filename); + fsp->fontspec = (uint8_t *) U_strdup((char *) fontspec); + fsp->fpat = fpat; + fsp->fsize = fd; + break; + } + /* release FC's own memory related to this call that does not need to be kept around so that face will work */ + if(pattern)FcPatternDestroy(pattern); /* done with this memory */ + if(status<0){ + if(fontset)FcFontSetDestroy(fontset); + if(fpat)FcPatternDestroy(fpat); + return(status); + } + + /* get the current face */ + if(FT_New_Face( fti->library, (const char *) fsp->file, 0, &(fsp->face) )){ return(-8); } + + if(FT_Set_Char_Size( + fsp->face, /* handle to face object */ + 0, /* char_width in 1/64th of points */ + fd*64, /* char_height in 1/64th of points */ + 72, /* horizontal device resolution, DPI */ + 72) /* vebrical device resolution, DPI */ + ){ return(-9); } + + /* The space advance is needed in various places. Get it now, and get it in the font units, + so that it can be scaled later with the text size */ + status = TR_getadvance(fti, fsp,' ',0,FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, FT_KERNING_UNSCALED, NULL, NULL); + if(status < 0)return(-7); + fsp->spcadv = ((double) status)/(64.0); + + fti->used++; + +/* + char *fs; + int fb; + if(FcPatternGetBool( fpat, FC_OUTLINE, 0, &fb)== FcResultMatch){ printf("outline: %d\n",fb);fflush(stdout); } + if(FcPatternGetBool( fpat, FC_SCALABLE, 0, &fb)== FcResultMatch){ printf("scalable: %d\n",fb);fflush(stdout); } + if(FcPatternGetDouble( fpat, FC_DPI, 0, &fd)== FcResultMatch){ printf("DPI: %f\n",fd);fflush(stdout); } + if(FcPatternGetInteger( fpat, FC_FONTVERSION, 0, &fb)== FcResultMatch){ printf("fontversion: %d\n",fb);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FULLNAME , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAME : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FAMILY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILY : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_STYLE , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLE : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FOUNDRY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FOUNDRY : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FAMILYLANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILYLANG : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_STYLELANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLELANG : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FULLNAMELANG, 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAMELANG: %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_CAPABILITY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("CAPABILITY : %s\n",fs);fflush(stdout); } + if(FcPatternGetString( fpat, FC_FONTFORMAT , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FONTFORMAT : %s\n",fs);fflush(stdout); } +*/ + + return(fi_idx); +} + +/** + \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code. + \param tri pointer to the TR_INFO structure. +*/ +void ftinfo_dump(const FT_INFO *fti){ + uint32_t i,j; + FNT_SPECS *fsp; + printf("fti space: %d\n",fti->space); + printf("fti used: %d\n",fti->used); + for(i=0; i< fti->used; i++){ + fsp = &(fti->fonts[i]); + printf("fti font: %6d space: %6d used: %6d spcadv %8f fsize %8f \n",i,fsp->space,fsp->used,fsp->spcadv,fsp->fsize); + printf(" file: %s\n",fsp->file); + printf(" fspc: %s\n",fsp->fontspec); + for(j=0;j<fsp->used;j++){ + printf(" alts: %6d fi_idx: %6d wgt: %6d\n",j,fsp->alts[j].fi_idx,fsp->alts[j].weight); + } + } + +} + +/** + \brief Make the FNT_SPECS alts structure insertable. Adds storage as needed. + \param fti pointer to the FT_INFO structure + \returns 0 on success, !0 on error. +*/ +int fsp_alts_make_insertable(FNT_SPECS *fsp){ + int status=0; + ALT_SPECS *tmp; + if(!fsp)return(2); + if(fsp->used >= fsp->space){ + fsp->space += ALLOCINFO_CHUNK; + tmp = (ALT_SPECS *) realloc(fsp->alts, fsp->space * sizeof(ALT_SPECS) ); + if(tmp){ + fsp->alts = tmp; + memset(&fsp->alts[fsp->used],0,(fsp->space - fsp->used)*sizeof(ALT_SPECS)); + } + else { + status=1; + } + } + return(status); +} + + +/** + \brief Insert a new ALT_SPECS into the FNT_SPECS alts list. + \param fsp pointer to the FNT_SPECS structure. + \param fi_idx font index to add to the alts list + \returns 0 on success, !0 on error. +*/ +int fsp_alts_insert(FNT_SPECS *fsp, uint32_t fi_idx){ + int status=1; + ALT_SPECS alt; + if(!fsp)return(3); + alt.fi_idx = fi_idx; + alt.weight = 1; /* new ones start with this weight, it can only go up */ + if(!(status = fsp_alts_make_insertable(fsp))){ + fsp->alts[fsp->used] = alt; + fsp->used++; + } + return(status); +} + +/** + \brief Increment the weight of an alts entry by 1, readjust order if necessary + \param fsp pointer to the FNT_SPECS structure. + \param idx index of the alts entry to increment + \returns 0 on success, !0 on error. +*/ +int fsp_alts_weight(FNT_SPECS *fsp, uint32_t a_idx){ + uint32_t i; + ALT_SPECS alt; + if(!fsp)return(1); + if(!fsp->used)return(2); + if(a_idx >= fsp->used)return(3); + /* If a counter hits the limit divide all counts in half. */ + if(fsp->alts[a_idx].weight == UINT32_MAX){ + for(i=0; i<fsp->used; i++){ fsp->alts[i].weight /= 2; } + } + fsp->alts[a_idx].weight++; + for(i=a_idx; i>0; i--){ + if(fsp->alts[i-1].weight >= fsp->alts[a_idx].weight)break; + alt = fsp->alts[i-1]; + fsp->alts[i-1] = fsp->alts[a_idx]; + fsp->alts[a_idx] = alt; + } + return(0); +} + + + +/** + \brief Make a CHILD_SPECS structure insertable. Adds storage as needed. + \param csp pointer to the CHILD_SPECS structure + \returns 0 on success, !0 on error. +*/ +int csp_make_insertable(CHILD_SPECS *csp){ + int status=0; + int *tmp; + if(!csp)return(2); + if(csp->used >= csp->space){ + csp->space += ALLOCINFO_CHUNK; + tmp = (int *) realloc(csp->members, csp->space * sizeof(int) ); + if(tmp){ + csp->members = tmp; + memset(&csp->members[csp->used],0,(csp->space - csp->used)*sizeof(int)); + } + else { + status=1; + } + } + return(status); +} + +/** + \brief Add a member to a CHILD_SPECS structure. (Member is an index for either a text object or a complex.) + \param dst pointer to the CHILD_SPECS structure. + \param src index of the member. + \returns 0 on success, !0 on error. +*/ +int csp_insert(CHILD_SPECS *dst, int src){ + int status=1; + if(!dst)return(2); + if(!(status=csp_make_insertable(dst))){ + dst->members[dst->used]=src; + dst->used++; + } + return(status); +} + +/** + \brief Append all the members of one CHILD_SPECS structure to another CHILD_SPECS structure. + Member is an index for either a text object or a complex. + The donor is not modified. + \param dst pointer to the recipient CHILD_SPECS structure. + \param src pointer to the donor CHILD_SPECS structure. + \returns 0 on success, !0 on error. +*/ +int csp_merge(CHILD_SPECS *dst, CHILD_SPECS *src){ + uint32_t i; + int status=1; + if(!dst)return(2); + if(!src)return(3); + for(i=0;i<src->used;i++){ + status = csp_insert(dst, src->members[i]); + if(status)break; + } + return(status); +} + +/** + \brief Release a CHILD_SPECS structure. Release all associated memory. + \param csp pointer to the CHILD_SPECS structure. + \returns NULL. +*/ +void csp_release(CHILD_SPECS *csp){ + if(csp){ + free(csp->members); + csp->space = 0; + csp->used = 0; + } +} + +/** + \brief Clear a CHILD_SPECS structure, making all allocated slots usable. Does not release associated memory. + \param csp pointer to the CHILD_SPECS structure. + \returns NULL. +*/ +void csp_clear(CHILD_SPECS *csp){ + csp->used = 0; +} + + +/** + \brief Initialize an CX_INFO structure. Holds complexes (multiple text objects in known positions and order.) + \returns a pointer to the CX_INFO structure created, or NULL on error. +*/ +CX_INFO *cxinfo_init(void){ + CX_INFO *cxi = NULL; + cxi = (CX_INFO *)calloc(1,sizeof(CX_INFO)); + if(cxi){ + if(cxinfo_make_insertable(cxi)){ + free(cxi); + cxi=NULL; + } + } + return(cxi); +} + +/** + \brief Make a CX_INFO structure insertable. Adds storage as needed. + \returns 0 on success, !0 on error. + \param cxi pointer to the CX_INFO structure +*/ +int cxinfo_make_insertable(CX_INFO *cxi){ + int status=0; + CX_SPECS *tmp; + if(cxi->used >= cxi->space){ + cxi->space += ALLOCINFO_CHUNK; + tmp = (CX_SPECS *) realloc(cxi->cx, cxi->space * sizeof(CX_SPECS) ); + if(tmp){ + cxi->cx = tmp; + memset(&cxi->cx[cxi->used],0,(cxi->space - cxi->used)*sizeof(CX_SPECS)); + } + else { + status=1; + } + } + return(status); +} + +/** + \brief Insert a complex into the CX_INFO structure. (Insert may be either TR_TEXT or TR_LINE.) + \returns 0 on success, !0 on error. + \param cxi pointer to the CX_INFO structure (complexes). + \param src index of the complex to insert. + \param src_rt_tidx index of the bounding rectangle + \param type TR_TEXT (index is for tpi->chunks[]) or TR_LINE (index is for cxi->kids[]) +*/ +int cxinfo_insert(CX_INFO *cxi, int src, int src_rt_tidx, enum tr_classes type){ + int status=1; + if(!cxi)return(2); + if(!(status=cxinfo_make_insertable(cxi))){ + cxi->cx[cxi->used].rt_cidx = src_rt_tidx; + cxi->cx[cxi->used].type = type; + status = csp_insert(&(cxi->cx[cxi->used].kids), src); + cxi->used++; + } + return(status); +} + +/** + \brief Append a complex to the CX_INFO structure and give it a type. + \param cxi pointer to the CX_INFO structure (complexes). + \param src index of the complex to append. + \param type TR_LINE (src is an index for tpi->chunks[]) or TR_PARA (src is an index for cxi->kids[]). + \returns 0 on success, !0 on error. +*/ +int cxinfo_append(CX_INFO *cxi, int src, enum tr_classes type){ + int status=1; + if(!cxi)return(2); + if(!(status=cxinfo_make_insertable(cxi))){ + cxi->cx[cxi->used-1].type = type; + status = csp_insert(&(cxi->cx[cxi->used-1].kids), src); + } + return(status); +} + + +/** + \brief Merge a complex dst with N members (N>=1) by adding a second complex src, and change the type. + \param cxi pointer to the CX_INFO structure (complexes). + \param dst index of the complex to expand. + \param src index of the donor complex (which is not modified). + \param type TR_LINE (src is an index for tpi->chunks[]) or TR_PARA (src is an index for cxi->kids[]). + \returns 0 on success, !0 on error. +*/ +int cxinfo_merge(CX_INFO *cxi, int dst, int src, enum tr_classes type){ + int status =1; + if(!cxi)return(2); + if(!cxi->used)return(3); + if(dst < 0 || dst >= (int) cxi->used)return(4); + if(src < 0)return(5); + cxi->cx[dst].type = type; + status = csp_merge(&(cxi->cx[dst].kids), &(cxi->cx[src].kids)); + return(status); +} + +/** + \brief Trim the last complex from thelist of complexes. + \param cxi pointer to the CX_INFO structure (complexes). + \returns 0 on success, !0 on error. +*/ +int cxinfo_trim(CX_INFO *cxi){ + int status = 0; + int last ; + if(!cxi)return(1); + if(!cxi->used)return(2); + last = cxi->used - 1; + csp_clear(&(cxi->cx[last].kids)); + cxi->used--; + return(status); +} + + +/** + \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code. + \param tri pointer to the TR_INFO structure. +*/ +void cxinfo_dump(const TR_INFO *tri){ + uint32_t i,j,k; + CX_INFO *cxi = tri->cxi; + BR_INFO *bri = tri->bri; + TP_INFO *tpi = tri->tpi; + BRECT_SPECS *bsp; + CX_SPECS *csp; + if(cxi){ + printf("cxi space: %d\n",cxi->space); + printf("cxi used: %d\n",cxi->used); + printf("cxi phase1: %d\n",cxi->phase1); + printf("cxi lines: %d\n",cxi->lines); + printf("cxi paras: %d\n",cxi->paras); + printf("cxi xy: %f , %f\n",tri->x,tri->y); + + for(i=0;i<cxi->used;i++){ + csp = &(cxi->cx[i]); + bsp = &(bri->rects[csp->rt_cidx]); + printf("cxi cx[%d] type:%d rt_tidx:%d kids_used:%d kids_space:%d\n",i, csp->type, csp->rt_cidx, csp->kids.used, csp->kids.space); + printf("cxi cx[%d] br (LL,UR) (%f,%f),(%f,%f)\n",i,bsp->xll,bsp->yll,bsp->xur,bsp->yur); + for(j=0;j<csp->kids.used;j++){ + k = csp->kids.members[j]; + bsp = &(bri->rects[k]); + if(csp->type == TR_TEXT || csp->type == TR_LINE){ + printf("cxi cx[%d] member:%3d tp_idx:%3d ldir:%d rt_tidx:%3d br (LL,UR) (%8.3f,%8.3f),(%8.3f,%8.3f) xy (%8.3f,%8.3f) kern (%8.3f,%8.3f) text:<%s> decor:%5.5x\n", + i, j, k, tpi->chunks[k].ldir, tpi->chunks[k].rt_tidx, + bsp->xll,bsp->yll,bsp->xur,bsp->yur, + tpi->chunks[k].x, tpi->chunks[k].y, + tpi->chunks[k].xkern, tpi->chunks[k].ykern, + tpi->chunks[k].string, tpi->chunks[k].decoration ); + } + else { /* TR_PARA_* */ + printf("cxi cx[%d] member:%d cx_idx:%d\n",i, j, k); + } + } + } + } + return; +} + +/** + \brief Release a CX_INFO structure. Release all associated memory. + use like: cxi = cxiinfo_release(cxi); + \param cxi pointer to the CX_INFO structure. + \returns NULL. +*/ +CX_INFO *cxinfo_release(CX_INFO *cxi){ + uint32_t i; + if(cxi){ + for(i=0;i<cxi->used;i++){ csp_release(&cxi->cx[i].kids); } + free(cxi->cx); + free(cxi); /* release the overall cxinfo structure */ + } + return NULL; +} + + +/** + \brief Initialize an TP_INFO structure. Holds text objects from which complexes are built. + \returns a pointer to the TP_INFO structure created, or NULL on error. +*/ +TP_INFO *tpinfo_init(void){ + TP_INFO *tpi = NULL; + tpi = (TP_INFO *)calloc(1,sizeof(TP_INFO)); + if(tpi){ + if(tpinfo_make_insertable(tpi)){ + free(tpi); + tpi=NULL; + } + } + return(tpi); +} + + +/** + \brief Make a TP_INFO structure insertable. Adds storage as needed. + \returns 0 on success, !0 on error. + \param tpi pointer to the TP_INFO structure +*/ +int tpinfo_make_insertable(TP_INFO *tpi){ + int status=0; + TCHUNK_SPECS *tmp; + if(tpi->used >= tpi->space){ + tpi->space += ALLOCINFO_CHUNK; + tmp = (TCHUNK_SPECS *) realloc(tpi->chunks, tpi->space * sizeof(TCHUNK_SPECS) ); + if(tmp){ + tpi->chunks = tmp; + memset(&tpi->chunks[tpi->used],0,(tpi->space - tpi->used)*sizeof(TCHUNK_SPECS)); + } + else { + status=1; + } + } + return(status); +} + +/** + \brief Insert a copy of a TCHUNK_SPECS structure into a TP_INFO structure. (Insert a text object.) + \returns 0 on success, !0 on error. + \param tpi pointer to the TP_INFO structure + \param tsp pointer to the TCHUNK_SPECS structure +*/ +int tpinfo_insert(TP_INFO *tpi, const TCHUNK_SPECS *tsp){ + int status=1; + TCHUNK_SPECS *ltsp; + if(!tpi)return(2); + if(!tsp)return(3); + if(!(status = tpinfo_make_insertable(tpi))){ + ltsp = &(tpi->chunks[tpi->used]); + memcpy(ltsp,tsp,sizeof(TCHUNK_SPECS)); + if(tsp->co)ltsp->condensed = 75; /* Narrow was set in the font name */ + ltsp->xkern = ltsp->ykern = 0.0; /* kerning will be calculated from the derived layout */ + tpi->used++; + } + return(status); +} + +/** + \brief Release a TP_INFO structure. Release all associated memory. + use like: tpi = tpinfo_release(tpi); + \returns NULL. + \param tpi pointer to the TP_INFO structure. +*/ +TP_INFO *tpinfo_release(TP_INFO *tpi){ + uint32_t i; + if(tpi){ + for(i=0;i<tpi->used;i++){ + free(tpi->chunks[i].string); } + free(tpi->chunks); /* release the array */ + free(tpi); /* release the overall tpinfo structure */ + } + return NULL; +} + +/** + \brief Initialize an BR_INFO structure. Holds bounding rectangles, for both text objects and complexes. + \returns a pointer to the BR_INFO structure created, or NULL on error. +*/ +BR_INFO *brinfo_init(void){ + BR_INFO *bri = NULL; + bri = (BR_INFO *)calloc(1,sizeof(BR_INFO)); + if(bri){ + if(brinfo_make_insertable(bri)){ + free(bri); + bri=NULL; + } + } + return(bri); +} + +/** + \brief Make a BR_INFO structure insertable. Adds storage as needed. + \returns 0 on success, !0 on error. + \param bri pointer to the BR_INFO structure +*/ +int brinfo_make_insertable(BR_INFO *bri){ + int status=0; + BRECT_SPECS *tmp; + if(!bri)return(2); + if(bri->used >= bri->space){ + bri->space += ALLOCINFO_CHUNK; + tmp = (BRECT_SPECS *) realloc(bri->rects, bri->space * sizeof(BRECT_SPECS) ); + if(tmp){ bri->rects = tmp; } + else { status = 1;} + } + return(status); +} + +/** + \brief Insert a copy of a BRECT_SPEC structure into a BR_INFO structure. (Insert a bounding rectangle.) + \returns 0 on success, !0 on error. + \param bri pointer to the BR_INFO structure + \param element pointer to the BRECT_SPECS structure +*/ +int brinfo_insert(BR_INFO *bri, const BRECT_SPECS *element){ + int status=1; + if(!bri)return(2); + if(!(status=brinfo_make_insertable(bri))){ + memcpy(&(bri->rects[bri->used]),element,sizeof(BRECT_SPECS)); + bri->used++; + } + return(status); +} + +/** + \brief Merge BRECT_SPEC element src into/with BRECT_SPEC element dst. src is unchanged. (Merge two bounding rectangles.) + \returns 0 on success, !0 on error. + \param bri pointer to the BR_INFO structure + \param dst index of the destination bounding rectangle. + \param src index of the source bounding rectangle. +*/ +int brinfo_merge(BR_INFO *bri, int dst, int src){ + if(!bri)return(1); + if(!bri->used)return(2); + if(dst<0 || dst >= (int) bri->used)return(3); + if(src<0 || src >= (int) bri->used)return(4); + bri->rects[dst].xll = TEREMIN(bri->rects[dst].xll, bri->rects[src].xll); + bri->rects[dst].yll = TEREMAX(bri->rects[dst].yll, bri->rects[src].yll); /* MAX because Y is positive DOWN */ + bri->rects[dst].xur = TEREMAX(bri->rects[dst].xur, bri->rects[src].xur); + bri->rects[dst].yur = TEREMIN(bri->rects[dst].yur, bri->rects[src].yur); /* MIN because Y is positive DOWN */ +/* +printf("bri_Merge into rect:%d (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",dst, +(bri->rects[dst].xll), +(bri->rects[dst].yll), +(bri->rects[dst].xur), +(bri->rects[dst].yur), +(bri->rects[src].xll), +(bri->rects[src].yll), +(bri->rects[src].xur), +(bri->rects[src].yur)); +*/ + return(0); +} + +/** + \brief Check for an allowable overlap of two bounding rectangles. + Allowable overlap is any area overlap of src and dst bounding rectangles, after + they have been expanded (padded) by allowed edge expansions. (For instance, if + missing spaces must be accounted for.) + The method works backwards: look for all reasons they might not overlap, + if none are found, then the rectangles do overlap. + An overlap here does not count just a line or a point - area must be involved. + \returns 0 on success (overlap detected), 1 on no overlap, anything else is an error. + \param bri pointer to the BR_INFO structure + \param dst index of the destination bounding rectangle. + \param src index of the source bounding rectangle. + \param rp_dst Pointer to edge padding values for dst. + \param rp_src Pointer to edge padding values for src. +*/ +int brinfo_overlap(const BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src){ + int status; + BRECT_SPECS *br_dst; + BRECT_SPECS *br_src; + if(!bri || !rp_dst || !rp_src)return(2); + if(!bri->used)return(3); + if(dst<0 || dst>= (int) bri->used)return(4); + if(src<0 || src>= (int) bri->used)return(5); + br_dst=&bri->rects[dst]; + br_src=&bri->rects[src]; + if( /* Test all conditions that exclude overlap, if any are true, then no overlap */ + ((br_dst->xur + rp_dst->right) < (br_src->xll - rp_src->left) ) || /* dst fully to the left */ + ((br_dst->xll - rp_dst->left) > (br_src->xur + rp_src->right) ) || /* dst fully to the right */ + ((br_dst->yur - rp_dst->up) > (br_src->yll + rp_src->down) ) || /* dst fully below (Y is positive DOWN) */ + ((br_dst->yll + rp_dst->down) < (br_src->yur - rp_src->up) ) /* dst fully above (Y is positive DOWN) */ + ){ + status = 1; + } + else { + /* overlap not excluded, so it must occur. + Only accept overlaps that are mostly at one end or the other, not mostly top or bottom. + If the following condition is true then there is no more than a tiny bit of horizontal overlap of src + within dist, which suggests that the two pieces of text may be considered part of one line. + (For a vertical alphabet the same method could be used for up/down.) */ + if( + (br_src->xll >= br_dst->xur - rp_dst->right) || /* src overlaps just a little on the right (L->R language) */ + (br_src->xur <= br_dst->xll + rp_dst->left) /* src overlaps just a little on the left (R->L language) */ + ){ + status = 0; + } + else { /* Too much overlap, reject the overlap */ + status = 1; + } + } +/* +printf("Overlap status:%d\nOverlap trects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n", +status, +(br_dst->xll - rp_dst->left ), +(br_dst->yll - rp_dst->down ), +(br_dst->xur + rp_dst->right), +(br_dst->yur + rp_dst->up ), +(br_src->xll - rp_src->left ), +(br_src->yll - rp_src->down ), +(br_src->xur + rp_src->right), +(br_src->yur + rp_src->up )); +printf("Overlap brects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n", +(br_dst->xll), +(br_dst->yll), +(br_dst->xur), +(br_dst->yur), +(br_src->xll), +(br_src->yll), +(br_src->xur), +(br_src->yur)); +printf("Overlap rprect (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n", +(rp_dst->left), +(rp_dst->down), +(rp_dst->right), +(rp_dst->up), +(rp_src->left), +(rp_src->down), +(rp_src->right), +(rp_src->up)); +*/ + return(status); +} + +/** + \brief Check for various sorts of invalid text elements upstream (language dir changes, draw order backwards from language direction) + \returns 0 on success (not upstream), 1 if upstream, anything else is an error. + \param bri pointer to the BR_INFO structure + \param dst index of the destination bounding rectangle. + \param src index of the source bounding rectangle. + \param ddir direction of dst + \param sdir direction of src +*/ + +int brinfo_upstream(BR_INFO *bri, int dst, int src, int ddir, int sdir){ + int status=0; + BRECT_SPECS *br_dst; + BRECT_SPECS *br_src; + if(!bri)return(2); + if(!bri->used)return(3); + if(dst<0 || dst>= (int) bri->used)return(4); + if(src<0 || src>= (int) bri->used)return(5); + br_dst=&bri->rects[dst]; + br_src=&bri->rects[src]; + if( ddir == LDIR_RL && sdir == LDIR_LR){ + if(br_dst->xur <= (br_src->xll + br_src->xur)/2.0){ status = 1; } + } + else if( ddir == LDIR_LR && sdir == LDIR_RL){ + if((br_src->xll + br_src->xur)/2.0 <= br_dst->xll ){ status = 1; } + } + else if( ddir == LDIR_RL && sdir == LDIR_RL){ + if(br_dst->xur <= (br_src->xll + br_src->xur)/2.0){ status = 1; } + } + else if( ddir == LDIR_LR && sdir == LDIR_LR){ + if((br_src->xll + br_src->xur)/2.0 <= br_dst->xll ){ status = 1; } + } + return(status); +} + + +/** + \brief Try to deduce justification of a paragraph from the bounding rectangles for two successive lines. + \returns one of TR_PARA_ UJ (unknown justified), LJ, CJ, or RJ (left, center, or right justified). + \param bri pointer to the BR_INFO structure + \param dst index of the destination bounding rectangle. + \param src index of the source bounding rectangle. + \param slop allowed error in edge alignment. + \param type Preexisting justification for dst, if any. Justification of dst and src must match this or + TR_PARA_UJ is returned even if dst and src have some (other) alignment. +*/ +enum tr_classes brinfo_pp_alignment(const BR_INFO *bri, int dst, int src, double slop, enum tr_classes type){ + enum tr_classes newtype; + BRECT_SPECS *br_dst = & bri->rects[dst]; + BRECT_SPECS *br_src = & bri->rects[src]; + if((br_dst->yur >= br_src->yur) || (br_dst->yll >= br_src->yll)){ /* Y is positive DOWN */ + /* lines in the wrong vertical order, no paragraph possible (Y is positive down) */ + newtype = TR_PARA_UJ; + } + else if(fabs(br_dst->xll - br_src->xll) < slop){ + /* LJ (might also be CJ but LJ takes precedence) */ + newtype = TR_PARA_LJ; + } + else if(fabs(br_dst->xur - br_src->xur) < slop){ + /* RJ */ + newtype = TR_PARA_RJ; + } + else if(fabs( (br_dst->xur + br_dst->xll)/2.0 - (br_src->xur + br_src->xll)/2.0 ) < slop){ + /* CJ */ + newtype = TR_PARA_CJ; + } + else { + /* not aligned */ + newtype = TR_PARA_UJ; + } + /* within a paragraph type can change from unknown to known, but not from one known type to another*/ + if((type != TR_PARA_UJ) && (newtype != type)){ + newtype = TR_PARA_UJ; + } +/* +printf("pp_align newtype:%d brects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n", +newtype, +(br_dst->xll), +(br_dst->yll), +(br_dst->xur), +(br_dst->yur), +(br_src->xll), +(br_src->yll), +(br_src->xur), +(br_src->yur)); +*/ + return(newtype); +} + +/** + \brief Release a BR_INFO structure. Release all associated memory. + use like: bri = brinfo_release(bri); + \param bri pointer to the BR_INFO structure. + \returns NULL. +*/ +BR_INFO *brinfo_release(BR_INFO *bri){ + if(bri){ + free(bri->rects); + free(bri); /* release the overall brinfo structure */ + } + return NULL; +} + + + +/** + \brief Initialize an TR_INFO structure. Holds all data for text reassembly. + \returns a pointer to the TR_INFO structure created, or NULL on error. +*/ +TR_INFO *trinfo_init(TR_INFO *tri){ + if(tri)return(tri); /* tri is already set, double initialization is not allowed */ + if(!(tri = (TR_INFO *)calloc(1,sizeof(TR_INFO))) || + !(tri->fti = ftinfo_init()) || + !(tri->tpi = tpinfo_init()) || + !(tri->bri = brinfo_init()) || + !(tri->cxi = cxinfo_init()) + ){ tri = trinfo_release(tri); } + tri->out = NULL; /* This will allocate as needed, it might not ever be needed. */ + tri->qe = 0.0; + tri->esc = 0.0; + tri->x = DBL_MAX; + tri->y = DBL_MAX; + tri->dirty = 0; + tri->use_kern = 1; + tri->load_flags = FT_LOAD_NO_SCALE; + tri->kern_mode = FT_KERNING_UNSCALED; + tri->outspace = 0; + tri->outused = 0; + tri->usebk = BKCLR_NONE; + memset(&(tri->bkcolor),0,sizeof(TRCOLORREF)); + return(tri); +} + +/** + \brief Release a TR_INFO structure completely. + Release all associated memory, including FontConfig. + See also trinfo_clear() and trinfo_release_except_FC(). + use like: tri = trinfo_release(tri); + \param tri pointer to the TR_INFO structure. + \returns NULL. +*/ +TR_INFO *trinfo_release(TR_INFO *tri){ + if(tri){ + if(tri->bri)tri->bri=brinfo_release(tri->bri); + if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi); + if(tri->fti)tri->fti=ftinfo_release(tri->fti); + if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi); + if(tri->out){ free(tri->out); tri->out=NULL; }; + free(tri); + } + return(NULL); +} + +/** + \brief Release a TR_INFO structure mostly. + Release all associated memory EXCEPT Fontconfig. + Fontconfig may still be needed elsewhere in a program and there is no way to figure that out here. + See also trinfo_clear() and trinfo_release(). + use like: tri = trinfo_release_except_FC(tri); + \param tri pointer to the TR_INFO structure. + \returns NULL. +*/ +TR_INFO *trinfo_release_except_FC(TR_INFO *tri){ + if(tri){ + if(tri->bri)tri->bri=brinfo_release(tri->bri); + if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi); + if(tri->fti)tri->fti=ftinfo_clear(tri->fti); + if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi); + if(tri->out){ free(tri->out); tri->out=NULL; }; + free(tri); + } + return(NULL); +} + +/** + \brief Clear a TR_INFO structure. + Releases text and rectangle information, but retains font information, both + Freetype information and Fontconfig information. + See also trinfo_release() and trinfo_release_except_FC(). + Use like: tri = trinfo_clear(tri); + \param tri pointer to the TR_INFO structure. + \returns NULL. +*/ +TR_INFO *trinfo_clear(TR_INFO *tri){ + if(tri){ + + if(tri->bri)tri->bri=brinfo_release(tri->bri); + if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi); + if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi); + if(tri->out){ + free(tri->out); + tri->out = NULL; + tri->outused = 0; + tri->outspace = 0; + }; + /* Do NOT modify: qe, use_kern, usebk, load_flags, kern_mode, or bkcolor. Set the rest back to their defaults */ + tri->esc = 0.0; + tri->x = DBL_MAX; + tri->y = DBL_MAX; + tri->dirty = 0; + if(!(tri->tpi = tpinfo_init()) || /* re-init the pieces just released */ + !(tri->bri = brinfo_init()) || + !(tri->cxi = cxinfo_init()) + ){ + tri = trinfo_release(tri); /* something horrible happened, clean out tri and return NULL */ + } + } + return(tri); +} + + +/** + \brief Set the quantization error value for a TR_INFO structure. + If coordinates have passed through an integer form limits + in accuracy may have been imposed. For instance, if the X coordinate of a point in such a file + is 1000, and the conversion factor from those coordinates to points is .04, then eq is .04. This + just says that single coordinates are only good to within .04, and two coordinates may differ by as much + as .08, just due to quantization error. So if some calculation shows a difference of + .02 it may be interpreted as this sort of error and set to 0.0. + \returns 0 on success, !0 on error. + \param tri pointer to TR_INFO structure + \param qe quantization error. +*/ +int trinfo_load_qe(TR_INFO *tri, double qe){ + if(!tri)return(1); + if(qe<0.0)return(2); + tri->qe=qe; + return(0); +} + +/** + \brief Set the background color and whether or not to use it. + When background color is turned on each line of text is underwritten with a rectangle + of the specified color. The rectangle is the merged bounding rectangle for that line. + \returns 0 on success but nothing changed, >0 on error, <0 on success and a value changed. + \param tri pointer to TR_INFO structure + \param usebk 0 for no background, anything else uses background color + \param bkcolor background color to use +*/ +int trinfo_load_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor){ + int status=0; + if(!tri){ status = 1; } + else { + if((usebk < BKCLR_NONE) || (usebk > BKCLR_ALL)){ status = 2; } + else { + status = trinfo_check_bk(tri, usebk, bkcolor); + tri->usebk = usebk; + tri->bkcolor = bkcolor; + } + } + return(status); +} + +/** + \brief Are the proposed new background and background color a change? + \returns 0 if they are the same, -1 if either is different + \param tri pointer to TR_INFO structure + \param usebk 0 for no background, anything else uses background color + \param bkcolor background color to use +*/ +int trinfo_check_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor){ + int status = 0; + if( (tri->usebk != usebk) || memcmp(&tri->bkcolor,&bkcolor,sizeof(TRCOLORREF))){ status = -1; } + return(status); +} + +/** + \brief Set Freetype parameters and kerning mode (if any) in a TRI_INFO structure. + \returns 0 on success, !0 on error. + \param tri pointer to a TR_INFO structure + \param use_kern 0 if kerning is to be employed, !0 otherwise. + \param load_flags Controls internal advance: + FT_LOAD_NO_SCALE, internal advance is in 1/64th of a point. (kerning values are still scaled) + FT_LOAD_TARGET_NORMAL internal advance is in 1/64th of a point. The scale + factor seems to be (Font Size in points)*(DPI)/(32.0 pnts)*(72 dpi). + \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application. +*/ +int trinfo_load_ft_opts(TR_INFO *tri, int use_kern, int load_flags, int kern_mode){ + if(!tri)return(1); + tri->use_kern = use_kern; + tri->load_flags = load_flags; + tri->kern_mode = kern_mode; + return(0); +} + +/** + \brief Append text to a TR_INFO struct's output buffer, expanding it if necessary. + \returns 0 on success, !0 on error. + \param tri pointer to a TR_INFO structure + \param src Pointer to a text string. +*/ +int trinfo_append_out(TR_INFO *tri, const char *src){ + size_t slen; + uint8_t *tmp; + if(!src)return(-1); + slen = strlen(src); + if(tri->outused + (int) slen + 1 >= tri->outspace){ + tri->outspace += TEREMAX(ALLOCOUT_CHUNK,slen+1); + tmp = realloc(tri->out, tri->outspace * sizeof(uint8_t) ); + if(tmp){ tri->out = tmp; } + else { return(-1); } + } + memcpy(tri->out + tri->outused, src, slen+1); /* copy the terminator */ + tri->outused += slen; /* do not count the terminator in the length */ + return(0); +} + + +/** + \brief Load a text object into a TR_INFO struct. + \returns 0 on success, !0 on error. -1 means that the escapement is different from the objects already loaded. + \param tri pointer to a TR_INFO structure + \param tsp pointer to a TCHUNK_SPECS structure (text object to load) + \param escapement angle in degrees of the text object. + \param flags special processing flags: + TR_EMFBOT calculate Y coordinates of ALIBOT object compatible with EMF files TA_BOTTOM alignment. +*/ +int trinfo_load_textrec(TR_INFO *tri, const TCHUNK_SPECS *tsp, double escapement, int flags){ + + int status; + double x,y,xe; + double asc,dsc; /* these are the ascender/descender for the actual text */ + int ymin,ymax; + double fasc,fdsc; /* these are the ascender/descender for the font as a whole (text independent) */ + TP_INFO *tpi; + FT_INFO *fti; + BR_INFO *bri; + int current,idx,taln; + uint32_t prev; + uint32_t *text32,*tptr; + FNT_SPECS *fsp; + BRECT_SPECS bsp; + + /* check incoming parameters */ + if(!tri)return(1); + if(!tsp)return(2); + if(!tsp->string)return(3); + fti = tri->fti; + tpi = tri->tpi; + bri = tri->bri; + idx = tsp->fi_idx; + taln = tsp->taln; + if(!fti->used)return(4); + if(idx <0 || idx >= (int) fti->used)return(5); + fsp = &(fti->fonts[idx]); + + if(!tri->dirty){ + tri->x = tsp->x; + tri->y = tsp->y; + tri->esc = escapement; + tri->dirty = 1; + } + else { + if(tri->esc != escapement)return(-1); + } + + + tpinfo_insert(tpi,tsp); + current=tpi->used-1; + ymin = 64000; + ymax = -64000; + + /* The geometry model has origin Y at the top of screen, positive Y is down, maximum positive + Y is at the bottom of the screen. That makes "top" (by positive Y) actually the bottom + (as viewed on the screen.) */ + + escapement *= 2.0 * M_PI / 360.0; /* degrees to radians */ + x = tpi->chunks[current].x - tri->x; /* convert to internal orientation */ + y = tpi->chunks[current].y - tri->y; + tpi->chunks[current].x = x * cos(escapement) - y * sin(escapement); /* coordinate transformation */ + tpi->chunks[current].y = x * sin(escapement) + y * cos(escapement); + +/* Careful! face bbox does NOT scale with FT_Set_Char_Size +printf("Face idx:%d bbox: xMax/Min:%ld,%ld yMax/Min:%ld,%ld UpEM:%d asc/des:%d,%d height:%d size:%f\n", + idx, + fsp->face->bbox.xMax,fsp->face->bbox.xMin, + fsp->face->bbox.yMax,fsp->face->bbox.yMin, + fsp->face->units_per_EM,fsp->face->ascender,fsp->face->descender,fsp->face->height,fsp->fsize); +*/ + + text32 = U_Utf8ToUtf32le((char *) tsp->string,0,NULL); + if(!text32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail + text32 = U_Latin1ToUtf32le((char *) tsp->string,0,NULL); + if(!text32)return(5); + } + /* baseline advance is independent of character orientation */ + for(xe=0.0, prev=0, tptr=text32; *tptr; tptr++){ + status = TR_getadvance(fti, fsp, *tptr, (tri->use_kern ? prev: 0), tri->load_flags, tri->kern_mode, &ymin, &ymax); + if(status>=0){ + xe += ((double) status)/64.0; + } + else { return(6); } + prev=*tptr; + } + + /* Some glyphs in fonts have no vertical extent, for instance, Hebrew glyphs in Century Schoolbook L. + Use the 3/4 of the font size as a (very bad) approximation for the actual values. */ + if(ymin==0 && ymax==0){ + ymax = 0.75 * fsp->fsize * 64.0; + } + + asc = ((double) (ymax))/64.0; + dsc = ((double) (ymin))/64.0; /* This is negative */ +/* This did not work very well because the ascender/descender went well beyond the actual characters, causing + overlaps on lines that did not actually overlap (vertically). + asc = ((double) (fsp->face->ascender) )/64.0; + dsc = ((double) (fsp->face->descender))/64.0; +*/ + + free(text32); + + /* find the font ascender descender (general one, not specific for current text) */ + fasc = ((double) (fsp->face->ascender) )/64.0; + fdsc = ((double) (fsp->face->descender))/64.0; + + /* originally the denominator was just 32.0, but it broke when units_per_EM wasn't 2048 */ + double fixscale = tsp->fs/(((double) fsp->face->units_per_EM)/64.0); + if(tri->load_flags & FT_LOAD_NO_SCALE) xe *= fixscale; + + /* now place the rectangle using ALN information */ + if( taln & ALIHORI & ALILEFT ){ + bsp.xll = tpi->chunks[current].x; + bsp.xur = tpi->chunks[current].x + xe; + } + else if( taln & ALIHORI & ALICENTER){ + bsp.xll = tpi->chunks[current].x - xe/2.0; + bsp.xur = tpi->chunks[current].x + xe/2.0; + } + else{ /* taln & ALIHORI & ALIRIGHT */ + bsp.xll = tpi->chunks[current].x - xe; + bsp.xur = tpi->chunks[current].x; + } + tpi->chunks[current].ldir = tsp->ldir; + + if(tri->load_flags & FT_LOAD_NO_SCALE){ + asc *= fixscale; + dsc *= fixscale; + fasc *= fixscale; + fdsc *= fixscale; + } + + + /* From this point forward y is on the baseline, so need to correct it in chunks. The asc/dsc are the general + ones for the font, else the text content will muck around with the baseline in BAD ways. */ + if( taln & ALIVERT & ALITOP ){ tpi->chunks[current].y += fasc; } + else if( taln & ALIVERT & ALIBASE){ } /* no correction required */ + else{ /* taln & ALIVERT & ALIBOT */ + if(flags & TR_EMFBOT){ tpi->chunks[current].y -= 0.35 * tsp->fs; } /* compatible with EMF implementations */ + else { tpi->chunks[current].y += fdsc; } + } + tpi->chunks[current].boff = -dsc; + + /* since y is always on the baseline, the lower left and upper right are easy. These use asc/dsc for the particular text, + so that the bounding box will fit it tightly. */ + bsp.yll = tpi->chunks[current].y - dsc; + bsp.yur = tpi->chunks[current].y - asc; + brinfo_insert(bri,&bsp); + tpi->chunks[current].rt_tidx = bri->used - 1; /* index of rectangle that contains it */ + + return(0); +} + +/** + \brief Fontweight conversion. Fontconfig units to SVG units. + Anything not recognized becomes "normal" == 400. + There is no interpolation because a value that mapped to 775, for instance, most + likely would not display properly because it is intermediate between 700 and 800, and + only those need be supported in SVG viewers. + \returns SVG font weight + \param weight Fontconfig font weight. +*/ +int TR_weight_FC_to_SVG(int weight){ + int ret=400; + if( weight == 0){ ret = 100; } + else if(weight == 40){ ret = 200; } + else if(weight == 50){ ret = 300; } + else if(weight == 80){ ret = 400; } + else if(weight == 100){ ret = 500; } + else if(weight == 180){ ret = 600; } + else if(weight == 200){ ret = 700; } + else if(weight == 205){ ret = 800; } + else if(weight == 210){ ret = 900; } + else { ret = 400; } + return(ret); +} + +/** + \brief Set the padding that will be added to bounding rectangles before checking for overlaps in brinfo_overlap(). + \returns void + \param rt_pad pointer to an RT_PAD structure. + \param up padding for the top of a bounding rectangle. + \param down padding for the bottom of a bounding rectangle. + \param left padding for the left of a bounding rectangle. + \param right padding for the right of a bounding rectangle. +*/ +void TR_rt_pad_set(RT_PAD *rt_pad, double up, double down, double left, double right){ + rt_pad->up = up; + rt_pad->down = down; + rt_pad->left = left; + rt_pad->right = right; +} + +/** + \brief Convert from analyzed complexes to SVG format. + \returns void + \param tri pointer to a TR_INFO struct which will be analyzed. Result is stored in its "out" buffer. +*/ +void TR_layout_2_svg(TR_INFO *tri){ + double x = tri->x; + double y = tri->y; + double dx,dy; + double esc; + double recenter; /* horizontal offset to set things up correctly for CJ and RJ text, is 0 for LJ*/ + double lineheight=1.25; + int cutat; + FT_INFO *fti=tri->fti; /* Font info storage */ + TP_INFO *tpi=tri->tpi; /* Text Info/Position Info storage */ + BR_INFO *bri=tri->bri; /* bounding Rectangle Info storage */ + CX_INFO *cxi=tri->cxi; /* Complexes deduced for this text */ + TCHUNK_SPECS *tsp; /* current text object */ + CX_SPECS *csp; + CX_SPECS *cline_sp; + unsigned int i,j,k,jdx,kdx; + int ldir; + char obuf[1024]; /* big enough for style and so forth */ + char cbuf[16]; /* big enough for one hex color */ + + char stransform[128]; + double newx,newy,tmpx; + uint32_t utmp; + + /* copy the current numeric locale, make a copy because setlocale may stomp on + the memory it points to. Then change it because SVG needs decimal points, + not commas, in floats. Restore on exit from this routine. + */ + char *prev_locale = setlocale(LC_NUMERIC,NULL); + char *hold_locale = malloc(sizeof(char) * (strlen(prev_locale) + 1)); + strcpy(hold_locale,prev_locale); + (void) setlocale(LC_NUMERIC,"POSIX"); + +/* +#define DBG_TR_PARA 0 +#define DBG_TR_INPUT 1 +*/ + /* The debug section below is difficult to see if usebk is anything other than BKCLR_NONE */ +#if DBG_TR_PARA || DBG_TR_INPUT /* enable debugging code, writes extra information into SVG */ + /* put rectangles down for each text string - debugging!!! This will not work properly for any Narrow fonts */ + esc = tri->esc; + esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ + sprintf(stransform,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y); + for(i=cxi->phase1; i<cxi->used;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */ + csp = &(cxi->cx[i]); + for(j=0; j<csp->kids.used; j++){ /* over all members of these complexes, which are phase1 complexes */ + jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */ + for(k=0; k<cxi->cx[jdx].kids.used; k++){ /* over all members of the phase1 complex */ + kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */ + tsp = &tpi->chunks[kdx]; + ldir = tsp->ldir; + if(!j && !k){ +#if DBG_TR_PARA + TRPRINT(tri, "<rect\n"); + TRPRINT(tri, "style=\"color:#0000FF;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:none;stroke:#000000;stroke-width:0.8;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n"); + sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].xll),1.25*(bri->rects[csp->rt_cidx].yur)); + TRPRINT(tri, obuf); + TRPRINT(tri, stransform); + TRPRINT(tri, "/>\n"); +#endif /* DBG_TR_PARA */ + } +#if DBG_TR_INPUT /* debugging code, this section writes the original text objects */ + newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll); + newy = 1.25*(bri->rects[tsp->rt_tidx].yur); + TRPRINT(tri, "<rect\n"); + TRPRINT(tri, "style=\"color:#000000;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:none;stroke:#00FF00;stroke-width:0.3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n"); + sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].xll),newy); + TRPRINT(tri, obuf); + TRPRINT(tri, stransform); + TRPRINT(tri, "/>\n"); + + newy = 1.25*(bri->rects[tsp->rt_tidx].yll - tsp->boff); + sprintf(obuf,"<text x=\"%f\" y=\"%f\"\n",newx, newy ); + TRPRINT(tri, obuf); + sprintf(obuf,"xml:space=\"preserve\"\n"); + TRPRINT(tri, obuf); + TRPRINT(tri, stransform); + TRPRINT(tri, "style=\"fill:#FF0000;"); + sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/ + TRPRINT(tri, obuf); + sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal")); + TRPRINT(tri, obuf); + TRPRINT(tri, "font-variant:normal;"); + sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight)); + TRPRINT(tri, obuf); + sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed")); + TRPRINT(tri, obuf); + sprintf(obuf,"text-anchor:%s;",(tsp->ldir == LDIR_RL ? "end" : "start")); + TRPRINT(tri, obuf); + cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":"); + sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec); + TRPRINT(tri, obuf); + sprintf(obuf,"\n\">%s</text>\n",&tsp->string[tsp->spaces]); + TRPRINT(tri, obuf); +#endif /* DBG_TR_INPUT debugging code, original text objects */ + } + } + } +#endif /* DBG_TR_PARA and/or DBG_TR_INPUT */ + + + if(tri->usebk){ + esc = tri->esc; + esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ + sprintf(stransform,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y); + + for(i=cxi->phase1; i<cxi->used;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */ + TRPRINT(tri, "<g>\n"); /* group backgrounds for each <text> object in the SVG */ + csp = &(cxi->cx[i]); + for(j=0; j<csp->kids.used; j++){ /* over all members of these complexes, which are phase1 complexes */ + jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */ + cline_sp = &(cxi->cx[jdx]); + if(tri->usebk == BKCLR_LINE){ + TRPRINT(tri, "<rect\n"); + sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue); + TRPRINT(tri, obuf); + sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[cline_sp->rt_cidx].xur - bri->rects[cline_sp->rt_cidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[cline_sp->rt_cidx].yll - bri->rects[cline_sp->rt_cidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[cline_sp->rt_cidx].xll),1.25*(bri->rects[cline_sp->rt_cidx].yur)); + TRPRINT(tri, obuf); + TRPRINT(tri, stransform); + TRPRINT(tri, "/>\n"); + } + + for(k=0; k<cxi->cx[jdx].kids.used; k++){ /* over all members of the phase1 complex */ + kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */ + tsp = &tpi->chunks[kdx]; + ldir = tsp->ldir; + if(!j && !k){ + if(tri->usebk == BKCLR_ALL){ + TRPRINT(tri, "<rect\n"); + sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue); + TRPRINT(tri, obuf); + sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].xll),1.25*(bri->rects[csp->rt_cidx].yur)); + TRPRINT(tri, obuf); + TRPRINT(tri, stransform); + TRPRINT(tri, "/>\n"); + } + } + if(tri->usebk == BKCLR_FRAG){ + newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll); + newy = 1.25*(bri->rects[tsp->rt_tidx].yur); + TRPRINT(tri, "<rect\n"); + sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue); + TRPRINT(tri, obuf); + sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%f\" y=\"%f\"\n",newx,newy); + TRPRINT(tri, obuf); + TRPRINT(tri, stransform); + TRPRINT(tri, "/>\n"); + } + } + } + TRPRINT(tri, "</g>\n"); /* end of grouping for backgrounds for each <text> object in the SVG */ + } + } + + + /* over all complex members from phase2. Paragraphs == TR_PARA_* */ + for(i=cxi->phase1; i<cxi->used;i++){ + csp = &(cxi->cx[i]); + esc = tri->esc; + esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */ + + /* over all members of the present Paragraph. Each of these is a line and a phase 1 complex. + It may be either TR_TEXT or TR_LINE */ + for(j=0; j<csp->kids.used; j++){ + if(j){ + sprintf(obuf,"</tspan>"); + TRPRINT(tri, obuf); + } + jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */ + recenter = 0; /* mostly to quiet a compiler warning, should always be set below */ + + + /* over all members of the present Line. These are the original text objects which were reassembled. + There will be one for TR_TEXT, more than one for TR_LINE */ + for(k=0; k<cxi->cx[jdx].kids.used; k++){ + kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi, for this k */ + tsp = &tpi->chunks[kdx]; /* text chunk for this k */ + ldir = tsp->ldir; /* language direction for this k */ + if(!k){ /* first iteration */ + switch(csp->type){ /* set up the alignment, if there is one */ + case TR_TEXT: + case TR_LINE: + /* these should never occur, this section quiets a compiler warning */ + break; + case TR_PARA_UJ: + case TR_PARA_LJ: + if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); } + else { recenter = 0.0; } + break; + case TR_PARA_CJ: + if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; } + else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; } + break; + case TR_PARA_RJ: + if(ldir == LDIR_RL){ recenter = 0.0; } + else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); } + break; + } + if(!j){ + TRPRINT(tri, "<text\n"); + TRPRINT(tri, "xml:space=\"preserve\"\n"); + TRPRINT(tri, "style=\""); + sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/ + TRPRINT(tri, obuf); + sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal")); + TRPRINT(tri, obuf); + TRPRINT(tri, "font-variant:normal;"); + sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight)); + TRPRINT(tri, obuf); + sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed")); + TRPRINT(tri, obuf); + if(tsp->vadvance){ lineheight = tsp->vadvance *100.0; } + else { lineheight = 125.0; } + sprintf(obuf,"line-height:%f%%;",lineheight); + TRPRINT(tri, obuf); + TRPRINT(tri, "letter-spacing:0px;"); + TRPRINT(tri, "word-spacing:0px;"); + TRPRINT(tri, "fill:#000000;"); + TRPRINT(tri, "fill-opacity:1;"); + TRPRINT(tri, "stroke:none;"); + cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":"); + sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec); + TRPRINT(tri, obuf); + switch(csp->type){ /* set up the alignment, if there is one */ + case TR_TEXT: + case TR_LINE: + /* these should never occur, this section quiets a compiler warning */ + break; + case TR_PARA_UJ: + case TR_PARA_LJ: + sprintf(obuf,"text-align:start;text-anchor:start;"); + break; + case TR_PARA_CJ: + sprintf(obuf,"text-align:center;text-anchor:middle;"); + break; + case TR_PARA_RJ: + sprintf(obuf,"text-align:end;text-anchor:end;"); + break; + } + TRPRINT(tri, obuf); + TRPRINT(tri, "\"\n"); /* End of style specification */ + sprintf(obuf,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc),1.25*x,1.25*y); + TRPRINT(tri, obuf); + tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter); + sprintf(obuf,"x=\"%f\" y=\"%f\"\n>",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff)); + TRPRINT(tri, obuf); + } + tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter); + sprintf(obuf,"<tspan sodipodi:role=\"line\"\nx=\"%f\" y=\"%f\"\n>",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff)); + TRPRINT(tri, obuf); + } + TRPRINT(tri, "<tspan\n"); + + /* Scale kerning and make any other necessary adjustments + + */ + dx = 1.25 * tsp->xkern; + dy = 1.25 * tsp->ykern; + + sprintf(obuf,"dx=\"%f\" dy=\"%f\" ",dx, dy); + TRPRINT(tri, obuf); + sprintf(obuf,"style=\"fill:#%2.2X%2.2X%2.2X;",tsp->color.Red,tsp->color.Green,tsp->color.Blue); + TRPRINT(tri, obuf); + sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/ + TRPRINT(tri, obuf); + sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal")); + TRPRINT(tri, obuf); + if(tsp->decoration & TXTDECOR_TMASK){ + sprintf(obuf,"text-decoration:"); + /* multiple text decoration styles may be set */ + utmp = tsp->decoration & TXTDECOR_TMASK; + if(utmp & TXTDECOR_UNDER ){ strcat(obuf," underline"); } + if(utmp & TXTDECOR_OVER ){ strcat(obuf," overline"); } + if(utmp & TXTDECOR_BLINK ){ strcat(obuf," blink"); } + if(utmp & TXTDECOR_STRIKE){ strcat(obuf," line-through");} + if(*obuf){ + /* only a single text decoration line type may be set */ + switch(tsp->decoration & TXTDECOR_LMASK){ + case TXTDECOR_SOLID: break; // "solid" is the CSS 3 default, omitting it remains CSS 2 compatible + case TXTDECOR_DOUBLE: strcat(obuf," double"); break; // these are all CSS3 + case TXTDECOR_DOTTED: strcat(obuf," dotted"); break; + case TXTDECOR_DASHED: strcat(obuf," dashed"); break; + case TXTDECOR_WAVY: strcat(obuf," wavy" ); break; + default: break; + } + if((tsp->decoration & TXTDECOR_CLRSET) && memcmp(&(tsp->decColor),&(tsp->color),sizeof(TRCOLORREF))){ + /* CSS 3, CSS 2 implementations may choke on it. If the specified color matches text color omit, for better CSS 2 compatitiblity. */ + sprintf(cbuf," #%2.2X%2.2X%2.2X",tsp->decColor.Red,tsp->decColor.Green,tsp->decColor.Blue); + strcat(obuf,cbuf); + } + } + strcat(obuf,";"); + TRPRINT(tri,obuf); + } + TRPRINT(tri, "font-variant:normal;"); + sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight)); + TRPRINT(tri, obuf); + sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed")); + TRPRINT(tri, obuf); + cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":"); + sprintf(obuf,"font-family:%.*s;\"",cutat,fti->fonts[tsp->fi_idx].fontspec); + TRPRINT(tri, obuf); + TRPRINT(tri, "\n>"); + TRPRINT(tri, (char *) tsp->string); + TRPRINT(tri, "</tspan>"); + } /* end of k loop */ + } /* end of j loop */ + TRPRINT(tri,"</tspan></text>\n"); + } /* end of i loop */ + + /* restore locale and free memory. */ + (void) setlocale(LC_NUMERIC,hold_locale); + free(hold_locale); +} + +/** + \brief Attempt to figure out the original organization, in lines and paragraphs, of the text objects. + The method is: + 1. Generate complexes from the text objects (strings) by overlaps (optionally allowing up to two spaces to be + added) to produce larger rectangles. Complexes that are more or less sequential and have 2 or more text objects + are TR_LINEs, therwise they are TR_TEXT. + 2. Group sequential complexes (TR_LINE or TR_TEXT) into TR_PARA_UJ (paragraphs,by smooth progression in vertical + position down page). + 3. Analyze the paragraphs to classify them as Left/Center/Right justified (possibly with indentation.) If + they do not fall into any of these categories break that one back down into TR_LINE/TR_TEXT. + 4. Return the number of complex text objects. + \returns Number of complexes. (>=1, <= number of text objects.) <0 is an error. + \param tri pointer to the TR_INFO structure holding the data, which will also hold the results. +*/ +int TR_layout_analyze(TR_INFO *tri){ + unsigned int i,j,k; + int ok; + int cxidx; + int src_rt; + int dst_rt; + TP_INFO *tpi; + BR_INFO *bri; + CX_INFO *cxi; + FT_INFO *fti; + BRECT_SPECS bsp; + RT_PAD rt_pad_i; + RT_PAD rt_pad_j; + double ratio; + double qsp,dx,dy; + double spcadv; + enum tr_classes type; + TCHUNK_SPECS *tspi; + TCHUNK_SPECS *tspj; + TCHUNK_SPECS *tspRevEnd=NULL; + TCHUNK_SPECS *tspRevStart=NULL; + CX_SPECS *csp; + CHILD_SPECS *kidp; /* used with preceding complex (see below) */ + CHILD_SPECS *kidc; /* used with current complex (see below) */ + int lastldir,ldir,rev; + + if(!tri)return(-1); + if(!tri->cxi)return(-2); + if(!tri->tpi)return(-3); + if(!tri->bri)return(-4); + if(!tri->fti)return(-5); + tpi=tri->tpi; + cxi=tri->cxi; + bri=tri->bri; + fti=tri->fti; + cxi->lines = 0; + cxi->paras = 0; + cxi->phase1 = 0; + +/* When debugging + ftinfo_dump(fti); +*/ + /* Phase 1. Working sequentially, insert text. Initially as TR_TEXT and then try to extend to TR_LINE by checking + overlaps. When done the complexes will contain a mix of TR_LINE and TR_TEXT. */ + + for(i=0; i<tpi->used; i++){ + tspi = &(tpi->chunks[i]); + memcpy(&bsp,&(bri->rects[tspi->rt_tidx]),sizeof(BRECT_SPECS)); /* Must make a copy as next call may reallocate rects! */ + (void) brinfo_insert(bri,&bsp); + dst_rt = bri->used-1; + (void) cxinfo_insert(cxi, i, dst_rt, TR_TEXT); + cxidx = cxi->used-1; + + spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */ + /* for the leading text: pad with no leading and two trailing spaces, leading and trailing depend on direction */ + if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); } + else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); } + + for(j=i+1; j<tpi->used; j++){ + tspj = &(tpi->chunks[j]); + /* Reject font size changes of greater than 50%, these are almost certainly not continuous text. These happen + in math formulas, for instance, where a sum or integral is much larger than the other symbols. */ + ratio = (double)(tspj->fs)/(double)(tspi->fs); + if(ratio >2.0 || ratio <0.5)break; + + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */ + /* for the trailing text: pad with one leading and trailing spaces (so it should work L->R and R->L) */ + TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv); + src_rt = tspj->rt_tidx; + + /* Reject direction changes like [1 <- Hebrew][2 -> English], that is where the direction changes AND the + next logical piece of text is "upstream" positionally of its logical predecessor. The meaning of such + a construct is at best ambiguous. The test is only applied with respect to the first text chunk. This sort + of construct may appear when a valid initial construct like [1->English][2<-Hebrew][3->English] is edited + and the leading chunk of text removed. + + Also reject reversed order text as in (English) <A><B><C> (draw order) arranged as <C><B><A>. This happens + if the language direction field is incorrect, perhaps due to a corrupt or malformed input file. + */ + if(brinfo_upstream(bri, + dst_rt, /* index into bri for dst */ + src_rt, /* index into bri for src */ + tspi->ldir,tspj->ldir))break; + + if(!brinfo_overlap(bri, + dst_rt, /* index into bri for dst */ + src_rt, /* index into bri for src */ + &rt_pad_i,&rt_pad_j)){ + (void) cxinfo_append(cxi,j,TR_LINE); + (void) brinfo_merge(bri,dst_rt,src_rt); + /* for the leading text: pad with two leading and trailing spaces (so it should work L->R and R->L */ + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */ + TR_rt_pad_set(&rt_pad_i, tri->qe, tri->qe, + tri->qe + 2.0 * spcadv, tri->qe + 2.0 * spcadv); + } + else { /* either alignment ge*/ + break; + } + } + + /* Bidirectional text will cause complexes to not assemble in one pass. + This happens whenever a change of direction occurs with 2 or more sequential elements in + the opposite direction, + + Let + = LR and - = RL. + + Reading left to right, this happens with +-- or -++. + For instance, the sequence ++-+ ---+ would break into the two complexes shown. + Not until the last element in the second complex is added will the bounding rectangles for the complexes overlap. + + Check for this effect now if there is a preceding complex and the first element of the current complex is + reversed from the last in the preceding. */ + if(cxidx >= 1){ + kidp = &(cxi->cx[cxidx-1].kids); + kidc = &(cxi->cx[cxidx ].kids); + tspi = &(tpi->chunks[ kidp->members[kidp->used - 1] ]); /* here, the last text element in preceding complex */ + tspj = &(tpi->chunks[ kidc->members[0 ] ]); /* here, tge first text element in current complex */ + if(tspi->ldir != tspj->ldir){ + spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0; + if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); } + else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); } + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; + TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv); + if(!brinfo_overlap(bri, + cxi->cx[cxidx-1].rt_cidx, /* index into rt for dst cx */ + cxi->cx[cxidx].rt_cidx, /* index into rt for src cx */ + &rt_pad_i,&rt_pad_j)){ + /* Merge the current complex into the preceding one*/ + (void) cxinfo_merge(cxi, cxidx-1, cxidx, TR_LINE); + (void) brinfo_merge(bri,cxi->cx[cxidx-1].rt_cidx,cxi->cx[cxidx].rt_cidx); /* merge the bounding boxes*/ + (void) cxinfo_trim(cxi); + cxi->lines--; /* else the normal line count value is one too high */ + /* remove the current complex */ + } + } + } + + if(cxi->cx[cxidx].type == TR_LINE)cxi->lines++; + i=j-1; /* start up after the last merged entry (there may not be any) */ + } + cxi->phase1 = cxi->used; /* total complexes defined in this phase, all TR_LINE or TR_TEXT */ + + /* phase 1.5, calculate kerning. This is as good a place to do it as any. At this point all kern values + are zero. Each of these pieces is strictly unidirectional, but each piece can have a different direction. + The direction of the line is set by the first text element. The ends of runs of elements which are + reversed with respect to the line direction are special, everything else is simple: + Let: + == L->R, - == R->L, $ == end of text, the rules for kerning on B are: + A B others xkern + [+|$] + + [+|$] Bll - Aur + [-|$] - - [-|$] All - Bur (chs) + + - + [-|$] Bll - Aur (chs) + - + - [+|$] All - Bur + + - -...[-=C] [+|$] All - Cur (chs) + - + +...[+=C] [-|$] Cll - Aur + + chs = change sign, because dx is an absolute direction, and direction of text on RTL is in -x. + + Kerning calculations currently seems unstable for R->L if the kerning extends to the end of the line. If + the first and last characters are back in sync there are no issues. When things go south R->L left justified + text is not justified when read in. + */ + + for(i=0; i < cxi->phase1; i++){ /* over all lines */ + csp = &(cxi->cx[i]); + if(csp->kids.used < 2)continue; /* no kerning possible */ + tspi = &tpi->chunks[csp->kids.members[0]]; /* used here as last tsp. no kerning is applied to the first element */ + lastldir = ldir = tspi->ldir; + rev = 0; /* the first ldir defines forward and reverse */ + for(j=1; j<csp->kids.used; j++){ + tspj = &tpi->chunks[csp->kids.members[j]]; + ldir = tspj->ldir; + if(ldir != lastldir){ /* direction change */ + rev = !rev; /* reverse direction tracker */ + if(!rev){ /* back in original orientation */ + if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspRevStart->rt_tidx].xll; } + else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspRevStart->rt_tidx].xur; } + tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) - + (bri->rects[tspRevStart->rt_tidx].yll - tspRevStart->boff); + } + else { /* now in reversed orientation */ + tspRevStart = tspj; /* Save the beginning of this run (length >=1 ) */ + /* scan forward for the last text object in this orientation, include the first */ + for(k=j; k <csp->kids.used; k++){ + if(tpi->chunks[csp->kids.members[k]].ldir == ldir){ tspRevEnd = &tpi->chunks[csp->kids.members[k]]; } + else { break; } + } + if(lastldir == LDIR_RL){ tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; } + else { tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; } + tspj->ykern = (bri->rects[tspRevEnd->rt_tidx].yll - tspRevEnd->boff) - + (bri->rects[ tspi->rt_tidx].yll - tspi->boff ); + } + } + else { + if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; } + else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; } + tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) - + (bri->rects[tspi->rt_tidx].yll - tspi->boff); + } + + + /* + Sometimes a font substitution was absolutely terrible, for instance, for Arial Narrow on (most) Linux systems, + The resulting advance (xkern) may be much too large so that it overruns the next text chunk. Since + overlapping text on the same line is almost never encountered, this may be used to detect the bad + substitution so that a more appropriate offset can be used. + Detect this situation as a negative dx < 1/2 a space character's width while |dy| < an entire space width. + The y constraints allow super and subscripts, which overlap in x but are shifted above/below in y. + */ + spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; + qsp = 0.25 * spcadv; + dx = tspj->xkern; + dy = tspj->ykern; + if(dy <=qsp && dy >= -qsp){ + if(ldir==LDIR_RL){ + if(dx > 2*qsp)tspj->xkern = 0.0; + } + else { + if(dx < -2*qsp)tspj->xkern = 0.0; + } + } + + /* if x or y kern is less than twice the quantization error it is probably noise, set it to zero */ + if(fabs(tspj->xkern) <= 2.0*tri->qe)tspj->xkern = 0.0; + if(fabs(tspj->ykern) <= 2.0*tri->qe)tspj->ykern = 0.0; + + /* reintroduce spaces on the leading edge of text "j" if the kerning can be in part or in whole replaced + with 1 or 2 spaces */ + if(tspj->ykern == 0.0){ + double spaces = tspj->xkern/spcadv; /* negative on RL language, positive on LR */ + if((ldir == LDIR_RL && (spaces <= -0.9 && spaces >= -2.1)) || + (ldir == LDIR_LR && (spaces >= 0.9 && spaces <= 2.1)) ){ + int ispaces = lround(spaces); + tspj->xkern -= ((double)ispaces*spcadv); + if(ispaces<0)ispaces=-ispaces; + size_t slen = strlen((char *)tspj->string); + uint8_t *newstring = malloc(1 + ispaces + slen); + sprintf((char *)newstring," "); /* start with two spaces, possibly overwrite one in the next line */ + memcpy(newstring+ispaces,tspj->string,slen+1); /* copy existing string to proper position */ + free(tspj->string); + tspj->string = newstring; + tspj->spaces = ispaces; // only needed to fix optional debugging SVG output later + } + } + + tspi = tspj; + lastldir = ldir; + } + } + + + /* Phase 2, try to group sequential lines. There may be "lines" that are still TR_TEXT, as in: + + ... this is a sentence that wraps by one + word. + + And some paragrahs might be single word lines (+ = bullet in the following) + + +verbs + +nouns + +adjectives + + Everything starts out as TR_PARA_UJ and if the next one can be lined up, the type changes to + an aligned paragraph and complexes are appended to the existing one. + */ + + for(i=0; i < cxi->phase1; i++){ + type = TR_PARA_UJ; /* any paragraph alignment will be acceptable */ + /* Must make a copy as next call may reallocate rects, so if we just passed a pointer to something in the structure + it would vaporize part way through the call. */ + memcpy(&bsp,&(bri->rects[cxi->cx[i].rt_cidx]),sizeof(BRECT_SPECS)); + (void) brinfo_insert(bri,&bsp); + dst_rt = bri->used-1; + (void) cxinfo_insert(cxi, i, dst_rt, type); + + cxi->paras++; + ok = 1; + for(j=i+1; ok && (j < cxi->phase1); j++){ + type = brinfo_pp_alignment(bri, cxi->cx[i].rt_cidx, cxi->cx[j].rt_cidx, 3*tri->qe, type); + switch (type){ + case TR_PARA_UJ: /* paragraph type was set and j line does not fit, or no paragraph alignment matched */ + ok = 0; /* force exit from j loop */ + j--; /* this will increment at loop bottom */ + break; + case TR_PARA_LJ: + case TR_PARA_CJ: + case TR_PARA_RJ: + /* two successive lines have been identified (possible following others already in the paragraph */ + if(TR_check_set_vadvance(tri,j,i)){ /* check for compatibility with vadvance if set, set it if it isn't. */ + ok = 0; /* force exit from j loop */ + j--; /* this will increment at loop bottom */ + } + else { + src_rt = cxi->cx[j].rt_cidx; + (void) cxinfo_append(cxi, j, type); + (void) brinfo_merge(bri, dst_rt, src_rt); + } + break; + default: + return(-6); /* programming error */ + } + } + if(j>=cxi->phase1)break; + i=j-1; + } + + +/* When debugging + cxinfo_dump(tri); +*/ + + return(cxi->used); +} + + +/* no doxygen documentation below this point, these pieces are for the text program, not the library. */ + +#if TEST +#define MAXLINE 2048 /* big enough for testing */ +enum OP_TYPES {OPCOM,OPOOPS,OPFONT,OPESC,OPORI,OPXY,OPFS,OPTEXT,OPALN,OPLDIR,OPMUL,OPITA,OPWGT,OPDEC,OPCND,OPBKG,OPCLR,OPDCLR,OPBCLR,OPFLAGS,OPEMIT,OPDONE}; + +int parseit(char *buffer,char **data){ + int pre; + pre = strcspn(buffer,":"); + if(!pre)return(OPOOPS); + *data=&buffer[pre+1]; + buffer[pre]='\0'; + if(*buffer=='#' )return(OPCOM ); + if(0==strcmp("FONT",buffer))return(OPFONT); + if(0==strcmp("ESC" ,buffer))return(OPESC ); + if(0==strcmp("ORI", buffer))return(OPORI ); + if(0==strcmp("XY", buffer))return(OPXY ); + if(0==strcmp("FS", buffer))return(OPFS ); + if(0==strcmp("TEXT",buffer))return(OPTEXT); + if(0==strcmp("ALN", buffer))return(OPALN ); + if(0==strcmp("LDIR",buffer))return(OPLDIR); + if(0==strcmp("MUL", buffer))return(OPMUL ); + if(0==strcmp("ITA", buffer))return(OPITA ); + if(0==strcmp("WGT", buffer))return(OPWGT ); + if(0==strcmp("DEC", buffer))return(OPDEC ); + if(0==strcmp("CND", buffer))return(OPCND ); + if(0==strcmp("BKG", buffer))return(OPBKG ); + if(0==strcmp("CLR", buffer))return(OPCLR ); + if(0==strcmp("DCLR", buffer))return(OPDCLR ); + if(0==strcmp("BCLR",buffer))return(OPBCLR ); + if(0==strcmp("FLAG",buffer))return(OPFLAGS); + if(0==strcmp("EMIT",buffer))return(OPEMIT); + if(0==strcmp("DONE",buffer))return(OPDONE); + return(OPOOPS); +} + +void boom(char *string,int lineno){ + fprintf(stderr,"Fatal error at line %d %s\n",lineno,string); + exit(EXIT_FAILURE); +} + + +void init_as_svg(TR_INFO *tri){ + TRPRINT(tri,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"); + TRPRINT(tri,"<!-- Created with Inkscape (http://www.inkscape.org/) -->\n"); + TRPRINT(tri,"\n"); + TRPRINT(tri,"<svg\n"); + TRPRINT(tri," xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); + TRPRINT(tri," xmlns:cc=\"http://creativecommons.org/ns#\"\n"); + TRPRINT(tri," xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n"); + TRPRINT(tri," xmlns:svg=\"http://www.w3.org/2000/svg\"\n"); + TRPRINT(tri," xmlns=\"http://www.w3.org/2000/svg\"\n"); + TRPRINT(tri," xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"); + TRPRINT(tri," xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n"); + TRPRINT(tri," width=\"900\"\n"); + TRPRINT(tri," height=\"675\"\n"); + TRPRINT(tri," id=\"svg4122\"\n"); + TRPRINT(tri," version=\"1.1\"\n"); + TRPRINT(tri," inkscape:version=\"0.48+devel r11679 custom\"\n"); + TRPRINT(tri," sodipodi:docname=\"simplest_text.svg\">\n"); + TRPRINT(tri," <defs\n"); + TRPRINT(tri," id=\"defs4124\" />\n"); + TRPRINT(tri," <sodipodi:namedview\n"); + TRPRINT(tri," id=\"base\"\n"); + TRPRINT(tri," pagecolor=\"#ffffff\"\n"); + TRPRINT(tri," bordercolor=\"#666666\"\n"); + TRPRINT(tri," borderopacity=\"1.0\"\n"); + TRPRINT(tri," inkscape:pageopacity=\"0.0\"\n"); + TRPRINT(tri," inkscape:pageshadow=\"2\"\n"); + TRPRINT(tri," inkscape:zoom=\"0.98994949\"\n"); + TRPRINT(tri," inkscape:cx=\"309.88761\"\n"); + TRPRINT(tri," inkscape:cy=\"482.63995\"\n"); + TRPRINT(tri," inkscape:document-units=\"px\"\n"); + TRPRINT(tri," inkscape:current-layer=\"layer1\"\n"); + TRPRINT(tri," showgrid=\"false\"\n"); + TRPRINT(tri," width=\"0px\"\n"); + TRPRINT(tri," height=\"0px\"\n"); + TRPRINT(tri," fit-margin-top=\"0\"\n"); + TRPRINT(tri," fit-margin-left=\"0\"\n"); + TRPRINT(tri," fit-margin-right=\"0\"\n"); + TRPRINT(tri," fit-margin-bottom=\"0\"\n"); + TRPRINT(tri," units=\"in\"\n"); + TRPRINT(tri," inkscape:window-width=\"1200\"\n"); + TRPRINT(tri," inkscape:window-height=\"675\"\n"); + TRPRINT(tri," inkscape:window-x=\"26\"\n"); + TRPRINT(tri," inkscape:window-y=\"51\"\n"); + TRPRINT(tri," inkscape:window-maximized=\"0\" />\n"); + TRPRINT(tri," <metadata\n"); + TRPRINT(tri," id=\"metadata4127\">\n"); + TRPRINT(tri," <rdf:RDF>\n"); + TRPRINT(tri," <cc:Work\n"); + TRPRINT(tri," rdf:about=\"\">\n"); + TRPRINT(tri," <dc:format>image/svg+xml</dc:format>\n"); + TRPRINT(tri," <dc:type\n"); + TRPRINT(tri," rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" />\n"); + TRPRINT(tri," <dc:title></dc:title>\n"); + TRPRINT(tri," </cc:Work>\n"); + TRPRINT(tri," </rdf:RDF>\n"); + TRPRINT(tri," </metadata>\n"); + TRPRINT(tri," <g\n"); + TRPRINT(tri," inkscape:label=\"Layer 1\"\n"); + TRPRINT(tri," inkscape:groupmode=\"layer\"\n"); + TRPRINT(tri," id=\"layer1\"\n"); + TRPRINT(tri," transform=\"translate(0,0)\">\n"); + TRPRINT(tri,"\n"); +} + + +void flush_as_svg(TR_INFO *tri, FILE *fp){ + fwrite(tri->out,tri->outused,1,fp); +} + +FILE *close_as_svg(TR_INFO *tri, FILE *fp){ + TRPRINT(tri, " </g>\n"); + TRPRINT(tri, "</svg>\n"); + flush_as_svg(tri,fp); + fclose(fp); + return(NULL); +} + + +int main(int argc, char *argv[]){ + char *data; + char inbuf[MAXLINE]; + FILE *fpi = NULL; + FILE *fpo = NULL; + int op; + double fact = 1.0; /* input units to points */ + double escapement = 0.0; /* degrees */ + int lineno = 0; + int ok = 1; + int status; + TCHUNK_SPECS tsp; + TR_INFO *tri=NULL; + int flags=0; + char *infile; + uint32_t utmp32; + TRCOLORREF bkcolor; + int bkmode; + char *fontspec; + + infile=malloc(strlen(argv[1])+1); + strcpy(infile,argv[1]); + + if(argc < 2 || !(fpi = fopen(infile,"r"))){ + printf("Usage: text_reassemble input_file\n"); + printf(" Test program reads an input file containing lines like:\n"); + printf(" FONT:(font for next text)\n"); + printf(" ESC:(escapement angle degrees of text line, up from X axis)\n"); + printf(" ORI:(angle degrees of character orientation, up from X axis)\n"); + printf(" FS:(font size, units)\n"); + printf(" XY:(x,y) X 0 is at left, N is at right, Y 0 is at top, N is at bottom, as page is viewed.\n"); + printf(" TEXT:(UTF8 text)\n"); + printf(" ALN:combination of {LCR}{BLT} = Text is placed on {X,Y} at Left/Center/Right of text, at Bottom,baseLine,Top of text.\n"); + printf(" LDIR:{LR|RL|TB) Left to Right, Right to Left, and Top to Bottom \n"); + printf(" MUL:(float, multiplicative factor to convert FS,XY units to points).\n"); + printf(" ITA:(Italics, 0=normal, 100=italics, 110=oblique).\n"); + printf(" WGT:(Weight, 0-215: 80=normal, 200=bold, 215=ultrablack, 0=thin)).\n"); + printf(" DEC:(this is a bit field. For color see DCLR\n"); + printf(" style: 000 none, 001 underline,002 overline, 004 blink, 008 strike-through\n"); + printf(" line: 000 solid, 010 double, 020 dotted, 040 dashed, 080 wavy)\n"); + printf(" CND:(Condensed 50-200: 100=normal, 50=ultracondensed, 75=condensed, 200=expanded).\n"); + printf(" BKG:(Background color: 0 none, 1 by input fragment, 2 by assembled line, 3 by entire assembly. Use BCLR, THEN BKG) \n"); + printf(" CLR:(Text RGB color, as 6 HEX digits, like: FF0000 (red) or 0000FF (blue)) \n"); + printf(" DCLR:(Decoration color, specify like CLR, except 1000000 or higher disables.)\n"); + printf(" BCLR:(Background RGB color, specify like CLR.) \n"); + printf(" FLAG: Special processing options. 1 EMF compatible text alignment.\n"); + printf(" EMIT:(Process everything up to this point, then start clean for remaining input).\n"); + printf(" DONE:(no more input, process it).\n"); + printf(" # comment\n"); + printf("\n"); + printf(" The output is a summary of how the pieces are to be assembled into complex text.\n"); + printf("\n"); + printf(" egrep pattern: '^LOAD:|^FONT:|^ESC:|^ORI:|^FS:|^XY:|^TEXT:|^ALN:|^LDIR:|^MUL:|^ITA:|^WGT:|^DEC:|^CND:|^BKG:|^CLR:|^BCLR:|^DCLR:|^FLAG:|^EMIT:^DONE:'\n"); + exit(EXIT_FAILURE); + } + + tri = trinfo_init(tri); /* If it loops the trinfo_clear at the end will reset tri to the proper state, do NOT call trinfo_init twice! */ + +#ifdef DBG_LOOP + int ldx; + for(ldx=0;ldx<5;ldx++){ + if(fpi)fclose(fpi); + fpi = fopen(infile,"r"); +#endif + tsp.string = NULL; + tsp.ori = 0.0; /* degrees */ + tsp.fs = 12.0; /* font size */ + tsp.x = 0.0; + tsp.y = 0.0; + tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/ + tsp.vadvance = 0.0; /* meaningful only when a complex contains two or more lines */ + tsp.taln = ALILEFT + ALIBASE; + tsp.ldir = LDIR_LR; + tsp.color.Red = tsp.decColor.Red = 0; /* RGBA Black */ + tsp.color.Green = tsp.decColor.Green = 0; /* RGBA Black */ + tsp.color.Blue = tsp.decColor.Blue = 0; /* RGBA Black */ + tsp.color.Reserved = tsp.decColor.Reserved = 0; /* unused */ + tsp.italics = 0; + tsp.weight = 80; + tsp.condensed = 100; + tsp.decoration = 0; /* none */ + tsp.spaces = 0; /* none */ + tsp.fi_idx = -1; /* set to an invalid */ + tsp.rt_tidx = -1; /* set to an invalid */ + tsp.xkern = tsp.ykern = 0.0; + /* no need to set rt_tidx */ + + + + if(!tri){ + fprintf(stderr,"Fatal error, could not initialize data structures\n"); + exit(EXIT_FAILURE); + } + (void) trinfo_load_ft_opts(tri, 1, + FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, + FT_KERNING_UNSCALED); + + fpo=fopen("dump.svg","wb"); + init_as_svg(tri); + + while(ok){ + lineno++; + if(!fgets(inbuf,MAXLINE,fpi))boom("Unexpected end of file - no DONE:",lineno); + inbuf[strlen(inbuf)-1]='\0'; /* step on the EOL character */ + op = parseit(inbuf,&data); + switch(op){ + case OPCOM: /* ignore comments*/ + break; + case OPFONT: + /* If the font name includes "Narrow" condensed may not have been set */ + if(0<= TR_findcasesub(data, "Narrow")){ + tsp.co=1; + } + else { + tsp.co=0; + } + fontspec = TR_construct_fontspec(&tsp, data); + if((tsp.fi_idx = ftinfo_load_fontname(tri->fti, fontspec)) < 0 )boom("Font load failed",lineno); + free(fontspec); + break; + case OPESC: + if(1 != sscanf(data,"%lf",&escapement))boom("Invalid ESC:",lineno); + break; + case OPORI: + if(1 != sscanf(data,"%lf",&tsp.ori))boom("Invalid ORI:",lineno); + break; + case OPFS: + if(1 != sscanf(data,"%lf",&tsp.fs) || tsp.fs <= 0.0)boom("Invalid FS:",lineno); + tsp.fs *= fact; + break; + case OPXY: + if(2 != sscanf(data,"%lf,%lf",&tsp.x,&tsp.y) )boom("Invalid XY:",lineno); + tsp.x *= fact; + tsp.y *= fact; + break; + case OPTEXT: + tsp.string = (uint8_t *) U_strdup(data); + /* FreeType parameters match inkscape*/ + status = trinfo_load_textrec(tri, &tsp, escapement,flags); + if(status==-1){ // change of escapement, emit what we have and reset + TR_layout_analyze(tri); + TR_layout_2_svg(tri); + flush_as_svg(tri, fpo); + tri = trinfo_clear(tri); + if(trinfo_load_textrec(tri, &tsp, escapement,flags)){ boom("Text load failed",lineno); } + } + else if(status){ boom("Text load failed",lineno); } + break; + case OPALN: + tsp.taln=0; + switch (*data++){ + case 'L': tsp.taln |= ALILEFT; break; + case 'C': tsp.taln |= ALICENTER; break; + case 'R': tsp.taln |= ALIRIGHT; break; + default: boom("Invalid ALN:",lineno); + } + switch (*data++){ + case 'T': tsp.taln |= ALITOP; break; + case 'L': tsp.taln |= ALIBASE; break; + case 'B': tsp.taln |= ALIBOT; break; + default: boom("Invalid ALN:",lineno); + } + break; + case OPLDIR: + tsp.ldir=0; + if(0==strcmp("LR",data)){ tsp.ldir=LDIR_LR; break;} + if(0==strcmp("RL",data)){ tsp.ldir=LDIR_RL; break;} + if(0==strcmp("TB",data)){ tsp.ldir=LDIR_TB; break;} + boom("Invalid LDIR:",lineno); + break; + case OPMUL: + if(1 != sscanf(data,"%lf",&fact) || fact <= 0.0)boom("Invalid MUL:",lineno); + (void) trinfo_load_qe(tri,fact); + break; + case OPITA: + if(1 != sscanf(data,"%d",&tsp.italics) || tsp.italics < 0 || tsp.italics>110)boom("Invalid ITA:",lineno); + break; + case OPWGT: + if(1 != sscanf(data,"%d",&tsp.weight) || tsp.weight < 0 || tsp.weight > 215)boom("Invalid WGT:",lineno); + break; + case OPDEC: + if(1 != sscanf(data,"%X",(unsigned int *) &tsp.decoration))boom("Invalid DEC:",lineno); + break; + case OPCND: + if(1 != sscanf(data,"%d",&tsp.condensed) || tsp.condensed < 50 || tsp.condensed > 200)boom("Invalid CND:",lineno); + break; + case OPBKG: + if(1 != sscanf(data,"%d",&bkmode) )boom("Invalid BKG:",lineno); + (void) trinfo_load_bk(tri,bkmode,bkcolor); + break; + case OPCLR: + if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid CLR:",lineno); + tsp.color.Red = (utmp32 >> 16) & 0xFF; + tsp.color.Green = (utmp32 >> 8) & 0xFF; + tsp.color.Blue = (utmp32 >> 0) & 0xFF; + tsp.color.Reserved = 0; + break; + case OPDCLR: + if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid DCLR:",lineno); + if(utmp32 >= 0x1000000){ + tsp.decColor.Red = tsp.decColor.Green = tsp.decColor.Blue = tsp.decColor.Reserved = 0; + tsp.decoration &= ~TXTDECOR_CLRSET; + } + else { + tsp.decColor.Red = (utmp32 >> 16) & 0xFF; + tsp.decColor.Green = (utmp32 >> 8) & 0xFF; + tsp.decColor.Blue = (utmp32 >> 0) & 0xFF; + tsp.decColor.Reserved = 0; + tsp.decoration |= TXTDECOR_CLRSET; + } + break; + case OPBCLR: + if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid BCLR:",lineno); + bkcolor.Red = (utmp32 >> 16) & 0xFF; + bkcolor.Green = (utmp32 >> 8) & 0xFF; + bkcolor.Blue = (utmp32 >> 0) & 0xFF; + bkcolor.Reserved = 0; + break; + case OPFLAGS: + if(1 != sscanf(data,"%d",&flags) )boom("Invalid FLAG:",lineno); + break; + case OPEMIT: + TR_layout_analyze(tri); + TR_layout_2_svg(tri); + flush_as_svg(tri, fpo); + tri = trinfo_clear(tri); + break; + case OPDONE: + TR_layout_analyze(tri); + TR_layout_2_svg(tri); + flush_as_svg(tri, fpo); + tri = trinfo_clear(tri); + ok = 0; + break; + case OPOOPS: + default: + boom("Input line cannot be parsed",lineno); + break; + } + + } + + if(fpo){ + fpo=close_as_svg(tri, fpo); + } + + +#ifdef DBG_LOOP + tri = trinfo_clear(tri); + ok = 1; + } +#endif /* DBG_LOOP */ + + fclose(fpi); + tri = trinfo_release(tri); + free(infile); + + exit(EXIT_SUCCESS); +} +#endif /* TEST */ + +#ifdef __cplusplus +} +#endif diff --git a/src/extension/internal/text_reassemble.h b/src/extension/internal/text_reassemble.h new file mode 100644 index 0000000..25b556a --- /dev/null +++ b/src/extension/internal/text_reassemble.h @@ -0,0 +1,397 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * text_reassemble.h from libTERE + *//* + * Authors: see below + * + * + * Copyright (C) 2017 Authors + * Released under GNU GPL v2.0+, read the file 'COPYING' for more information. + */ +/** + @file text_reassemble.h libTERE headers. + +See text_reassemble.c for notes + +File: text_reassemble.h +Version: 0.0.13 +Date: 06-FEB-2014 +Author: David Mathog, Biology Division, Caltech +email: mathog@caltech.edu +Copyright: 2014 David Mathog and California Institute of Technology (Caltech) +*/ + +#ifndef _TEXT_REASSEMBLE_ +#define _TEXT_REASSEMBLE_ + +#ifdef __cplusplus +extern "C" { +#endif + + +#include <stdlib.h> //NOLINT +#include <stdio.h> //NOLINT +#include <math.h> //NOLINT +#include <stdint.h> //NOLINT +#include <ctype.h> //NOLINT +#include <fontconfig/fontconfig.h> +#include <ft2build.h> +#include <iconv.h> +#include FT_FREETYPE_H +#include FT_GLYPH_H + +/** \cond */ +#define TEREMIN(A,B) (A < B ? A : B) +#define TEREMAX(A,B) (A > B ? A : B) + +#ifndef M_PI +# define M_PI 3.14159265358979323846 /* pi */ +#endif +#define ALLOCINFO_CHUNK 32 +#define ALLOCOUT_CHUNK 8192 +#define TRPRINT trinfo_append_out +/** \endcond */ + +/** \defgroup color background options + Text is underwritten with the background color not at all, + by reassembled line, or by full assembly . + @{ +*/ +#define BKCLR_NONE 0x00 /**< text is not underwritten with background color (default) */ +#define BKCLR_FRAG 0x01 /**< each fragment of text is underwritten with background color */ +#define BKCLR_LINE 0x02 /**< each line of text is underwritten with background color */ +#define BKCLR_ALL 0x03 /**< entire assembly is underwritten with background color */ +/** @} */ + +/** \defgroup decoration options + One of these values may be present in the decoration field. + Unused bits may be used by end user code. + These values are SVG specific. Other applications could use the text + decoration field for a different set of bits, so long as it provided its own + output function. + @{ +*/ +#define TXTDECOR_NONE 0x000 /**< text is not decorated (default) */ +#define TXTDECOR_UNDER 0x001 /**< underlined */ +#define TXTDECOR_OVER 0x002 /**< overlined */ +#define TXTDECOR_BLINK 0x004 /**< blinking text */ +#define TXTDECOR_STRIKE 0x008 /**< strike through */ +#define TXTDECOR_TMASK 0x00F /**< Mask for selecting bits above */ + +#define TXTDECOR_SOLID 0x000 /**< draw as single solid line */ +#define TXTDECOR_DOUBLE 0x010 /**< draw as double solid line */ +#define TXTDECOR_DOTTED 0x020 /**< draw as single dotted line */ +#define TXTDECOR_DASHED 0x040 /**< draw as single dashed line */ +#define TXTDECOR_WAVY 0x080 /**< draw as single wavy line */ +#define TXTDECOR_LMASK 0x0F0 /**< Mask for selecting these bits */ + +#define TXTDECOR_CLRSET 0x100 /**< decoration has its own color */ + +/** @} */ + + + + + +/** \defgroup text alignment types + Location of text's {X,Y} coordinate on bounding rectangle. + Values are compatible with Fontconfig. + @{ +*/ +#define ALILEFT 0x01 /**< text object horizontal alignment = left */ +#define ALICENTER 0x02 /**< text object horizontal alignment = center */ +#define ALIRIGHT 0x04 /**< text object horizontal alignment = right */ +#define ALIHORI 0x07 /**< text object horizontal alignment mask */ +#define ALITOP 0x08 /**< text object vertical alignment = top */ +#define ALIBASE 0x10 /**< text object vertical alignment = baseline */ +#define ALIBOT 0x20 /**< text object vertical alignment = bottom */ +#define ALIVERT 0x38 /**< text object vertical alignment mask */ +/** @} */ + +/** \defgroup language direction types + @{ +*/ +#define LDIR_LR 0x00 /**< left to right */ +#define LDIR_RL 0x01 /**< right to left */ +#define LDIR_TB 0x02 /**< top to bottom */ +/** @} */ + +/** \defgroup special processing flags + @{ +*/ +#define TR_EMFBOT 0x01 /**< use an approximation compatible with EMF file's "BOTTOM" text orientation, which is not the "bottom" for Freetype fonts */ +/** @} */ + +/** \enum tr_classes +classification of complexes + @{ +*/ +enum tr_classes { + TR_TEXT, /**< simple text object */ + TR_LINE, /**< linear assembly of TR_TEXTs */ + TR_PARA_UJ, /**< sequential assembly of TR_LINEs and TR_TEXTs into a paragraph - + unknown justification properties */ + TR_PARA_LJ, /**< ditto, left justified */ + TR_PARA_CJ, /**< ditto, center justified */ + TR_PARA_RJ /**< ditto, right justified */ + }; +/** @} */ + +/** + \brief alt font entries. +*/ +typedef struct { + uint32_t fi_idx; /**< index into FT_INFO fonts, for fonts added for missing glyphs */ + uint32_t weight; /**< integer weight for alt fonts, kept sorted into descending order */ +} ALT_SPECS; + +/** + \brief Information for a font instance. +*/ +typedef struct { + FcFontSet *fontset; /**< all matching fonts (for fallback on missing glyphs) */ + ALT_SPECS *alts; /**< index into FT_INFO fonts, for fonts added for missing glyphs */ + uint32_t space; /**< alts storage slots allocated */ + uint32_t used; /**< alts storage slots in use */ + FT_Face face; /**< font face structures (FT_FACE is a pointer!) */ + uint8_t *file; /**< pointer to font paths to files */ + uint8_t *fontspec; /**< pointer to a font specification (name:italics, etc.) */ + FcPattern *fpat; /**< current font, must hang onto this or faces operations break */ + double spcadv; /**< advance equal to a space, in points at font's face size */ + double fsize; /**< font's face size in points */ +} FNT_SPECS; + +/** + \brief Information for all font instances. +*/ +typedef struct { + FT_Library library; /**< Fontconfig handle */ + FNT_SPECS *fonts; /**< Array of fontinfo structures */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ +} FT_INFO; + +typedef struct { + uint8_t Red; //!< Red color (0-255) + uint8_t Green; //!< Green color (0-255) + uint8_t Blue; //!< Blue color (0-255) + uint8_t Reserved; //!< Not used +} TRCOLORREF; + +/** + \brief Information for a single text object +*/ +typedef struct { + uint8_t *string; /**< UTF-8 text */ + double ori; /**< Orientation, angle of characters with respect to baseline in degrees */ + double fs; /**< font size of text */ + double x; /**< x coordinate, relative to TR_INFO x,y, in points */ + double y; /**< y coordinate, relative to TR_INFO x,y, in points */ + double xkern; /**< x kern relative to preceding text chunk in complex (if any) */ + double ykern; /**< y kern relative to preceding text chunk in complex (if any) */ + double boff; /**< Y LL corner - boff finds baseline */ + double vadvance; /**< Line spacing typically 1.25 or 1.2, only set on the first text + element in a complex */ + TRCOLORREF color; /**< RGB */ + int taln; /**< text alignment with respect to x,y */ + int ldir; /**< language direction LDIR_* */ + int italics; /**< italics, as in FontConfig */ + int weight; /**< weight, as in FontConfig */ + int condensed; /**< condensed, as in FontConfig */ + int decoration; /**< text decorations, ignored during assembly, used during output */ + int spaces; /**< count of spaces converted from wide kerning (1 or 2) */ + TRCOLORREF decColor; /**< text decoration color, ignored during assembly, used during output */ + int co; /**< condensed override, if set Font name included narrow */ + int rt_tidx; /**< index of rectangle that contains it */ + int fi_idx; /**< index of the font it uses */ +} TCHUNK_SPECS; + +/** + \brief Information for all text objects. + Coordinates here are INTERNAL, after offset/rotate using values in TR_INFO. +*/ +typedef struct { + TCHUNK_SPECS *chunks; /**< text chunks */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ +} TP_INFO; + +/** + \brief Information for a single bounding rectangle. + Coordinates here are INTERNAL, after offset/rotate using values in TR_INFO. +*/ +typedef struct { + double xll; /**< x rectangle lower left corner */ + double yll; /**< y " */ + double xur; /**< x upper right corner */ + double yur; /**< y " */ + double xbearing; /**< x bearing of the leftmost character */ +} BRECT_SPECS; + +/** + \brief Information for all bounding rectangles. +*/ +typedef struct { + BRECT_SPECS *rects; /**< bounding rectangles */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ +} BR_INFO; + +/** + \brief List of all members of a single complex. +*/ +typedef struct { + int *members; /**< array of immediate children (for TR_PARA_* these are indices + for TR_TEXT or TR_LINE complexes also in cxi. For TR_TEXT + and TR_LINE these are indices to the actual text in tpi.) */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ +} CHILD_SPECS; + +/** + \brief Information for a single complex. +*/ +typedef struct { + int rt_cidx; /**< index of rectangle that contains all members */ + enum tr_classes type; /**< classification of the complex */ + CHILD_SPECS kids; /**< immediate child nodes of this complex, for type TR_TEXT the + idx refers to the tpi data. otherwise, cxi data */ +} CX_SPECS; + +/** + \brief Information for all complexes. +*/ +typedef struct { + CX_SPECS *cx; /**< complexes */ + uint32_t space; /**< storage slots allocated */ + uint32_t used; /**< storage slots in use */ + uint32_t phase1; /**< Number of complexes (lines + text fragments) entered in phase 1 */ + uint32_t lines; /**< Number of lines in phase 1 */ + uint32_t paras; /**< Number of complexes (paras) entered in phase 2 */ +} CX_INFO; + +/** + \brief Information for the entire text reassembly system. +*/ +typedef struct { + FT_INFO *fti; /**< Font info storage */ + TP_INFO *tpi; /**< Text Info/Position Info storage */ + BR_INFO *bri; /**< Bounding Rectangle Info storage */ + CX_INFO *cxi; /**< Complex Info storage */ + uint8_t *out; /**< buffer to hold formatted output */ + double qe; /**< quantization error in points. */ + double esc; /**< escapement angle in DEGREES */ + double x; /**< x coordinate of first text object, in points */ + double y; /**< y coordinate of first text object, in points */ + int dirty; /**< 1 if text records are loaded */ + int use_kern; /**< 1 if kerning is used, 0 if not */ + int load_flags; /**< FT_LOAD_NO_SCALE or FT_LOAD_TARGET_NORMAL */ + int kern_mode; /**< FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED */ + uint32_t outspace; /**< storage in output buffer allocated */ + uint32_t outused; /**< storage in output buffer in use */ + int usebk; /**< On output write the background color under the text */ + TRCOLORREF bkcolor; /**< RGB background color */ +} TR_INFO; + +/* padding added to rectangles before overlap test */ +/** + \brief Information for one padding record. (Padding is added to bounding rectangles before overlap tests.) +*/ +typedef struct { + double up; /**< to top */ + double down; /**< to bottom */ + double left; /**< to left */ + double right; /**< to right */ +} RT_PAD; + +/** \cond */ +/* + iconv() has a funny cast on some older systems, on most recent ones + it is just char **. This tries to work around the issue. If you build this + on another funky system this code may need to be modified, or define ICONV_CAST + on the compile line(but it may be tricky). +*/ +#ifdef SOL8 +#define ICONV_CAST (const char **) +#endif //SOL8 +#if !defined(ICONV_CAST) +#define ICONV_CAST (char **) +#endif //ICONV_CAST +/** \endcond */ + +/* Prototypes */ +int TR_findcasesub(const char *string, const char *sub); +char *TR_construct_fontspec(const TCHUNK_SPECS *tsp, const char *fontname); +char *TR_reconstruct_fontspec(const char *fontspec, const char *fontname); +int TR_find_alternate_font(FT_INFO *fti, FNT_SPECS **efsp, uint32_t wc); +int TR_getadvance(FT_INFO *fti, FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax); +int TR_getkern2(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int kern_mode); +int TR_kern_gap(FNT_SPECS *fsp, TCHUNK_SPECS *tsp, TCHUNK_SPECS *ptsp, int kern_mode); +void TR_rt_pad_set(RT_PAD *rt_pad, double up, double down, double left, double right); +double TR_baseline(TR_INFO *tri, int src, double *AscMax, double *DscMax); +int TR_check_set_vadvance(TR_INFO *tri, int src, int lines); +int TR_layout_analyze(TR_INFO *tri); +void TR_layout_2_svg(TR_INFO *tri); +int TR_weight_FC_to_SVG(int weight); + +FT_INFO *ftinfo_init(void); +int ftinfo_make_insertable(FT_INFO *fti); +int ftinfo_insert(FT_INFO *fti, FNT_SPECS *fsp); +FT_INFO *ftinfo_release(FT_INFO *fti); +FT_INFO *ftinfo_clear(FT_INFO *fti); +int ftinfo_find_loaded_by_spec(const FT_INFO *fti, const uint8_t *fname); +int ftinfo_find_loaded_by_src(const FT_INFO *fti, const uint8_t *filename); +int ftinfo_load_fontname(FT_INFO *fti, const char *fontspec); +void ftinfo_dump(const FT_INFO *fti); + +int fsp_alts_make_insertable(FNT_SPECS *fsp); +int fsp_alts_insert(FNT_SPECS *fsp, uint32_t fi_idx); +int fsp_alts_weight(FNT_SPECS *fsp, uint32_t a_idx); + +int csp_make_insertable(CHILD_SPECS *csp); +int csp_insert(CHILD_SPECS *csp, int src); +int csp_merge(CHILD_SPECS *dst, CHILD_SPECS *src); +void csp_release(CHILD_SPECS *csp); +void csp_clear(CHILD_SPECS *csp); + +CX_INFO *cxinfo_init(void); +int cxinfo_make_insertable(CX_INFO *cxi); +int cxinfo_insert(CX_INFO *cxi, int src, int src_rt_idx, enum tr_classes type); +int cxinfo_append(CX_INFO *cxi, int src, enum tr_classes type); +int cxinfo_merge(CX_INFO *cxi, int dst, int src, enum tr_classes type); +int cxinfo_trim(CX_INFO *cxi); +CX_INFO *cxinfo_release(CX_INFO *cxi); +void cxinfo_dump(const TR_INFO *tri); + +TP_INFO *tpinfo_init(void); +int tpinfo_make_insertable(TP_INFO *tpi); +int tpinfo_insert(TP_INFO *tpi, const TCHUNK_SPECS *tsp); +TP_INFO *tpinfo_release(TP_INFO *tpi); + +BR_INFO *brinfo_init(void); +int brinfo_make_insertable(BR_INFO *bri); +int brinfo_insert(BR_INFO *bri, const BRECT_SPECS *element); +int brinfo_merge(BR_INFO *bri, int dst, int src); +enum tr_classes + brinfo_pp_alignment(const BR_INFO *bri, int dst, int src, double slop, enum tr_classes type); +int brinfo_overlap(const BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src); +BR_INFO *brinfo_release(BR_INFO *bri); + +TR_INFO *trinfo_init(TR_INFO *tri); +TR_INFO *trinfo_release(TR_INFO *tri); +TR_INFO *trinfo_release_except_FC(TR_INFO *tri); +TR_INFO *trinfo_clear(TR_INFO *tri); +int trinfo_load_qe(TR_INFO *tri, double qe); +int trinfo_load_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor); +int trinfo_load_ft_opts(TR_INFO *tri, int use_kern, int load_flags, int kern_mode); +int trinfo_load_textrec(TR_INFO *tri, const TCHUNK_SPECS *tsp, double escapement, int flags); +int trinfo_check_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor); +int trinfo_append_out(TR_INFO *tri, const char *src); + +int is_mn_unicode(int test); + + +#ifdef __cplusplus +} +#endif +#endif /* _TEXT_REASSEMBLE_ */ diff --git a/src/extension/internal/vsd-input.cpp b/src/extension/internal/vsd-input.cpp new file mode 100644 index 0000000..c91f799 --- /dev/null +++ b/src/extension/internal/vsd-input.cpp @@ -0,0 +1,391 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file came from libwpg as a source, their utility wpg2svg + * specifically. It has been modified to work as an Inkscape extension. + * The Inkscape extension code is covered by this copyright, but the + * rest is covered by the one below. + * + * Authors: + * Fridrich Strba (fridrich.strba@bluewin.ch) + * + * Copyright (C) 2012 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 + +#include <cstdio> + +#include "vsd-input.h" + +#ifdef WITH_LIBVISIO + +#include <string> +#include <cstring> + +#include <libvisio/libvisio.h> + +// TODO: Drop this check when librevenge is widespread. +#if WITH_LIBVISIO01 + #include <librevenge-stream/librevenge-stream.h> + + using librevenge::RVNGString; + using librevenge::RVNGFileStream; + using librevenge::RVNGStringVector; +#else + #include <libwpd-stream/libwpd-stream.h> + + typedef WPXString RVNGString; + typedef WPXFileStream RVNGFileStream; + typedef libvisio::VSDStringVector RVNGStringVector; +#endif + +#include <gtkmm/spinbutton.h> + +#include "extension/system.h" +#include "extension/input.h" + +#include "document.h" +#include "inkscape.h" + +#include "ui/dialog-events.h" +#include <glibmm/i18n.h> + +#include "ui/view/svg-view-widget.h" + +#include "object/sp-root.h" + +#include "util/units.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + + +class VsdImportDialog : public Gtk::Dialog { +public: + VsdImportDialog(const std::vector<RVNGString> &vec); + ~VsdImportDialog() override; + + bool showDialog(); + unsigned getSelectedPage(); + void getImportSettings(Inkscape::XML::Node *prefs); + +private: + void _setPreviewPage(); + + // Signal handlers + void _onPageNumberChanged(); + void _onSpinButtonPress(GdkEventButton* button_event); + void _onSpinButtonRelease(GdkEventButton* button_event); + + class Gtk::VBox * vbox1; + class Inkscape::UI::View::SVGViewWidget * _previewArea; + class Gtk::Button * cancelbutton; + class Gtk::Button * okbutton; + + class Gtk::HBox * _page_selector_box; + class Gtk::Label * _labelSelect; + class Gtk::Label * _labelTotalPages; + class Gtk::SpinButton * _pageNumberSpin; + + const std::vector<RVNGString> &_vec; // Document to be imported + unsigned _current_page; // Current selected page + bool _spinning; // whether SpinButton is pressed (i.e. we're "spinning") +}; + +VsdImportDialog::VsdImportDialog(const std::vector<RVNGString> &vec) + : _previewArea(nullptr) + , _vec(vec) + , _current_page(1) + , _spinning(false) +{ + int num_pages = _vec.size(); + if ( num_pages <= 1 ) + return; + + + // Dialog settings + this->set_title(_("Page Selector")); + this->set_modal(true); + sp_transientize(GTK_WIDGET(this->gobj())); //Make transient + this->property_window_position().set_value(Gtk::WIN_POS_NONE); + this->set_resizable(true); + this->property_destroy_with_parent().set_value(false); + + // Preview area + vbox1 = Gtk::manage(new class Gtk::VBox()); + this->get_content_area()->pack_start(*vbox1); + + // CONTROLS + _page_selector_box = Gtk::manage(new Gtk::HBox()); + + // Labels + _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:"))); + _labelTotalPages = Gtk::manage(new class Gtk::Label()); + _labelSelect->set_line_wrap(false); + _labelSelect->set_use_markup(false); + _labelSelect->set_selectable(false); + _page_selector_box->pack_start(*_labelSelect, Gtk::PACK_SHRINK); + + // Adjustment + spinner + auto _pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _vec.size(), 1, 10, 0); + _pageNumberSpin = Gtk::manage(new Gtk::SpinButton(_pageNumberSpin_adj, 1, 0)); + _pageNumberSpin->set_can_focus(); + _pageNumberSpin->set_update_policy(Gtk::UPDATE_ALWAYS); + _pageNumberSpin->set_numeric(true); + _pageNumberSpin->set_wrap(false); + _page_selector_box->pack_start(*_pageNumberSpin, Gtk::PACK_SHRINK); + + _labelTotalPages->set_line_wrap(false); + _labelTotalPages->set_use_markup(false); + _labelTotalPages->set_selectable(false); + gchar *label_text = g_strdup_printf(_("out of %i"), num_pages); + _labelTotalPages->set_label(label_text); + g_free(label_text); + _page_selector_box->pack_start(*_labelTotalPages, Gtk::PACK_SHRINK); + + vbox1->pack_end(*_page_selector_box, Gtk::PACK_SHRINK); + + // Buttons + cancelbutton = Gtk::manage(new Gtk::Button(_("_Cancel"), true)); + okbutton = Gtk::manage(new Gtk::Button(_("_OK"), true)); + this->add_action_widget(*cancelbutton, Gtk::RESPONSE_CANCEL); + this->add_action_widget(*okbutton, Gtk::RESPONSE_OK); + + // Show all widgets in dialog + this->show_all(); + + // Connect signals + _pageNumberSpin->signal_value_changed().connect(sigc::mem_fun(*this, &VsdImportDialog::_onPageNumberChanged)); + _pageNumberSpin->signal_button_press_event().connect_notify(sigc::mem_fun(*this, &VsdImportDialog::_onSpinButtonPress)); + _pageNumberSpin->signal_button_release_event().connect_notify(sigc::mem_fun(*this, &VsdImportDialog::_onSpinButtonRelease)); + + _setPreviewPage(); +} + +VsdImportDialog::~VsdImportDialog() = default; + +bool VsdImportDialog::showDialog() +{ + show(); + gint b = run(); + hide(); + if (b == Gtk::RESPONSE_OK || b == Gtk::RESPONSE_ACCEPT) { + return TRUE; + } else { + return FALSE; + } +} + +unsigned VsdImportDialog::getSelectedPage() +{ + return _current_page; +} + +void VsdImportDialog::_onPageNumberChanged() +{ + unsigned page = static_cast<unsigned>(_pageNumberSpin->get_value_as_int()); + _current_page = CLAMP(page, 1U, _vec.size()); + _setPreviewPage(); +} + +void VsdImportDialog::_onSpinButtonPress(GdkEventButton* /*button_event*/) +{ + _spinning = true; +} + +void VsdImportDialog::_onSpinButtonRelease(GdkEventButton* /*button_event*/) +{ + _spinning = false; + _setPreviewPage(); +} + +/** + * \brief Renders the given page's thumbnail + */ +void VsdImportDialog::_setPreviewPage() +{ + if (_spinning) { + return; + } + + SPDocument *doc = SPDocument::createNewDocFromMem(_vec[_current_page-1].cstr(), strlen(_vec[_current_page-1].cstr()), false); + if(!doc) { + g_warning("VSD import: Could not create preview for page %d", _current_page); + gchar const *no_preview_template = R"A( + <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'> + <path d='M 82,10 18,74 m 0,-64 64,64' style='fill:none;stroke:#ff0000;stroke-width:2px;'/> + <rect x='18' y='10' width='64' height='64' style='fill:none;stroke:#000000;stroke-width:1.5px;'/> + <text x='50' y='92' style='font-size:10px;text-anchor:middle;font-family:sans-serif;'>%s</text> + </svg> + )A"; + gchar * no_preview = g_strdup_printf(no_preview_template, _("No preview")); + doc = SPDocument::createNewDocFromMem(no_preview, strlen(no_preview), false); + g_free(no_preview); + } + + if (!doc) { + std::cerr << "VsdImportDialog::_setPreviewPage: No document!" << std::endl; + return; + } + + if (_previewArea) { + _previewArea->setDocument(doc); + } else { + _previewArea = Gtk::manage(new Inkscape::UI::View::SVGViewWidget(doc)); + vbox1->pack_start(*_previewArea, Gtk::PACK_EXPAND_WIDGET, 0); + } + + _previewArea->setResize(400, 400); + _previewArea->show_all(); +} + +SPDocument *VsdInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri) +{ + #ifdef _WIN32 + // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows + // therefore attempt to convert uri to the system codepage + // even if this is not possible the alternate short (8.3) file name will be used if available + gchar * converted_uri = g_win32_locale_filename_from_utf8(uri); + RVNGFileStream input(converted_uri); + g_free(converted_uri); + #else + RVNGFileStream input(uri); + #endif + + if (!libvisio::VisioDocument::isSupported(&input)) { + return nullptr; + } + + RVNGStringVector output; +#if WITH_LIBVISIO01 + librevenge::RVNGSVGDrawingGenerator generator(output, "svg"); + + if (!libvisio::VisioDocument::parse(&input, &generator)) { +#else + if (!libvisio::VisioDocument::generateSVG(&input, output)) { +#endif + return nullptr; + } + + if (output.empty()) { + return nullptr; + } + + std::vector<RVNGString> tmpSVGOutput; + for (unsigned i=0; i<output.size(); ++i) { + RVNGString tmpString("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); + tmpString.append(output[i]); + tmpSVGOutput.push_back(tmpString); + } + + unsigned page_num = 1; + + // If only one page is present, import that one without bothering user + if (tmpSVGOutput.size() > 1) { + VsdImportDialog *dlg = nullptr; + if (INKSCAPE.use_gui()) { + dlg = new VsdImportDialog(tmpSVGOutput); + if (!dlg->showDialog()) { + delete dlg; + throw Input::open_cancelled(); + } + } + + // Get needed page + if (dlg) { + page_num = dlg->getSelectedPage(); + if (page_num < 1) + page_num = 1; + if (page_num > tmpSVGOutput.size()) + page_num = tmpSVGOutput.size(); + } + } + + SPDocument * doc = SPDocument::createNewDocFromMem(tmpSVGOutput[page_num-1].cstr(), strlen(tmpSVGOutput[page_num-1].cstr()), TRUE); + + // Set viewBox if it doesn't exist + if (doc && !doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit()))); + } + return doc; +} + +#include "clear-n_.h" + +void VsdInput::init() +{ + /* VSD */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("VSD Input") "</name>\n" + "<id>org.inkscape.input.vsd</id>\n" + "<input>\n" + "<extension>.vsd</extension>\n" + "<mimetype>application/vnd.visio</mimetype>\n" + "<filetypename>" N_("Microsoft Visio Diagram (*.vsd)") "</filetypename>\n" + "<filetypetooltip>" N_("File format used by Microsoft Visio 6 and later") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new VsdInput()); + + /* VDX */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("VDX Input") "</name>\n" + "<id>org.inkscape.input.vdx</id>\n" + "<input>\n" + "<extension>.vdx</extension>\n" + "<mimetype>application/vnd.visio</mimetype>\n" + "<filetypename>" N_("Microsoft Visio XML Diagram (*.vdx)") "</filetypename>\n" + "<filetypetooltip>" N_("File format used by Microsoft Visio 2010 and later") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new VsdInput()); + + /* VSDM */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("VSDM Input") "</name>\n" + "<id>org.inkscape.input.vsdm</id>\n" + "<input>\n" + "<extension>.vsdm</extension>\n" + "<mimetype>application/vnd.visio</mimetype>\n" + "<filetypename>" N_("Microsoft Visio 2013 drawing (*.vsdm)") "</filetypename>\n" + "<filetypetooltip>" N_("File format used by Microsoft Visio 2013 and later") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new VsdInput()); + + /* VSDX */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("VSDX Input") "</name>\n" + "<id>org.inkscape.input.vsdx</id>\n" + "<input>\n" + "<extension>.vsdx</extension>\n" + "<mimetype>application/vnd.visio</mimetype>\n" + "<filetypename>" N_("Microsoft Visio 2013 drawing (*.vsdx)") "</filetypename>\n" + "<filetypetooltip>" N_("File format used by Microsoft Visio 2013 and later") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new VsdInput()); + + return; + +} // init + +} } } /* namespace Inkscape, Extension, Implementation */ +#endif /* WITH_LIBVISIO */ + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/vsd-input.h b/src/extension/internal/vsd-input.h new file mode 100644 index 0000000..f30c905 --- /dev/null +++ b/src/extension/internal/vsd-input.h @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This code abstracts the libwpg interfaces into the Inkscape + * input extension interface. + * + * Authors: + * Fridrich Strba (fridrich.strba@bluewin.ch) + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __EXTENSION_INTERNAL_VSDOUTPUT_H__ +#define __EXTENSION_INTERNAL_VSDOUTPUT_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#ifdef WITH_LIBVISIO + +#include <gtkmm/dialog.h> + +#include "../implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class VsdInput : public Inkscape::Extension::Implementation::Implementation { + VsdInput () = default;; +public: + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ) override; + static void init( ); + +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + +#endif /* WITH_LIBVISIO */ +#endif /* __EXTENSION_INTERNAL_VSDOUTPUT_H__ */ + + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/wmf-inout.cpp b/src/extension/internal/wmf-inout.cpp new file mode 100644 index 0000000..e5e2279 --- /dev/null +++ b/src/extension/internal/wmf-inout.cpp @@ -0,0 +1,3263 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Windows-only Enhanced Metafile input and output. + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * David Mathog + * Abhishek Sharma + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + * References: + * - How to Create & Play Enhanced Metafiles in Win32 + * http://support.microsoft.com/kb/q145999/ + * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles + * http://support.microsoft.com/kb/q66949/ + * - Metafile Functions + * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp + * - Metafile Structures + * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp + */ + +//#include <png.h> //This must precede text_reassemble.h or it blows up in pngconf.h when compiling +#include <cstdio> +#include <cstdlib> +#include <cstdint> +#include <3rdparty/libuemf/symbol_convert.h> + +#include "document.h" +#include "object/sp-root.h" // even though it is included indirectly by wmf-inout.h +#include "object/sp-path.h" +#include "print.h" +#include "extension/system.h" +#include "extension/print.h" +#include "extension/db.h" +#include "extension/input.h" +#include "extension/output.h" +#include "display/drawing.h" +#include "display/drawing-item.h" +#include "clear-n_.h" +#include "svg/svg.h" +#include "util/units.h" // even though it is included indirectly by wmf-inout.h +#include "inkscape.h" // even though it is included indirectly by wmf-inout.h + + +#include "wmf-inout.h" +#include "wmf-print.h" + +#define PRINT_WMF "org.inkscape.print.wmf" + +#ifndef U_PS_JOIN_MASK +#define U_PS_JOIN_MASK (U_PS_JOIN_BEVEL|U_PS_JOIN_MITER|U_PS_JOIN_ROUND) +#endif + +namespace Inkscape { +namespace Extension { +namespace Internal { + + +static bool clipset = false; +static uint32_t BLTmode=0; + +Wmf::Wmf () // The null constructor +{ + return; +} + + +Wmf::~Wmf () //The destructor +{ + return; +} + + +bool +Wmf::check (Inkscape::Extension::Extension * /*module*/) +{ + if (nullptr == Inkscape::Extension::db.get(PRINT_WMF)) + return FALSE; + return TRUE; +} + + +void +Wmf::print_document_to_file(SPDocument *doc, const gchar *filename) +{ + Inkscape::Extension::Print *mod; + SPPrintContext context; + const gchar *oldconst; + gchar *oldoutput; + + doc->ensureUpToDate(); + + mod = Inkscape::Extension::get_print(PRINT_WMF); + oldconst = mod->get_param_string("destination"); + oldoutput = g_strdup(oldconst); + mod->set_param_string("destination", filename); + +/* Start */ + context.module = mod; + /* fixme: This has to go into module constructor somehow */ + /* Create new arena */ + mod->base = doc->getRoot(); + Inkscape::Drawing drawing; + mod->dkey = SPItem::display_key_new(1); + mod->root = mod->base->invoke_show(drawing, mod->dkey, SP_ITEM_SHOW_DISPLAY); + drawing.setRoot(mod->root); + /* Print document */ + if (mod->begin(doc)) { + g_free(oldoutput); + mod->base->invoke_hide(mod->dkey); + mod->base = nullptr; + mod->root = nullptr; + throw Inkscape::Extension::Output::save_failed(); + } + mod->base->invoke_print(&context); + mod->finish(); + /* Release arena */ + mod->base->invoke_hide(mod->dkey); + mod->base = nullptr; + mod->root = nullptr; // deleted by invoke_hide +/* end */ + + mod->set_param_string("destination", oldoutput); + g_free(oldoutput); + + return; +} + + +void +Wmf::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) +{ + Inkscape::Extension::Extension * ext; + + ext = Inkscape::Extension::db.get(PRINT_WMF); + if (ext == nullptr) + return; + + bool new_val = mod->get_param_bool("textToPath"); + bool new_FixPPTCharPos = mod->get_param_bool("FixPPTCharPos"); // character position bug + // reserve FixPPT2 for opacity bug. Currently WMF does not export opacity values + bool new_FixPPTDashLine = mod->get_param_bool("FixPPTDashLine"); // dashed line bug + bool new_FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys"); // gradient bug + bool new_FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch"); // force all patterns as standard WMF hatch + + TableGen( //possibly regenerate the unicode-convert tables + mod->get_param_bool("TnrToSymbol"), + mod->get_param_bool("TnrToWingdings"), + mod->get_param_bool("TnrToZapfDingbats"), + mod->get_param_bool("UsePUA") + ); + + ext->set_param_bool("FixPPTCharPos",new_FixPPTCharPos); // Remember to add any new ones to PrintWmf::init or a mysterious failure will result! + ext->set_param_bool("FixPPTDashLine",new_FixPPTDashLine); + ext->set_param_bool("FixPPTGrad2Polys",new_FixPPTGrad2Polys); + ext->set_param_bool("FixPPTPatternAsHatch",new_FixPPTPatternAsHatch); + ext->set_param_bool("textToPath", new_val); + + // ensure usage of dot as decimal separator in scanf/printf functions (independently of current locale) + char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr)); + setlocale(LC_NUMERIC, "C"); + + print_document_to_file(doc, filename); + + // restore decimal separator used in scanf/printf functions to initial value + setlocale(LC_NUMERIC, oldlocale); + g_free(oldlocale); + + return; +} + + +/* WMF has no worldTransform, so this always returns 1.0. Retain it to keep WMF and WMF in sync as much as possible.*/ +double Wmf::current_scale(PWMF_CALLBACK_DATA /*d*/){ + return 1.0; +} + +/* WMF has no worldTransform, so this always returns an Identity rotation matrix, but the offsets may have values.*/ +std::string Wmf::current_matrix(PWMF_CALLBACK_DATA d, double x, double y, int useoffset){ + SVGOStringStream cxform; + double scale = current_scale(d); + cxform << "\"matrix("; + cxform << 1.0/scale; cxform << ","; + cxform << 0.0; cxform << ","; + cxform << 0.0; cxform << ","; + cxform << 1.0/scale; cxform << ","; + if(useoffset){ cxform << x; cxform << ","; cxform << y; } + else { cxform << "0,0"; } + cxform << ")\""; + return(cxform.str()); +} + +/* WMF has no worldTransform, so this always returns 0. Retain it to keep WMF and WMF in sync as much as possible.*/ +double Wmf::current_rotation(PWMF_CALLBACK_DATA /*d*/){ + return 0.0; +} + +/* Add another 100 blank slots to the hatches array. +*/ +void Wmf::enlarge_hatches(PWMF_CALLBACK_DATA d){ + d->hatches.size += 100; + d->hatches.strings = (char **) realloc(d->hatches.strings,d->hatches.size * sizeof(char *)); +} + +/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1) +*/ +int Wmf::in_hatches(PWMF_CALLBACK_DATA d, char *test){ + int i; + for(i=0; i<d->hatches.count; i++){ + if(strcmp(test,d->hatches.strings[i])==0)return(i+1); + } + return(0); +} + +class TagEmitter +{ +public: + TagEmitter(Glib::ustring & p_defs, char * p_tmpcolor, char * p_hpathname): + defs(p_defs), tmpcolor(p_tmpcolor), hpathname(p_hpathname) + { + }; + void append(const char *prefix, const char * inner) + { + defs += " "; + defs += prefix; + defs += hpathname; + defs += inner; + defs += tmpcolor; + defs += "\" />\n"; + } + +protected: + Glib::ustring & defs; + char * tmpcolor, * hpathname; +}; + +/* (Conditionally) add a hatch. If a matching hatch already exists nothing happens. If one + does not exist it is added to the hatches list and also entered into <defs>. + This is also used to add the path part of the hatches, which they reference with a xlink:href +*/ +uint32_t Wmf::add_hatch(PWMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor){ + char hatchname[64]; // big enough + char hpathname[64]; // big enough + char hbkname[64]; // big enough + char tmpcolor[8]; + char bkcolor[8]; + uint32_t idx; + + switch(hatchType){ + case U_HS_SOLIDTEXTCLR: + case U_HS_DITHEREDTEXTCLR: + sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].textColor)); + break; + case U_HS_SOLIDBKCLR: + case U_HS_DITHEREDBKCLR: + sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor)); + break; + default: + sprintf(tmpcolor,"%6.6X",sethexcolor(hatchColor)); + break; + } + auto & defs = d->defs; + TagEmitter a(defs, tmpcolor, hpathname); + + /* For both bkMode types set the PATH + FOREGROUND COLOR for the indicated standard hatch. + This will be used late to compose, or recompose the transparent or opaque final hatch.*/ + + std::string refpath; // used to reference later the path pieces which are about to be created + sprintf(hpathname,"WMFhpath%d_%s",hatchType,tmpcolor); + idx = in_hatches(d,hpathname); + if(!idx){ // add path/color if not already present + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hpathname); + + defs += "\n"; + switch(hatchType){ + case U_HS_HORIZONTAL: + a.append("<path id=\"", + "\" d=\"M 0 0 6 0\" style=\"fill:none;stroke:#"); + break; + case U_HS_VERTICAL: + a.append("<path id=\"", + "\" d=\"M 0 0 0 6\" style=\"fill:none;stroke:#"); + break; + case U_HS_FDIAGONAL: + a.append("<line id=\"sub", + "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"); + break; + case U_HS_BDIAGONAL: + a.append("<line id=\"sub", + "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"); + break; + case U_HS_CROSS: + a.append("<path id=\"", + "\" d=\"M 0 0 6 0 M 0 0 0 6\" style=\"fill:none;stroke:#"); + break; + case U_HS_DIAGCROSS: + a.append("<line id=\"subfd", + "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"); + a.append("<line id=\"subbd", + "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"); + break; + case U_HS_SOLIDCLR: + case U_HS_DITHEREDCLR: + case U_HS_SOLIDTEXTCLR: + case U_HS_DITHEREDTEXTCLR: + case U_HS_SOLIDBKCLR: + case U_HS_DITHEREDBKCLR: + default: + a.append("<path id=\"", + "\" d=\"M 0 0 6 0 6 6 0 6 z\" style=\"stroke:none;fill:#"); + break; + } + } + + // References to paths possibly just created above. These will be used in the actual patterns. + switch(hatchType){ + case U_HS_HORIZONTAL: + case U_HS_VERTICAL: + case U_HS_CROSS: + case U_HS_SOLIDCLR: + case U_HS_DITHEREDCLR: + case U_HS_SOLIDTEXTCLR: + case U_HS_DITHEREDTEXTCLR: + case U_HS_SOLIDBKCLR: + case U_HS_DITHEREDBKCLR: + default: + refpath += " <use xlink:href=\"#"; + refpath += hpathname; + refpath += "\" />\n"; + break; + case U_HS_FDIAGONAL: + case U_HS_BDIAGONAL: + refpath += " <use xlink:href=\"#sub"; + refpath += hpathname; + refpath += "\" />\n"; + refpath += " <use xlink:href=\"#sub"; + refpath += hpathname; + refpath += "\" transform=\"translate(6,0)\" />\n"; + refpath += " <use xlink:href=\"#sub"; + refpath += hpathname; + refpath += "\" transform=\"translate(-6,0)\" />\n"; + break; + case U_HS_DIAGCROSS: + refpath += " <use xlink:href=\"#subfd"; + refpath += hpathname; + refpath += "\" />\n"; + refpath += " <use xlink:href=\"#subfd"; + refpath += hpathname; + refpath += "\" transform=\"translate(6,0)\"/>\n"; + refpath += " <use xlink:href=\"#subfd"; + refpath += hpathname; + refpath += "\" transform=\"translate(-6,0)\"/>\n"; + refpath += " <use xlink:href=\"#subbd"; + refpath += hpathname; + refpath += "\" />\n"; + refpath += " <use xlink:href=\"#subbd"; + refpath += hpathname; + refpath += "\" transform=\"translate(6,0)\"/>\n"; + refpath += " <use xlink:href=\"#subbd"; + refpath += hpathname; + refpath += "\" transform=\"translate(-6,0)\"/>\n"; + break; + } + + if(d->dc[d->level].bkMode == U_TRANSPARENT || hatchType >= U_HS_SOLIDCLR){ + sprintf(hatchname,"WMFhatch%d_%s",hatchType,tmpcolor); + sprintf(hpathname,"WMFhpath%d_%s",hatchType,tmpcolor); + idx = in_hatches(d,hatchname); + if(!idx){ // add it if not already present + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hatchname); + defs += "\n"; + defs += " <pattern id=\""; + defs += hatchname; + defs += "\" xlink:href=\"#WMFhbasepattern\">\n"; + defs += refpath; + defs += " </pattern>\n"; + idx = d->hatches.count; + } + } + else { // bkMode==U_OPAQUE + /* Set up an object in the defs for this background, if there is not one already there */ + sprintf(bkcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor)); + sprintf(hbkname,"WMFhbkclr_%s",bkcolor); + idx = in_hatches(d,hbkname); + if(!idx){ // add path/color if not already present. Hatchtype is not needed in the name. + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hbkname); + + defs += "\n"; + defs += " <rect id=\""; + defs += hbkname; + defs += "\" x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"#"; + defs += bkcolor; + defs += "\" />\n"; + } + + // this is the pattern, its name will show up in Inkscape's pattern selector + sprintf(hatchname,"WMFhatch%d_%s_%s",hatchType,tmpcolor,bkcolor); + idx = in_hatches(d,hatchname); + if(!idx){ // add it if not already present + if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); } + d->hatches.strings[d->hatches.count++]=strdup(hatchname); + defs += "\n"; + defs += " <pattern id=\""; + defs += hatchname; + defs += "\" xlink:href=\"#WMFhbasepattern\">\n"; + defs += " <use xlink:href=\"#"; + defs += hbkname; + defs += "\" />\n"; + defs += refpath; + defs += " </pattern>\n"; + idx = d->hatches.count; + } + } + return(idx-1); +} + +/* Add another 100 blank slots to the images array. +*/ +void Wmf::enlarge_images(PWMF_CALLBACK_DATA d){ + d->images.size += 100; + d->images.strings = (char **) realloc(d->images.strings,d->images.size * sizeof(char *)); +} + +/* See if the image string is already in the list. If it is return its position (1->n, not 1-n-1) +*/ +int Wmf::in_images(PWMF_CALLBACK_DATA d, char *test){ + int i; + for(i=0; i<d->images.count; i++){ + if(strcmp(test,d->images.strings[i])==0)return(i+1); + } + return(0); +} + +/* (Conditionally) add an image from a DIB. If a matching image already exists nothing happens. If one + does not exist it is added to the images list and also entered into <defs>. + +*/ +uint32_t Wmf::add_dib_image(PWMF_CALLBACK_DATA d, const char *dib, uint32_t iUsage){ + + uint32_t idx; + char imagename[64]; // big enough + char xywh[64]; // big enough + int dibparams = U_BI_UNKNOWN; // type of image not yet determined + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = nullptr; + + char *rgba_px = nullptr; // RGBA pixels + const char *px = nullptr; // DIB pixels + const U_RGBQUAD *ct = nullptr; // DIB color table + uint32_t numCt; + int32_t width, height, colortype, invert; // if needed these values will be set by wget_DIB_params + if(iUsage == U_DIB_RGB_COLORS){ + // next call returns pointers and values, but allocates no memory + dibparams = wget_DIB_params(dib, &px, &ct, &numCt, &width, &height, &colortype, &invert); + if(dibparams == U_BI_RGB){ + if(!DIB_to_RGBA( + px, // DIB pixel array + ct, // DIB color table + numCt, // DIB color table number of entries + &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free. + width, // Width of pixel array in record + height, // Height of pixel array in record + colortype, // DIB BitCount Enumeration + numCt, // Color table used if not 0 + invert // If DIB rows are in opposite order from RGBA rows + )){ + toPNG( // Get the image from the RGBA px into mempng + &mempng, + width, height, // of the SRC bitmap + rgba_px + ); + free(rgba_px); + } + } + } + + gchar *base64String=nullptr; + if(dibparams == U_BI_JPEG || dibparams==U_BI_PNG){ // image was binary png or jpg in source file + base64String = g_base64_encode((guchar*) px, numCt ); + } + else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine + base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size ); + free(mempng.buffer); + } + else { // failed conversion, insert the common bad image picture + width = 3; + height = 4; + base64String = bad_image_png(); + } + idx = in_images(d, (char *) base64String); + auto & defs = d->defs; + if(!idx){ // add it if not already present - we looked at the actual data for comparison + if(d->images.count == d->images.size){ enlarge_images(d); } + idx = d->images.count; + d->images.strings[d->images.count++]=strdup(base64String); + + sprintf(imagename,"WMFimage%d",idx++); + sprintf(xywh," x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" ",width,height); // reuse this buffer + + defs += "\n"; + defs += " <image id=\""; + defs += imagename; + defs += "\"\n "; + defs += xywh; + defs += "\n"; + if(dibparams == U_BI_JPEG){ defs += " xlink:href=\"data:image/jpeg;base64,"; } + else { defs += " xlink:href=\"data:image/png;base64,"; } + defs += base64String; + defs += "\"\n"; + defs += " preserveAspectRatio=\"none\"\n"; + defs += " />\n"; + + + defs += "\n"; + defs += " <pattern id=\""; + defs += imagename; + defs += "_ref\"\n "; + defs += xywh; + defs += "\n patternUnits=\"userSpaceOnUse\""; + defs += " >\n"; + defs += " <use id=\""; + defs += imagename; + defs += "_ign\" "; + defs += " xlink:href=\"#"; + defs += imagename; + defs += "\" />\n"; + defs += " "; + defs += " </pattern>\n"; + } + g_free(base64String); //wait until this point to free because it might be a duplicate image + return(idx-1); +} + +/* (Conditionally) add an image from a Bitmap16. If a matching image already exists nothing happens. If one + does not exist it is added to the images list and also entered into <defs>. + +*/ +uint32_t Wmf::add_bm16_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px){ + + uint32_t idx; + char imagename[64]; // big enough + char xywh[64]; // big enough + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = nullptr; + + char *rgba_px = nullptr; // RGBA pixels + const U_RGBQUAD *ct = nullptr; // color table, always NULL here + int32_t width, height, colortype, numCt, invert; + numCt = 0; + width = Bm16.Width; // bitmap width in pixels. + height = Bm16.Height; // bitmap height in scan lines. + colortype = Bm16.BitsPixel; // seems to be BitCount Enumeration + invert = 0; + if(colortype < 16)return(U_WMR_INVALID); // these would need a colortable if they were a dib, no idea what bm16 is supposed to do instead. + + if(!DIB_to_RGBA(// This is not really a dib, but close enough so that it still works. + px, // DIB pixel array + ct, // DIB color table (always NULL here) + numCt, // DIB color table number of entries (always 0) + &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free. + width, // Width of pixel array + height, // Height of pixel array + colortype, // DIB BitCount Enumeration + numCt, // Color table used if not 0 + invert // If DIB rows are in opposite order from RGBA rows + )){ + toPNG( // Get the image from the RGBA px into mempng + &mempng, + width, height, // of the SRC bitmap + rgba_px + ); + free(rgba_px); + } + + gchar *base64String=nullptr; + if(mempng.buffer){ // image was Bm16 in source file, converted to png in this routine + base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size ); + free(mempng.buffer); + } + else { // failed conversion, insert the common bad image picture + width = 3; + height = 4; + base64String = bad_image_png(); + } + + idx = in_images(d, (char *) base64String); + auto & defs = d->defs; + if(!idx){ // add it if not already present - we looked at the actual data for comparison + if(d->images.count == d->images.size){ enlarge_images(d); } + idx = d->images.count; + d->images.strings[d->images.count++]=g_strdup(base64String); + + sprintf(imagename,"WMFimage%d",idx++); + sprintf(xywh," x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" ",width,height); // reuse this buffer + + defs += "\n"; + defs += " <image id=\""; + defs += imagename; + defs += "\"\n "; + defs += xywh; + defs += "\n"; + defs += " xlink:href=\"data:image/png;base64,"; + defs += base64String; + defs += "\"\n"; + defs += " preserveAspectRatio=\"none\"\n"; + defs += " />\n"; + + + defs += "\n"; + defs += " <pattern id=\""; + defs += imagename; + defs += "_ref\"\n "; + defs += xywh; + defs += "\n patternUnits=\"userSpaceOnUse\""; + defs += " >\n"; + defs += " <use id=\""; + defs += imagename; + defs += "_ign\" "; + defs += " xlink:href=\"#"; + defs += imagename; + defs += "\" />\n"; + defs += " </pattern>\n"; + } + g_free(base64String); //wait until this point to free because it might be a duplicate image + return(idx-1); +} + +/* Add another 100 blank slots to the clips array. +*/ +void Wmf::enlarge_clips(PWMF_CALLBACK_DATA d){ + d->clips.size += 100; + d->clips.strings = (char **) realloc(d->clips.strings,d->clips.size * sizeof(char *)); +} + +/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1) +*/ +int Wmf::in_clips(PWMF_CALLBACK_DATA d, const char *test){ + int i; + for(i=0; i<d->clips.count; i++){ + if(strcmp(test,d->clips.strings[i])==0)return(i+1); + } + return(0); +} + +/* (Conditionally) add a clip. + If a matching clip already exists nothing happens + If one does exist it is added to the clips list, entered into <defs>. +*/ +void Wmf::add_clips(PWMF_CALLBACK_DATA d, const char *clippath, unsigned int logic){ + int op = combine_ops_to_livarot(logic); + Geom::PathVector combined_vect; + char *combined = nullptr; + if (op >= 0 && d->dc[d->level].clip_id) { + unsigned int real_idx = d->dc[d->level].clip_id - 1; + Geom::PathVector old_vect = sp_svg_read_pathv(d->clips.strings[real_idx]); + Geom::PathVector new_vect = sp_svg_read_pathv(clippath); + combined_vect = sp_pathvector_boolop(new_vect, old_vect, (bool_op) op , (FillRule) fill_oddEven, (FillRule) fill_oddEven); + combined = sp_svg_write_path(combined_vect); + } + else { + combined = strdup(clippath); // COPY operation, erases everything and starts a new one + } + + uint32_t idx = in_clips(d, combined); + if(!idx){ // add clip if not already present + if(d->clips.count == d->clips.size){ enlarge_clips(d); } + d->clips.strings[d->clips.count++]=strdup(combined); + d->dc[d->level].clip_id = d->clips.count; // one more than the slot where it is actually stored + SVGOStringStream tmp_clippath; + tmp_clippath << "\n<clipPath"; + tmp_clippath << "\n\tclipPathUnits=\"userSpaceOnUse\" "; + tmp_clippath << "\n\tid=\"clipWmfPath" << d->dc[d->level].clip_id << "\""; + tmp_clippath << " >"; + tmp_clippath << "\n\t<path d=\""; + tmp_clippath << combined; + tmp_clippath << "\""; + tmp_clippath << "\n\t/>"; + tmp_clippath << "\n</clipPath>"; + d->outdef += tmp_clippath.str().c_str(); + } + else { + d->dc[d->level].clip_id = idx; + } + free(combined); +} + + + +void +Wmf::output_style(PWMF_CALLBACK_DATA d) +{ +// SVGOStringStream tmp_id; + SVGOStringStream tmp_style; + char tmp[1024] = {0}; + + float fill_rgb[3]; + d->dc[d->level].style.fill.value.color.get_rgb_floatv(fill_rgb); + float stroke_rgb[3]; + d->dc[d->level].style.stroke.value.color.get_rgb_floatv(stroke_rgb); + + // for U_WMR_BITBLT with no image, try to approximate some of these operations/ + // Assume src color is "white" + if(d->dwRop3){ + switch(d->dwRop3){ + case U_PATINVERT: // treat all of these as black + case U_SRCINVERT: + case U_DSTINVERT: + case U_BLACKNESS: + case U_SRCERASE: + case U_NOTSRCCOPY: + fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=0.0; + break; + case U_SRCCOPY: // treat all of these as white + case U_NOTSRCERASE: + case U_PATCOPY: + case U_WHITENESS: + fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=1.0; + break; + case U_SRCPAINT: // use the existing color + case U_SRCAND: + case U_MERGECOPY: + case U_MERGEPAINT: + case U_PATPAINT: + default: + break; + } + d->dwRop3 = 0; // might as well reset it here, it must be set for each BITBLT + } + + // Implement some of these, the ones where the original screen color does not matter. + // The options that merge screen and pen colors cannot be done correctly because we + // have no way of knowing what color is already on the screen. For those just pass the + // pen color through. + switch(d->dwRop2){ + case U_R2_BLACK: + fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 0.0; + stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 0.0; + break; + case U_R2_NOTMERGEPEN: + case U_R2_MASKNOTPEN: + break; + case U_R2_NOTCOPYPEN: + fill_rgb[0] = 1.0 - fill_rgb[0]; + fill_rgb[1] = 1.0 - fill_rgb[1]; + fill_rgb[2] = 1.0 - fill_rgb[2]; + stroke_rgb[0] = 1.0 - stroke_rgb[0]; + stroke_rgb[1] = 1.0 - stroke_rgb[1]; + stroke_rgb[2] = 1.0 - stroke_rgb[2]; + break; + case U_R2_MASKPENNOT: + case U_R2_NOT: + case U_R2_XORPEN: + case U_R2_NOTMASKPEN: + case U_R2_NOTXORPEN: + case U_R2_NOP: + case U_R2_MERGENOTPEN: + case U_R2_COPYPEN: + case U_R2_MASKPEN: + case U_R2_MERGEPENNOT: + case U_R2_MERGEPEN: + break; + case U_R2_WHITE: + fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 1.0; + stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 1.0; + break; + default: + break; + } + + +// tmp_id << "\n\tid=\"" << (d->id++) << "\""; +// d->outsvg += tmp_id.str().c_str(); + d->outsvg += "\n\tstyle=\""; + if (!d->dc[d->level].fill_set || ( d->mask & U_DRAW_NOFILL)) { // nofill are lines and arcs + tmp_style << "fill:none;"; + } else { + switch(d->dc[d->level].fill_mode){ + // both of these use the url(#) method + case DRAW_PATTERN: + snprintf(tmp, 1023, "fill:url(#%s); ",d->hatches.strings[d->dc[d->level].fill_idx]); + tmp_style << tmp; + break; + case DRAW_IMAGE: + snprintf(tmp, 1023, "fill:url(#WMFimage%d_ref); ",d->dc[d->level].fill_idx); + tmp_style << tmp; + break; + case DRAW_PAINT: + default: // <-- this should never happen, but just in case... + snprintf( + tmp, 1023, + "fill:#%02x%02x%02x;", + SP_COLOR_F_TO_U(fill_rgb[0]), + SP_COLOR_F_TO_U(fill_rgb[1]), + SP_COLOR_F_TO_U(fill_rgb[2]) + ); + tmp_style << tmp; + break; + } + snprintf( + tmp, 1023, + "fill-rule:%s;", + (d->dc[d->level].style.fill_rule.value == SP_WIND_RULE_NONZERO ? "evenodd" : "nonzero") + ); + tmp_style << tmp; + tmp_style << "fill-opacity:1;"; + + // if the stroke is the same as the fill, and the right size not to change the end size of the object, do not do it separately + if( + (d->dc[d->level].fill_set ) && + (d->dc[d->level].stroke_set ) && + (d->dc[d->level].style.stroke_width.value == 1 ) && + (d->dc[d->level].fill_mode == d->dc[d->level].stroke_mode) && + ( + (d->dc[d->level].fill_mode != DRAW_PAINT) || + ( + (fill_rgb[0]==stroke_rgb[0]) && + (fill_rgb[1]==stroke_rgb[1]) && + (fill_rgb[2]==stroke_rgb[2]) + ) + ) + ){ + d->dc[d->level].stroke_set = false; + } + } + + if (!d->dc[d->level].stroke_set) { + tmp_style << "stroke:none;"; + } else { + switch(d->dc[d->level].stroke_mode){ + // both of these use the url(#) method + case DRAW_PATTERN: + snprintf(tmp, 1023, "stroke:url(#%s); ",d->hatches.strings[d->dc[d->level].stroke_idx]); + tmp_style << tmp; + break; + case DRAW_IMAGE: + snprintf(tmp, 1023, "stroke:url(#WMFimage%d_ref); ",d->dc[d->level].stroke_idx); + tmp_style << tmp; + break; + case DRAW_PAINT: + default: // <-- this should never happen, but just in case... + snprintf( + tmp, 1023, + "stroke:#%02x%02x%02x;", + SP_COLOR_F_TO_U(stroke_rgb[0]), + SP_COLOR_F_TO_U(stroke_rgb[1]), + SP_COLOR_F_TO_U(stroke_rgb[2]) + ); + tmp_style << tmp; + break; + } + if(d->dc[d->level].style.stroke_width.value){ + tmp_style << "stroke-width:" << + MAX( 0.001, d->dc[d->level].style.stroke_width.value ) << "px;"; + } + else { // In a WMF a 0 width pixel means "1 pixel" + tmp_style << "stroke-width:" << pix_to_abs_size( d, 1 ) << "px;"; + } + + tmp_style << "stroke-linecap:" << + ( + d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_BUTT ? "butt" : + d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_ROUND ? "round" : + d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_SQUARE ? "square" : + "unknown" + ) << ";"; + + tmp_style << "stroke-linejoin:" << + ( + d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_MITER ? "miter" : + d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_ROUND ? "round" : + d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_BEVEL ? "bevel" : + "unknown" + ) << ";"; + + // Set miter limit if known, even if it is not needed immediately (not miter) + tmp_style << "stroke-miterlimit:" << + MAX( 2.0, d->dc[d->level].style.stroke_miterlimit.value ) << ";"; + + if (d->dc[d->level].style.stroke_dasharray.set && + !d->dc[d->level].style.stroke_dasharray.values.empty()) + { + tmp_style << "stroke-dasharray:"; + for (unsigned i=0; i<d->dc[d->level].style.stroke_dasharray.values.size(); i++) { + if (i) + tmp_style << ","; + tmp_style << d->dc[d->level].style.stroke_dasharray.values[i].value; + } + tmp_style << ";"; + tmp_style << "stroke-dashoffset:0;"; + } else { + tmp_style << "stroke-dasharray:none;"; + } + tmp_style << "stroke-opacity:1;"; + } + tmp_style << "\" "; + if (d->dc[d->level].clip_id) + tmp_style << "\n\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\" "; + + d->outsvg += tmp_style.str().c_str(); +} + + +double +Wmf::_pix_x_to_point(PWMF_CALLBACK_DATA d, double px) +{ + double scale = (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0); + double tmp; + tmp = ((((double) (px - d->dc[d->level].winorg.x))*scale) + d->dc[d->level].vieworg.x) * d->D2PscaleX; + tmp -= d->ulCornerOutX; //The WMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner + return(tmp); +} + +double +Wmf::_pix_y_to_point(PWMF_CALLBACK_DATA d, double py) +{ + double scale = (d->dc[d->level].ScaleInY ? d->dc[d->level].ScaleInY : 1.0); + double tmp; + tmp = ((((double) (py - d->dc[d->level].winorg.y))*scale) * d->E2IdirY + d->dc[d->level].vieworg.y) * d->D2PscaleY; + tmp -= d->ulCornerOutY; //The WMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner + return(tmp); +} + + +double +Wmf::pix_to_x_point(PWMF_CALLBACK_DATA d, double px, double /*py*/) +{ + double x = _pix_x_to_point(d, px); + return x; +} + +double +Wmf::pix_to_y_point(PWMF_CALLBACK_DATA d, double /*px*/, double py) +{ + double y = _pix_y_to_point(d, py); + return y; + +} + +double +Wmf::pix_to_abs_size(PWMF_CALLBACK_DATA d, double px) +{ + double ppx = fabs(px * (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0) * d->D2PscaleX * current_scale(d)); + return ppx; +} + +/* returns "x,y" (without the quotes) in inkscape coordinates for a pair of WMF x,y coordinates +*/ +std::string Wmf::pix_to_xy(PWMF_CALLBACK_DATA d, double x, double y){ + SVGOStringStream cxform; + cxform << pix_to_x_point(d,x,y); + cxform << ","; + cxform << pix_to_y_point(d,x,y); + return(cxform.str()); +} + + +void +Wmf::select_pen(PWMF_CALLBACK_DATA d, int index) +{ + int width; + char *record = nullptr; + U_PEN up; + + if (index < 0 && index >= d->n_obj){ return; } + record = d->wmf_obj[index].record; + if(!record){ return; } + d->dc[d->level].active_pen = index; + + (void) U_WMRCREATEPENINDIRECT_get(record, &up); + width = up.Widthw[0]; // width is stored in the first 16 bits of the 32. + + switch (up.Style & U_PS_STYLE_MASK) { + case U_PS_DASH: + case U_PS_DOT: + case U_PS_DASHDOT: + case U_PS_DASHDOTDOT: + { + int penstyle = (up.Style & U_PS_STYLE_MASK); + SPILength spilength(1.f); + if (!d->dc[d->level].style.stroke_dasharray.values.empty() && + (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray != + d->dc[d->level - 1].style.stroke_dasharray))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + spilength.setDouble(3); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + spilength.setDouble(1); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + if (penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + d->dc[d->level].style.stroke_dasharray.values.push_back(spilength); + } + + d->dc[d->level].style.stroke_dasharray.set = true; + break; + } + + case U_PS_SOLID: + default: + { + d->dc[d->level].style.stroke_dasharray.set = false; + break; + } + } + + switch (up.Style & U_PS_ENDCAP_MASK) { + case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND; break; } + case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; break; } + case U_PS_ENDCAP_FLAT: + default: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT; break; } + } + + switch (up.Style & U_PS_JOIN_MASK) { + case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL; break; } + case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; break; } + case U_PS_JOIN_ROUND: + default: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND; break; } + } + + + double pen_width; + if (up.Style == U_PS_NULL) { + d->dc[d->level].stroke_set = false; + pen_width =0.0; + } else if (width) { + d->dc[d->level].stroke_set = true; + int cur_level = d->level; + d->level = d->wmf_obj[index].level; // this object may have been defined in some other DC. + pen_width = pix_to_abs_size( d, width ); + d->level = cur_level; + } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?) + d->dc[d->level].stroke_set = true; + int cur_level = d->level; + d->level = d->wmf_obj[index].level; // this object may have been defined in some other DC. + pen_width = pix_to_abs_size( d, 1 ); + d->level = cur_level; + } + d->dc[d->level].style.stroke_width.value = pen_width; + + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(up.Color) ); + g = SP_COLOR_U_TO_F( U_RGBAGetG(up.Color) ); + b = SP_COLOR_U_TO_F( U_RGBAGetB(up.Color) ); + d->dc[d->level].style.stroke.value.color.set( r, g, b ); +} + + +void +Wmf::select_brush(PWMF_CALLBACK_DATA d, int index) +{ + uint8_t iType; + char *record; + const char *membrush; + + if (index < 0 || index >= d->n_obj)return; + record = d->wmf_obj[index].record; + if(!record)return; + d->dc[d->level].active_brush = index; + + iType = *(uint8_t *)(record + offsetof(U_METARECORD, iType ) ); + if(iType == U_WMR_CREATEBRUSHINDIRECT){ + U_WLOGBRUSH lb; + (void) U_WMRCREATEBRUSHINDIRECT_get(record, &membrush); + memcpy(&lb, membrush, U_SIZE_WLOGBRUSH); + if(lb.Style == U_BS_SOLID){ + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(lb.Color) ); + g = SP_COLOR_U_TO_F( U_RGBAGetG(lb.Color) ); + b = SP_COLOR_U_TO_F( U_RGBAGetB(lb.Color) ); + d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].fill_mode = DRAW_PAINT; + d->dc[d->level].fill_set = true; + } + else if(lb.Style == U_BS_HATCHED){ + d->dc[d->level].fill_idx = add_hatch(d, lb.Hatch, lb.Color); + d->dc[d->level].fill_recidx = index; // used if the hatch needs to be redone due to bkMode, textmode, etc. changes + d->dc[d->level].fill_mode = DRAW_PATTERN; + d->dc[d->level].fill_set = true; + } + else if(lb.Style == U_BS_NULL){ + d->dc[d->level].fill_mode = DRAW_PAINT; // set it to something + d->dc[d->level].fill_set = false; + } + } + else if(iType == U_WMR_DIBCREATEPATTERNBRUSH){ + uint32_t tidx; + uint16_t Style; + uint16_t cUsage; + const char *Bm16h = nullptr; // Pointer to Bitmap16 header (px follows) + const char *dib = nullptr; // Pointer to DIB + (void) U_WMRDIBCREATEPATTERNBRUSH_get(record, &Style, &cUsage, &Bm16h, &dib); + if(dib || Bm16h){ + if(dib){ tidx = add_dib_image(d, dib, cUsage); } + else { + U_BITMAP16 Bm16; + const char *px; + memcpy(&Bm16, Bm16h, U_SIZE_BITMAP16); + px = Bm16h + U_SIZE_BITMAP16; + tidx = add_bm16_image(d, Bm16, px); + } + if(tidx == U_WMR_INVALID){ // Problem with the image, for instance, an unsupported bitmap16 type + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); + g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); + b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); + d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].fill_mode = DRAW_PAINT; + } + else { + d->dc[d->level].fill_idx = tidx; + d->dc[d->level].fill_mode = DRAW_IMAGE; + } + d->dc[d->level].fill_set = true; + } + else { + g_message("Please send WMF file to developers - select_brush U_WMR_DIBCREATEPATTERNBRUSH not bm16 or dib, not handled"); + } + } + else if(iType == U_WMR_CREATEPATTERNBRUSH){ + uint32_t tidx; + int cbPx; + U_BITMAP16 Bm16h; + const char *px; + if(U_WMRCREATEPATTERNBRUSH_get(record, &Bm16h, &cbPx, &px)){ + tidx = add_bm16_image(d, Bm16h, px); + if(tidx == 0xFFFFFFFF){ // Problem with the image, for instance, an unsupported bitmap16 type + double r, g, b; + r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor)); + g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor)); + b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor)); + d->dc[d->level].style.fill.value.color.set( r, g, b ); + d->dc[d->level].fill_mode = DRAW_PAINT; + } + else { + d->dc[d->level].fill_idx = tidx; + d->dc[d->level].fill_mode = DRAW_IMAGE; + } + d->dc[d->level].fill_set = true; + } + } +} + + +void +Wmf::select_font(PWMF_CALLBACK_DATA d, int index) +{ + char *record = nullptr; + const char *memfont; + const char *facename; + U_FONT font; + + if (index < 0 || index >= d->n_obj)return; + record = d->wmf_obj[index].record; + if (!record)return; + d->dc[d->level].active_font = index; + + + (void) U_WMRCREATEFONTINDIRECT_get(record, &memfont); + memcpy(&font,memfont,U_SIZE_FONT_CORE); //make sure it is in a properly aligned structure before touching it + facename = memfont + U_SIZE_FONT_CORE; + + /* The logfont information always starts with a U_LOGFONT structure but the U_WMRCREATEFONTINDIRECT + is defined as U_LOGFONT_PANOSE so it can handle one of those if that is actually present. Currently only logfont + is supported, and the remainder, it it really is a U_LOGFONT_PANOSE record, is ignored + */ + int cur_level = d->level; + d->level = d->wmf_obj[index].level; + double font_size = pix_to_abs_size( d, font.Height ); + /* snap the font_size to the nearest 1/32nd of a point. + (The size is converted from Pixels to points, snapped, and converted back.) + See the notes where d->D2Pscale[XY] are set for the reason why. + Typically this will set the font to the desired exact size. If some peculiar size + was intended this will, at worst, make it .03125 off, which is unlikely to be a problem. */ + font_size = round(20.0 * 0.8 * font_size)/(20.0 * 0.8); + d->level = cur_level; + d->dc[d->level].style.font_size.computed = font_size; + d->dc[d->level].style.font_weight.value = + font.Weight == U_FW_THIN ? SP_CSS_FONT_WEIGHT_100 : + font.Weight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_200 : + font.Weight == U_FW_LIGHT ? SP_CSS_FONT_WEIGHT_300 : + font.Weight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_400 : + font.Weight == U_FW_MEDIUM ? SP_CSS_FONT_WEIGHT_500 : + font.Weight == U_FW_SEMIBOLD ? SP_CSS_FONT_WEIGHT_600 : + font.Weight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_700 : + font.Weight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_800 : + font.Weight == U_FW_HEAVY ? SP_CSS_FONT_WEIGHT_900 : + font.Weight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_NORMAL : + font.Weight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_BOLD : + font.Weight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_LIGHTER : + font.Weight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_BOLDER : + SP_CSS_FONT_WEIGHT_NORMAL; + d->dc[d->level].style.font_style.value = (font.Italic ? SP_CSS_FONT_STYLE_ITALIC : SP_CSS_FONT_STYLE_NORMAL); + d->dc[d->level].style.text_decoration_line.underline = font.Underline; + d->dc[d->level].style.text_decoration_line.line_through = font.StrikeOut; + d->dc[d->level].style.text_decoration_line.set = true; + d->dc[d->level].style.text_decoration_line.inherit = false; + + // malformed WMF with empty filename may exist, ignore font change if encountered + if(d->dc[d->level].font_name)free(d->dc[d->level].font_name); + if(*facename){ + d->dc[d->level].font_name = strdup(facename); + } + else { // Malformed WMF might specify an empty font name + d->dc[d->level].font_name = strdup("Arial"); // Default font, WMF spec says device can pick whatever it wants + } + d->dc[d->level].style.baseline_shift.value = round((double)((font.Escapement + 3600) % 3600) / 10.0); // use baseline_shift instead of text_transform to avoid overflow +} + +/* Find the first free hole where an object may be stored. + If there are not any return -1. This is a big error, possibly from a corrupt WMF file. +*/ +int Wmf::insertable_object(PWMF_CALLBACK_DATA d) +{ + int index = d->low_water; // Start looking from here, it may already have been filled + while(index < d->n_obj && d->wmf_obj[index].record != nullptr){ index++; } + if(index >= d->n_obj)return(-1); // this is a big problem, percolate it back up so the program can get out of this gracefully + d->low_water = index; // Could probably be index+1 + return(index); +} + +void +Wmf::delete_object(PWMF_CALLBACK_DATA d, int index) +{ + if (index >= 0 && index < d->n_obj) { + // If the active object is deleted set default draw values + if(index == d->dc[d->level].active_pen){ // Use default pen: solid, black, 1 pixel wide + d->dc[d->level].active_pen = -1; + d->dc[d->level].style.stroke_dasharray.set = false; + d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; // U_PS_ENDCAP_SQUARE + d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; // U_PS_JOIN_MITER; + d->dc[d->level].stroke_set = true; + d->dc[d->level].style.stroke_width.value = 1.0; + d->dc[d->level].style.stroke.value.color.set( 0, 0, 0 ); + } + else if(index == d->dc[d->level].active_brush){ + d->dc[d->level].active_brush = -1; + d->dc[d->level].fill_set = false; + } + else if(index == d->dc[d->level].active_font){ + d->dc[d->level].active_font = -1; + if(d->dc[d->level].font_name){ free(d->dc[d->level].font_name);} + d->dc[d->level].font_name = strdup("Arial"); // Default font, WMF spec says device can pick whatever it wants + d->dc[d->level].style.font_size.computed = 16.0; + d->dc[d->level].style.font_weight.value = SP_CSS_FONT_WEIGHT_400; + d->dc[d->level].style.font_style.value = SP_CSS_FONT_STYLE_NORMAL; + d->dc[d->level].style.text_decoration_line.underline = false; + d->dc[d->level].style.text_decoration_line.line_through = false; + d->dc[d->level].style.baseline_shift.value = 0; + } + + + d->wmf_obj[index].type = 0; +// We are keeping a copy of the WMR rather than just a structure. Currently that is not necessary as the entire +// WMF is read in at once and is stored in a big malloc. However, in past versions it was handled +// record by record, and we might need to do that again at some point in the future if we start running into WMF +// files too big to fit into memory. + if (d->wmf_obj[index].record) + free(d->wmf_obj[index].record); + d->wmf_obj[index].record = nullptr; + if(index < d->low_water)d->low_water = index; + } +} + + +// returns the new index, or -1 on error. +int Wmf::insert_object(PWMF_CALLBACK_DATA d, int type, const char *record) +{ + int index = insertable_object(d); + if(index>=0){ + d->wmf_obj[index].type = type; + d->wmf_obj[index].level = d->level; + d->wmf_obj[index].record = wmr_dup(record); + } + return(index); +} + + +/** + \fn create a UTF-32LE buffer and fill it with UNICODE unknown character + \param count number of copies of the Unicode unknown character to fill with +*/ +uint32_t *Wmf::unknown_chars(size_t count){ + uint32_t *res = (uint32_t *) malloc(sizeof(uint32_t) * (count + 1)); + if(!res)throw "Inkscape fatal memory allocation error - cannot continue"; + for(uint32_t i=0; i<count; i++){ res[i] = 0xFFFD; } + res[count]=0; + return res; +} + +/** + \brief store SVG for an image given the pixmap and various coordinate information + \param d + \param dib packed DIB in memory + \param dx (double) destination x in inkscape pixels + \param dy (double) destination y in inkscape pixels + \param dw (double) destination width in inkscape pixels + \param dh (double) destination height in inkscape pixels + \param sx (int) source x in src image pixels + \param sy (int) source y in src image pixels + \param iUsage +*/ +void Wmf::common_dib_to_image(PWMF_CALLBACK_DATA d, const char *dib, + double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh, uint32_t iUsage){ + + SVGOStringStream tmp_image; + int dibparams = U_BI_UNKNOWN; // type of image not yet determined + + tmp_image << "\n\t <image\n"; + if (d->dc[d->level].clip_id){ + tmp_image << "\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n"; + } + tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n "; + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = nullptr; + + char *rgba_px = nullptr; // RGBA pixels + char *sub_px = nullptr; // RGBA pixels, subarray + const char *px = nullptr; // DIB pixels + const U_RGBQUAD *ct = nullptr; // color table + uint32_t numCt; + int32_t width, height, colortype, invert; // if needed these values will be set in wget_DIB_params + if(iUsage == U_DIB_RGB_COLORS){ + // next call returns pointers and values, but allocates no memory + dibparams = wget_DIB_params(dib, &px, &ct, &numCt, &width, &height, &colortype, &invert); + if(dibparams == U_BI_RGB){ + if(sw == 0 || sh == 0){ + sw = width; + sh = height; + } + if(!DIB_to_RGBA( + px, // DIB pixel array + ct, // DIB color table + numCt, // DIB color table number of entries + &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free. + width, // Width of pixel array + height, // Height of pixel array + colortype, // DIB BitCount Enumeration + numCt, // Color table used if not 0 + invert // If DIB rows are in opposite order from RGBA rows + )){ + sub_px = RGBA_to_RGBA( // returns either a subset (side effect: frees rgba_px) or NULL (for subset == entire image) + rgba_px, // full pixel array from DIB + width, // Width of pixel array + height, // Height of pixel array + sx,sy, // starting point in pixel array + &sw,&sh // columns/rows to extract from the pixel array (output array size) + ); + + if(!sub_px)sub_px=rgba_px; + toPNG( // Get the image from the RGBA px into mempng + &mempng, + sw, sh, // size of the extracted pixel array + sub_px + ); + free(sub_px); + } + } + } + + gchar *base64String=nullptr; + if(dibparams == U_BI_JPEG){ // image was binary jpg in source file + tmp_image << " xlink:href=\"data:image/jpeg;base64,"; + base64String = g_base64_encode((guchar*) px, numCt ); + } + else if(dibparams==U_BI_PNG){ // image was binary png in source file + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = g_base64_encode((guchar*) px, numCt ); + } + else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size ); + free(mempng.buffer); + } + else { // unknown or unsupported image type or failed conversion, insert the common bad image picture + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = bad_image_png(); + } + tmp_image << base64String; + g_free(base64String); + + tmp_image << "\"\n height=\"" << dh << "\"\n width=\"" << dw << "\"\n"; + tmp_image << " transform=" << current_matrix(d, 0.0, 0.0, 0); // returns an identity matrix, no offsets. + tmp_image << " preserveAspectRatio=\"none\"\n"; + tmp_image << "/> \n"; + + d->outsvg += tmp_image.str().c_str(); + d->path = ""; +} + +/** + \brief store SVG for an image given the pixmap and various coordinate information + \param d + \param Bm16 core Bitmap16 header + \param px pointer to Bitmap16 image data + \param dx (double) destination x in inkscape pixels + \param dy (double) destination y in inkscape pixels + \param dw (double) destination width in inkscape pixels + \param dh (double) destination height in inkscape pixels + \param sx (int) source x in src image pixels + \param sy (int) source y in src image pixels + \param iUsage +*/ +void Wmf::common_bm16_to_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px, + double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh){ + + SVGOStringStream tmp_image; + + tmp_image << "\n\t <image\n"; + if (d->dc[d->level].clip_id){ + tmp_image << "\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n"; + } + tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n "; + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = nullptr; + + char *rgba_px = nullptr; // RGBA pixels + char *sub_px = nullptr; // RGBA pixels, subarray + const U_RGBQUAD *ct = nullptr; // color table + int32_t width, height, colortype, numCt, invert; + + numCt = 0; + width = Bm16.Width; // bitmap width in pixels. + height = Bm16.Height; // bitmap height in scan lines. + colortype = Bm16.BitsPixel; // seems to be BitCount Enumeration + invert = 0; + + if(sw == 0 || sh == 0){ + sw = width; + sh = height; + } + + if(colortype < 16)return; // these would need a colortable if they were a dib, no idea what bm16 is supposed to do instead. + + if(!DIB_to_RGBA(// This is not really a dib, but close enough so that it still works. + px, // DIB pixel array + ct, // DIB color table (always NULL here) + numCt, // DIB color table number of entries (always 0) + &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free. + width, // Width of pixel array + height, // Height of pixel array + colortype, // DIB BitCount Enumeration + numCt, // Color table used if not 0 + invert // If DIB rows are in opposite order from RGBA rows + )){ + sub_px = RGBA_to_RGBA( // returns either a subset (side effect: frees rgba_px) or NULL (for subset == entire image) + rgba_px, // full pixel array from DIB + width, // Width of pixel array + height, // Height of pixel array + sx,sy, // starting point in pixel array + &sw,&sh // columns/rows to extract from the pixel array (output array size) + ); + + if(!sub_px)sub_px=rgba_px; + toPNG( // Get the image from the RGBA px into mempng + &mempng, + sw, sh, // size of the extracted pixel array + sub_px + ); + free(sub_px); + } + + gchar *base64String=nullptr; + if(mempng.buffer){ // image was Bm16 in source file, converted to png in this routine + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size ); + free(mempng.buffer); + } + else { // unknown or unsupported image type or failed conversion, insert the common bad image picture + tmp_image << " xlink:href=\"data:image/png;base64,"; + base64String = bad_image_png(); + } + tmp_image << base64String; + g_free(base64String); + + tmp_image << "\"\n height=\"" << dh << "\"\n width=\"" << dw << "\"\n"; + tmp_image << " transform=" << current_matrix(d, 0.0, 0.0, 0); // returns an identity matrix, no offsets. + tmp_image << " preserveAspectRatio=\"none\"\n"; + tmp_image << "/> \n"; + + d->outsvg += tmp_image.str().c_str(); + d->path = ""; +} + +/** + \fn myMetaFileProc(char *contents, unsigned int length, PWMF_CALLBACK_DATA lpData) + \returns 1 on success, 0 on error + \param contents binary contents of an WMF file + \param length length in bytes of contents + \param d Inkscape data structures returned by this call +*/ +//THis was a callback, just build it into a normal function +int Wmf::myMetaFileProc(const char *contents, unsigned int length, PWMF_CALLBACK_DATA d) +{ + uint32_t off=0; + uint32_t wmr_mask; + int OK =1; + int file_status=1; + TCHUNK_SPECS tsp; + uint8_t iType; + int nSize; // size of the current record, in bytes, or an error value if <=0 + const char *blimit = contents + length; // 1 byte past the end of the last record + + /* variables used to retrieve data from WMF records */ + uint16_t utmp16; + U_POINT16 pt16; // any point + U_RECT16 rc; // any rectangle, usually a bounding rectangle + U_POINT16 Dst; // Destination coordinates + U_POINT16 cDst; // Destination w,h, if different from Src + U_POINT16 Src; // Source coordinates + U_POINT16 cSrc; // Source w,h, if different from Dst + U_POINT16 cwh; // w,h, if Src and Dst use the same values + uint16_t cUsage; // colorusage enumeration + uint32_t dwRop3; // raster operations, these are only barely supported here + const char *dib; // DIB style image structure + U_BITMAP16 Bm16; // Bitmap16 style image structure + const char *px; // Image for Bm16 + uint16_t cPts; // number of points in the next variable + const char *points; // any list of U_POINT16, may not be aligned + int16_t tlen; // length of returned text, in bytes + const char *text; // returned text, Latin1 encoded + uint16_t Opts; + const int16_t *dx; // character spacing for one text mode, inkscape ignores this + double left, right, top, bottom; // values used, because a bounding rect can have values reversed L<->R, T<->B + + uint16_t tbkMode = U_TRANSPARENT; // holds proposed change to bkMode, if text is involved saving these to the DC must wait until the text is written + U_COLORREF tbkColor = U_RGB(255, 255, 255); // holds proposed change to bkColor + + // code for end user debugging + int wDbgRecord=0; + int wDbgComment=0; + int wDbgFinal=0; + char const* wDbgString = getenv( "INKSCAPE_DBG_WMF" ); + if ( wDbgString != nullptr ) { + if(strstr(wDbgString,"RECORD")){ wDbgRecord = 1; } + if(strstr(wDbgString,"COMMENT")){ wDbgComment = 1; } + if(strstr(wDbgString,"FINAL")){ wDbgFinal = 1; } + } + + /* initialize the tsp for text reassembly */ + tsp.string = nullptr; + tsp.ori = 0.0; /* degrees */ + tsp.fs = 12.0; /* font size */ + tsp.x = 0.0; + tsp.y = 0.0; + tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/ + tsp.vadvance = 0.0; /* meaningful only when a complex contains two or more lines */ + tsp.taln = ALILEFT + ALIBASE; + tsp.ldir = LDIR_LR; + tsp.spaces = 0; // this field is only used for debugging + tsp.color.Red = 0; /* RGB Black */ + tsp.color.Green = 0; /* RGB Black */ + tsp.color.Blue = 0; /* RGB Black */ + tsp.color.Reserved = 0; /* not used */ + tsp.italics = 0; + tsp.weight = 80; + tsp.decoration = TXTDECOR_NONE; + tsp.condensed = 100; + tsp.co = 0; + tsp.fi_idx = -1; /* set to an invalid */ + tsp.rt_tidx = -1; /* set to an invalid */ + + SVGOStringStream dbg_str; + + /* There is very little information in WMF headers, get what is there. In many cases pretty much everything will have to + default. If there is no placeable header we know pretty much nothing about the size of the page, in which case + assume that it is 1440 WMF pixels/inch and make the page A4 landscape. That is almost certainly the wrong page size + but it has to be set to something, and nothing horrible happens if the drawing goes off the page. */ + { + + U_WMRPLACEABLE Placeable; + U_WMRHEADER Header; + off = 0; + nSize = wmfheader_get(contents, blimit, &Placeable, &Header); + if (!nSize) { + return(0); + } + if(!Header.nObjects){ Header.nObjects = 256; }// there _may_ be WMF files with no objects, more likely it is corrupt. Try to use it anyway. + d->n_obj = Header.nObjects; + d->wmf_obj = new WMF_OBJECT[d->n_obj]; + d->low_water = 0; // completely empty at this point, so start searches at 0 + + // Init the new wmf_obj list elements to null, provided the + // dynamic allocation succeeded. + if ( d->wmf_obj != nullptr ) + { + for( int i=0; i < d->n_obj; ++i ) + d->wmf_obj[i].record = nullptr; + } //if + + if(!Placeable.Inch){ Placeable.Inch= 1440; } + if(!Placeable.Dst.right && !Placeable.Dst.left){ // no page size has been supplied + // This is gross, scan forward looking for a SETWINDOWEXT record, use the first one found to + // define the page size + int hold_nSize = off = nSize; + Placeable.Dst.left = 0; + Placeable.Dst.top = 0; + while(OK){ + nSize = U_WMRRECSAFE_get(contents + off, blimit); + if(nSize){ + iType = *(uint8_t *)(contents + off + offsetof(U_METARECORD, iType ) ); + if(iType == U_WMR_SETWINDOWEXT){ + OK=0; + (void) U_WMRSETWINDOWEXT_get(contents + off, &Dst); + Placeable.Dst.right = Dst.x; + Placeable.Dst.bottom = Dst.y; + } + else if(iType == U_WMR_EOF){ + OK=0; + // Really messed up WMF, have to set the page to something, make it A4 horizontal + Placeable.Dst.right = round(((double) Placeable.Inch) * 297.0/25.4); + Placeable.Dst.bottom = round(((double) Placeable.Inch) * 210.0/25.4); + } + else { + off += nSize; + } + } + else { + return(0); + } + } + off=0; + nSize = hold_nSize; + OK=1; + } + + // drawing size in WMF pixels + d->PixelsInX = Placeable.Dst.right - Placeable.Dst.left + 1; + d->PixelsInY = Placeable.Dst.bottom - Placeable.Dst.top + 1; + + /* + Set values for Window and ViewPort extents to 0 - not defined yet. + */ + d->dc[d->level].sizeView.x = d->dc[d->level].sizeWnd.x = 0; + d->dc[d->level].sizeView.y = d->dc[d->level].sizeWnd.y = 0; + + /* Upper left corner in device units, usually both 0, but not always. + If a placeable header is used, and later a windoworg/windowext are found, then + the placeable information will be ignored. + */ + d->ulCornerInX = Placeable.Dst.left; + d->ulCornerInY = Placeable.Dst.top; + + d->E2IdirY = 1.0; // assume MM_ANISOTROPIC, if not, this will be changed later + d->D2PscaleX = d->D2PscaleY = Inkscape::Util::Quantity::convert(1, "in", "px")/(double) Placeable.Inch; + trinfo_load_qe(d->tri, d->D2PscaleX); /* quantization error that will affect text positions */ + + // drawing size in Inkscape pixels + d->PixelsOutX = d->PixelsInX * d->D2PscaleX; + d->PixelsOutY = d->PixelsInY * d->D2PscaleY; + + // Upper left corner in Inkscape units + d->ulCornerOutX = d->ulCornerInX * d->D2PscaleX; + d->ulCornerOutY = d->ulCornerInY * d->E2IdirY * d->D2PscaleY; + + d->dc[0].style.stroke_width.value = pix_to_abs_size( d, 1 ); // This could not be set until the size of the WMF was known + dbg_str << "<!-- U_WMR_HEADER -->\n"; + + d->outdef += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"; + + SVGOStringStream tmp_outdef; + tmp_outdef << "<svg\n"; + tmp_outdef << " xmlns:svg=\"http://www.w3.org/2000/svg\"\n"; + tmp_outdef << " xmlns=\"http://www.w3.org/2000/svg\"\n"; + tmp_outdef << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"; + tmp_outdef << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"; // needed for sodipodi:role + tmp_outdef << " version=\"1.0\"\n"; + + tmp_outdef << + " width=\"" << Inkscape::Util::Quantity::convert(d->PixelsOutX, "px", "mm") << "mm\"\n" << + " height=\"" << Inkscape::Util::Quantity::convert(d->PixelsOutY, "px", "mm") << "mm\">\n"; + d->outdef += tmp_outdef.str().c_str(); + d->outdef += "<defs>"; // temporary end of header + + // d->defs holds any defines which are read in. + + + } + + + + while(OK){ + if (off>=length) { + return(0); //normally should exit from while after WMREOF sets OK to false. + } + contents += nSize; // pointer to the start of the next record + off += nSize; // offset from beginning of buffer to the start of the next record + + /* Currently this is a weaker check than for EMF, it only checks the size of the constant part + of the record */ + nSize = U_WMRRECSAFE_get(contents, blimit); + if(!nSize) { + file_status = 0; + break; + } + + iType = *(uint8_t *)(contents + offsetof(U_METARECORD, iType ) ); + + wmr_mask = U_wmr_properties(iType); + if (wmr_mask == U_WMR_INVALID) { + file_status = 0; + break; + } +// At run time define environment variable INKSCAPE_DBG_WMF to include string RECORD. +// Users may employ this to track down toxic records + if(wDbgRecord){ + std::cout << "record type: " << iType << " name " << U_wmr_names(iType) << " length: " << nSize << " offset: " << off <<std::endl; + } + + SVGOStringStream tmp_path; + SVGOStringStream tmp_str; + +/* Uncomment the following to track down text problems */ +//std::cout << "tri->dirty:"<< d->tri->dirty << " wmr_mask: " << std::hex << wmr_mask << std::dec << std::endl; + + // incompatible change to text drawing detected (color or background change) forces out existing text + // OR + // next record is valid type and forces pending text to be drawn immediately + if ((d->dc[d->level].dirty & DIRTY_TEXT) || ((wmr_mask != U_WMR_INVALID) && (wmr_mask & U_DRAW_TEXT) && d->tri->dirty)){ + TR_layout_analyze(d->tri); + if (d->dc[d->level].clip_id){ + SVGOStringStream tmp_clip; + tmp_clip << "\n<g\n\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n>"; + d->outsvg += tmp_clip.str().c_str(); + } + TR_layout_2_svg(d->tri); + SVGOStringStream ts; + ts << d->tri->out; + d->outsvg += ts.str().c_str(); + d->tri = trinfo_clear(d->tri); + if (d->dc[d->level].clip_id){ + d->outsvg += "\n</g>\n"; + } + } + if(d->dc[d->level].dirty){ //Apply the delayed background changes, clear the flag + d->dc[d->level].bkMode = tbkMode; + memcpy(&(d->dc[d->level].bkColor),&tbkColor, sizeof(U_COLORREF)); + + if(d->dc[d->level].dirty & DIRTY_TEXT){ + // U_COLORREF and TRCOLORREF are exactly the same in memory, but the compiler needs some convincing... + if(tbkMode == U_TRANSPARENT){ (void) trinfo_load_bk(d->tri, BKCLR_NONE, *(TRCOLORREF *) &tbkColor); } + else { (void) trinfo_load_bk(d->tri, BKCLR_LINE, *(TRCOLORREF *) &tbkColor); } // Opaque + } + + /* It is possible to have a series of EMF records that would result in + the following creating hash patterns which are never used. For instance, if + there were a series of records that changed the background color but did nothing + else. + */ + if((d->dc[d->level].fill_mode == DRAW_PATTERN) && (d->dc[d->level].dirty & DIRTY_FILL)){ + select_brush(d, d->dc[d->level].fill_recidx); + } + + d->dc[d->level].dirty = 0; + } + +//std::cout << "BEFORE DRAW logic d->mask: " << std::hex << d->mask << " wmr_mask: " << wmr_mask << std::dec << std::endl; +/* +std::cout << "BEFORE DRAW" + << " test0 " << ( d->mask & U_DRAW_VISIBLE) + << " test1 " << ( d->mask & U_DRAW_FORCE) + << " test2 " << (wmr_mask & U_DRAW_ALTERS) + << " test2.5 " << ((d->mask & U_DRAW_NOFILL) != (wmr_mask & U_DRAW_NOFILL) ) + << " test3 " << (wmr_mask & U_DRAW_VISIBLE) + << " test4 " << !(d->mask & U_DRAW_ONLYTO) + << " test5 " << ((d->mask & U_DRAW_ONLYTO) && !(wmr_mask & U_DRAW_ONLYTO) ) + << std::endl; +*/ + /* spurious moveto records should not affect the drawing. However, they set the NOFILL + bit and that messes up the logic about when to emit a path. So prune out any + stray moveto records. That is those which were never followed by a lineto. + */ + if((d->mask & U_DRAW_NOFILL) && !(d->mask & U_DRAW_VISIBLE) && + !(wmr_mask & U_DRAW_ONLYTO) && (wmr_mask & U_DRAW_VISIBLE)){ + d->mask ^= U_DRAW_NOFILL; + } + + if( + (wmr_mask != U_WMR_INVALID) && // next record is valid type + (d->mask & U_DRAW_VISIBLE) && // This record is drawable + ( + (d->mask & U_DRAW_FORCE) || // This draw is forced by STROKE/FILL/STROKEANDFILL PATH + (wmr_mask & U_DRAW_ALTERS) || // Next record would alter the drawing environment in some way + ((d->mask & U_DRAW_NOFILL) != (wmr_mask & U_DRAW_NOFILL)) || // Fill<->!Fill requires a draw between + ( (wmr_mask & U_DRAW_VISIBLE) && // Next record is visible... + ( + ( !(d->mask & U_DRAW_ONLYTO) ) || // Non *TO records cannot be followed by any Visible + ((d->mask & U_DRAW_ONLYTO) && !(wmr_mask & U_DRAW_ONLYTO) )// *TO records can only be followed by other *TO records + ) + ) + ) + ){ +// std::cout << "PATH DRAW at TOP <<+++++++++++++++++++++++++++++++++++++" << std::endl; + if(!(d->path.empty())){ + d->outsvg += " <path "; // this is the ONLY place <path should be used!!!! + output_style(d); + d->outsvg += "\n\t"; + d->outsvg += "\n\td=\""; // this is the ONLY place d=" should be used!!!! + d->outsvg += d->path; + d->outsvg += " \" /> \n"; + d->path = ""; //reset the path + } + // reset the flags + d->mask = 0; + d->drawtype = 0; + } +// std::cout << "AFTER DRAW logic d->mask: " << std::hex << d->mask << " wmr_mask: " << wmr_mask << std::dec << std::endl; + switch (iType) + { + case U_WMR_EOF: + { + dbg_str << "<!-- U_WMR_EOF -->\n"; + + d->outsvg = d->outdef + d->defs + "\n</defs>\n\n" + d->outsvg + "</svg>\n"; + OK=0; + break; + } + case U_WMR_SETBKCOLOR: + { + dbg_str << "<!-- U_WMR_SETBKCOLOR -->\n"; + nSize = U_WMRSETBKCOLOR_get(contents, &tbkColor); + if(memcmp(&tbkColor, &(d->dc[d->level].bkColor), sizeof(U_COLORREF))){ + d->dc[d->level].dirty |= DIRTY_TEXT; + if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; } + tbkMode = d->dc[d->level].bkMode; + } + break; + } + case U_WMR_SETBKMODE:{ + dbg_str << "<!-- U_WMR_SETBKMODE -->\n"; + nSize = U_WMRSETBKMODE_get(contents, &tbkMode); + if(tbkMode != d->dc[d->level].bkMode){ + d->dc[d->level].dirty |= DIRTY_TEXT; + if(tbkMode != d->dc[d->level].bkMode){ + if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; } + } + memcpy(&tbkColor,&(d->dc[d->level].bkColor),sizeof(U_COLORREF)); + } + break; + } + case U_WMR_SETMAPMODE: + { + dbg_str << "<!-- U_WMR_SETMAPMODE -->\n"; + nSize = U_WMRSETMAPMODE_get(contents, &utmp16); + switch (utmp16){ + case U_MM_TEXT: + default: + // Use all values from the header. + break; + /* For all of the following the indicated scale this will be encoded in WindowExtEx/ViewportExtex + and show up in ScaleIn[XY] + */ + case U_MM_LOMETRIC: // 1 LU = 0.1 mm, + case U_MM_HIMETRIC: // 1 LU = 0.01 mm + case U_MM_LOENGLISH: // 1 LU = 0.1 in + case U_MM_HIENGLISH: // 1 LU = 0.01 in + case U_MM_TWIPS: // 1 LU = 1/1440 in + d->E2IdirY = -1.0; + // Use d->D2Pscale[XY] values from the header. + break; + case U_MM_ISOTROPIC: // ScaleIn[XY] should be set elsewhere by SETVIEWPORTEXTEX and SETWINDOWEXTEX + case U_MM_ANISOTROPIC: + break; + } + break; + } + case U_WMR_SETROP2: + { + dbg_str << "<!-- U_WMR_SETROP2 -->\n"; + nSize = U_WMRSETROP2_get(contents, &utmp16); + d->dwRop2 = utmp16; + break; + } + case U_WMR_SETRELABS: dbg_str << "<!-- U_WMR_SETRELABS -->\n"; break; + case U_WMR_SETPOLYFILLMODE: + { + dbg_str << "<!-- U_WMR_SETPOLYFILLMODE -->\n"; + nSize = U_WMRSETPOLYFILLMODE_get(contents, &utmp16); + d->dc[d->level].style.fill_rule.value = + (utmp16 == U_ALTERNATE ? SP_WIND_RULE_NONZERO + : utmp16 == U_WINDING ? SP_WIND_RULE_INTERSECT : SP_WIND_RULE_NONZERO); + break; + } + case U_WMR_SETSTRETCHBLTMODE: + { + dbg_str << "<!-- U_WMR_SETSTRETCHBLTMODE -->\n"; + nSize = U_WMRSETSTRETCHBLTMODE_get(contents, &utmp16); + BLTmode = utmp16; + break; + } + case U_WMR_SETTEXTCHAREXTRA: dbg_str << "<!-- U_WMR_SETTEXTCHAREXTRA -->\n"; break; + case U_WMR_SETTEXTCOLOR: + { + dbg_str << "<!-- U_WMR_SETTEXTCOLOR -->\n"; + nSize = U_WMRSETTEXTCOLOR_get(contents, &(d->dc[d->level].textColor)); + if(tbkMode != d->dc[d->level].bkMode){ + if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; } + } + // not text_dirty, because multicolored complex text is supported in libTERE + break; + } + case U_WMR_SETTEXTJUSTIFICATION: dbg_str << "<!-- U_WMR_SETTEXTJUSTIFICATION -->\n"; break; + case U_WMR_SETWINDOWORG: + { + dbg_str << "<!-- U_WMR_SETWINDOWORG -->\n"; + nSize = U_WMRSETWINDOWORG_get(contents, &d->dc[d->level].winorg); + d->ulCornerOutX = 0.0; // In the examples seen to date if this record is used with a placeable header, that header is ignored + d->ulCornerOutY = 0.0; + break; + } + case U_WMR_SETWINDOWEXT: + { + dbg_str << "<!-- U_WMR_SETWINDOWEXT -->\n"; + + nSize = U_WMRSETWINDOWEXT_get(contents, &d->dc[d->level].sizeWnd); + + if (!d->dc[d->level].sizeWnd.x || !d->dc[d->level].sizeWnd.y) { + d->dc[d->level].sizeWnd = d->dc[d->level].sizeView; + if (!d->dc[d->level].sizeWnd.x || !d->dc[d->level].sizeWnd.y) { + d->dc[d->level].sizeWnd.x = d->PixelsOutX; + d->dc[d->level].sizeWnd.y = d->PixelsOutY; + } + } + else { + /* There are a lot WMF files in circulation with the x,y values in the setwindowext reversed. If this is detected, swap them. + There is a remote possibility that the strange scaling this implies was intended, and those will be rendered incorrectly */ + double Ox = d->PixelsOutX; + double Oy = d->PixelsOutY; + double Wx = d->dc[d->level].sizeWnd.x; + double Wy = d->dc[d->level].sizeWnd.y; + if(Wx != Wy && Geom::are_near(Ox/Wy, Oy/Wx, 1.01/MIN(Wx,Wy)) ){ + int tmp; + tmp = d->dc[d->level].sizeWnd.x; + d->dc[d->level].sizeWnd.x = d->dc[d->level].sizeWnd.y; + d->dc[d->level].sizeWnd.y = tmp; + } + } + + if (!d->dc[d->level].sizeView.x || !d->dc[d->level].sizeView.y) { + /* Previously it used sizeWnd, but that always resulted in scale = 1 if no viewport ever appeared, and in most files, it did not */ + d->dc[d->level].sizeView.x = d->PixelsInX - 1; + d->dc[d->level].sizeView.y = d->PixelsInY - 1; + } + + /* scales logical to WMF pixels, transfer a negative sign on Y, if any */ + if (d->dc[d->level].sizeWnd.x && d->dc[d->level].sizeWnd.y) { + d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.x / (double) d->dc[d->level].sizeWnd.x; + d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.y / (double) d->dc[d->level].sizeWnd.y; + if(d->dc[d->level].ScaleInY < 0){ + d->dc[d->level].ScaleInY *= -1.0; + d->E2IdirY = -1.0; + } + } + else { + d->dc[d->level].ScaleInX = 1; + d->dc[d->level].ScaleInY = 1; + } + break; + } + case U_WMR_SETVIEWPORTORG: + { + dbg_str << "<!-- U_WMR_SETWINDOWORG -->\n"; + nSize = U_WMRSETVIEWPORTORG_get(contents, &d->dc[d->level].vieworg); + break; + } + case U_WMR_SETVIEWPORTEXT: + { + dbg_str << "<!-- U_WMR_SETVIEWPORTEXTEX -->\n"; + + nSize = U_WMRSETVIEWPORTEXT_get(contents, &d->dc[d->level].sizeView); + + if (!d->dc[d->level].sizeView.x || !d->dc[d->level].sizeView.y) { + d->dc[d->level].sizeView = d->dc[d->level].sizeWnd; + if (!d->dc[d->level].sizeView.x || !d->dc[d->level].sizeView.y) { + d->dc[d->level].sizeView.x = d->PixelsOutX; + d->dc[d->level].sizeView.y = d->PixelsOutY; + } + } + + if (!d->dc[d->level].sizeWnd.x || !d->dc[d->level].sizeWnd.y) { + d->dc[d->level].sizeWnd = d->dc[d->level].sizeView; + } + + /* scales logical to WMF pixels, transfer a negative sign on Y, if any */ + if (d->dc[d->level].sizeWnd.x && d->dc[d->level].sizeWnd.y) { + d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.x / (double) d->dc[d->level].sizeWnd.x; + d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.y / (double) d->dc[d->level].sizeWnd.y; + if(d->dc[d->level].ScaleInY < 0){ + d->dc[d->level].ScaleInY *= -1.0; + d->E2IdirY = -1.0; + } + } + else { + d->dc[d->level].ScaleInX = 1; + d->dc[d->level].ScaleInY = 1; + } + break; + } + case U_WMR_OFFSETWINDOWORG: dbg_str << "<!-- U_WMR_OFFSETWINDOWORG -->\n"; break; + case U_WMR_SCALEWINDOWEXT: dbg_str << "<!-- U_WMR_SCALEWINDOWEXT -->\n"; break; + case U_WMR_OFFSETVIEWPORTORG: dbg_str << "<!-- U_WMR_OFFSETVIEWPORTORG -->\n"; break; + case U_WMR_SCALEVIEWPORTEXT: dbg_str << "<!-- U_WMR_SCALEVIEWPORTEXT -->\n"; break; + case U_WMR_LINETO: + { + dbg_str << "<!-- U_WMR_LINETO -->\n"; + + nSize = U_WMRLINETO_get(contents, &pt16); + + d->mask |= wmr_mask; + + tmp_path << "\n\tL " << pix_to_xy( d, pt16.x, pt16.y) << " "; + break; + } + case U_WMR_MOVETO: + { + dbg_str << "<!-- U_WMR_MOVETO -->\n"; + + nSize = U_WMRLINETO_get(contents, &pt16); + + d->mask |= wmr_mask; + + d->dc[d->level].cur = pt16; + + tmp_path << + "\n\tM " << pix_to_xy( d, pt16.x, pt16.y ) << " "; + break; + } + case U_WMR_EXCLUDECLIPRECT: + { + dbg_str << "<!-- U_WMR_EXCLUDECLIPRECT -->\n"; + + U_RECT16 rc; + nSize = U_WMREXCLUDECLIPRECT_get(contents, &rc); + + SVGOStringStream tmp_path; + float faraway = 10000000; // hopefully well outside any real drawing! + //outer rect, clockwise + tmp_path << "M " << faraway << "," << faraway << " "; + tmp_path << "L " << faraway << "," << -faraway << " "; + tmp_path << "L " << -faraway << "," << -faraway << " "; + tmp_path << "L " << -faraway << "," << faraway << " "; + tmp_path << "z "; + //inner rect, counterclockwise (sign of Y is reversed) + tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " "; + tmp_path << "z"; + + add_clips(d, tmp_path.str().c_str(), U_RGN_AND); + + d->path = ""; + d->drawtype = 0; + break; + } + case U_WMR_INTERSECTCLIPRECT: + { + dbg_str << "<!-- U_WMR_INTERSECTCLIPRECT -->\n"; + + nSize = U_WMRINTERSECTCLIPRECT_get(contents, &rc); + + SVGOStringStream tmp_path; + tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " "; + tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " "; + tmp_path << "z"; + + add_clips(d, tmp_path.str().c_str(), U_RGN_AND); + + d->path = ""; + d->drawtype = 0; + break; + } + case U_WMR_ARC: + { + dbg_str << "<!-- U_WMR_ARC -->\n"; + U_POINT16 ArcStart, ArcEnd; + nSize = U_WMRARC_get(contents, &ArcStart, &ArcEnd, &rc); + + U_PAIRF center,start,end,size; + int f1; + int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1); + int stat = wmr_arc_points(rc, ArcStart, ArcEnd,&f1, f2, ¢er, &start, &end, &size); + if(!stat){ + tmp_path << "\n\tM " << pix_to_xy(d, start.x, start.y); + tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0; + tmp_path << " "; + tmp_path << 180.0 * current_rotation(d)/M_PI; + tmp_path << " "; + tmp_path << " " << f1 << "," << f2 << " "; + tmp_path << pix_to_xy(d, end.x, end.y) << " \n"; + d->mask |= wmr_mask; + } + else { + dbg_str << "<!-- ARC record is invalid -->\n"; + } + break; + } + case U_WMR_ELLIPSE: + { + dbg_str << "<!-- U_WMR_ELLIPSE -->\n"; + + nSize = U_WMRELLIPSE_get(contents, &rc); + + double cx = pix_to_x_point( d, (rc.left + rc.right)/2.0, (rc.bottom + rc.top)/2.0 ); + double cy = pix_to_y_point( d, (rc.left + rc.right)/2.0, (rc.bottom + rc.top)/2.0 ); + double rx = pix_to_abs_size( d, std::abs(rc.right - rc.left )/2.0 ); + double ry = pix_to_abs_size( d, std::abs(rc.top - rc.bottom)/2.0 ); + + SVGOStringStream tmp_ellipse; + tmp_ellipse << "cx=\"" << cx << "\" "; + tmp_ellipse << "cy=\"" << cy << "\" "; + tmp_ellipse << "rx=\"" << rx << "\" "; + tmp_ellipse << "ry=\"" << ry << "\" "; + + d->mask |= wmr_mask; + + d->outsvg += " <ellipse "; + output_style(d); + d->outsvg += "\n\t"; + d->outsvg += tmp_ellipse.str().c_str(); + d->outsvg += "/> \n"; + d->path = ""; + break; + } + case U_WMR_FLOODFILL: dbg_str << "<!-- U_WMR_EXTFLOODFILL -->\n"; break; + case U_WMR_PIE: + { + dbg_str << "<!-- U_WMR_PIE -->\n"; + U_POINT16 ArcStart, ArcEnd; + nSize = U_WMRPIE_get(contents, &ArcStart, &ArcEnd, &rc); + U_PAIRF center,start,end,size; + int f1; + int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1); + if(!wmr_arc_points(rc, ArcStart, ArcEnd, &f1, f2, ¢er, &start, &end, &size)){ + tmp_path << "\n\tM " << pix_to_xy(d, center.x, center.y); + tmp_path << "\n\tL " << pix_to_xy(d, start.x, start.y); + tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0; + tmp_path << " "; + tmp_path << 180.0 * current_rotation(d)/M_PI; + tmp_path << " "; + tmp_path << " " << f1 << "," << f2 << " "; + tmp_path << pix_to_xy(d, end.x, end.y) << " \n"; + tmp_path << " z "; + d->mask |= wmr_mask; + } + else { + dbg_str << "<!-- PIE record is invalid -->\n"; + } + break; + } + case U_WMR_RECTANGLE: + { + dbg_str << "<!-- U_WMR_RECTANGLE -->\n"; + + nSize = U_WMRRECTANGLE_get(contents, &rc); + U_sanerect16(rc, &left, &top, &right, &bottom); + + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n\tM " << pix_to_xy( d, left , top ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, right, top ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, right, bottom ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, left, bottom ) << " "; + tmp_rectangle << "\n\tz"; + + d->mask |= wmr_mask; + + tmp_path << tmp_rectangle.str().c_str(); + break; + } + case U_WMR_ROUNDRECT: + { + dbg_str << "<!-- U_WMR_ROUNDRECT -->\n"; + + int16_t Height,Width; + nSize = U_WMRROUNDRECT_get(contents, &Width, &Height, &rc); + U_sanerect16(rc, &left, &top, &right, &bottom); + double f = 4.*(sqrt(2) - 1)/3; + double f1 = 1.0 - f; + double cnx = Width/2; + double cny = Height/2; + + + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n" + << " M " + << pix_to_xy(d, left , top + cny ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, left , top + cny*f1 ) + << " " + << pix_to_xy(d, left + cnx*f1 , top ) + << " " + << pix_to_xy(d, left + cnx , top ) + << "\n"; + tmp_rectangle << " L " + << pix_to_xy(d, right - cnx , top ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, right - cnx*f1 , top ) + << " " + << pix_to_xy(d, right , top + cny*f1 ) + << " " + << pix_to_xy(d, right , top + cny ) + << "\n"; + tmp_rectangle << " L " + << pix_to_xy(d, right , bottom - cny ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, right , bottom - cny*f1 ) + << " " + << pix_to_xy(d, right - cnx*f1 , bottom ) + << " " + << pix_to_xy(d, right - cnx , bottom ) + << "\n"; + tmp_rectangle << " L " + << pix_to_xy(d, left + cnx , bottom ) + << "\n"; + tmp_rectangle << " C " + << pix_to_xy(d, left + cnx*f1 , bottom ) + << " " + << pix_to_xy(d, left , bottom - cny*f1 ) + << " " + << pix_to_xy(d, left , bottom - cny ) + << "\n"; + tmp_rectangle << " z\n"; + + + d->mask |= wmr_mask; + + tmp_path << tmp_rectangle.str().c_str(); + break; + } + case U_WMR_PATBLT: + { + dbg_str << "<!-- U_WMR_PATBLT -->\n"; + // Treat this like any other rectangle, ie, ignore the dwRop3 + nSize = U_WMRPATBLT_get(contents, &Dst, &cwh, &dwRop3); + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n\tM " << pix_to_xy( d, Dst.x , Dst.y ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, Dst.x + cwh.x, Dst.y ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, Dst.x + cwh.x, Dst.y + cwh.y ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, Dst.x, Dst.y + cwh.y ) << " "; + tmp_rectangle << "\n\tz"; + + d->mask |= wmr_mask; + + tmp_path << tmp_rectangle.str().c_str(); + break; + } + case U_WMR_SAVEDC: + { + dbg_str << "<!-- U_WMR_SAVEDC -->\n"; + + if (d->level < WMF_MAX_DC) { + d->dc[d->level + 1] = d->dc[d->level]; + if(d->dc[d->level].font_name){ + d->dc[d->level + 1].font_name = strdup(d->dc[d->level].font_name); // or memory access problems because font name pointer duplicated + } + d->level = d->level + 1; + } + break; + } + case U_WMR_SETPIXEL: dbg_str << "<!-- U_WMR_SETPIXEL -->\n"; break; + case U_WMR_OFFSETCLIPRGN: + { + dbg_str << "<!-- U_EMR_OFFSETCLIPRGN -->\n"; + U_POINT16 off; + nSize = U_WMROFFSETCLIPRGN_get(contents,&off); + if (d->dc[d->level].clip_id) { // can only offset an existing clipping path + unsigned int real_idx = d->dc[d->level].clip_id - 1; + Geom::PathVector tmp_vect = sp_svg_read_pathv(d->clips.strings[real_idx]); + double ox = pix_to_x_point(d, off.x, off.y) - pix_to_x_point(d, 0, 0); // take into account all active transforms + double oy = pix_to_y_point(d, off.x, off.y) - pix_to_y_point(d, 0, 0); + Geom::Affine tf = Geom::Translate(ox,oy); + tmp_vect *= tf; + char *tmp_path = sp_svg_write_path(tmp_vect); + add_clips(d, tmp_path, U_RGN_COPY); + free(tmp_path); + } + break; + } + // U_WMR_TEXTOUT should be here, but has been moved down to merge with U_WMR_EXTTEXTOUT + case U_WMR_BITBLT: + { + dbg_str << "<!-- U_WMR_BITBLT -->\n"; + nSize = U_WMRBITBLT_get(contents,&Dst,&cwh,&Src,&dwRop3,&Bm16,&px); + if(!px){ + if(dwRop3 == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */ + int32_t dx = Dst.x; + int32_t dy = Dst.y; + int32_t dw = cwh.x; + int32_t dh = cwh.y; + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " "; + tmp_rectangle << "\n\tz"; + + d->mask |= wmr_mask; + d->dwRop3 = dwRop3; // we will try to approximate SOME of these + d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that + + tmp_path << tmp_rectangle.str().c_str(); + } + else { /* Not done yet, Bm16 image present */ } + double dx = pix_to_x_point( d, Dst.x, Dst.y); + double dy = pix_to_y_point( d, Dst.x, Dst.y); + double dw = pix_to_abs_size( d, cwh.x); + double dh = pix_to_abs_size( d, cwh.y); + //source position within the bitmap, in pixels + int sx = Src.x; + int sy = Src.y; + int sw = 0; // extract all of the image + int sh = 0; + if(sx<0)sx=0; + if(sy<0)sy=0; + common_bm16_to_image(d,Bm16,px,dx,dy,dw,dh,sx,sy,sw,sh); + break; + } + case U_WMR_STRETCHBLT: + { + dbg_str << "<!-- U_WMR_STRETCHBLT -->\n"; + nSize = U_WMRSTRETCHBLT_get(contents,&Dst,&cDst,&Src,&cSrc,&dwRop3,&Bm16,&px); + if(!px){ + if(dwRop3 == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */ + int32_t dx = Dst.x; + int32_t dy = Dst.y; + int32_t dw = cDst.x; + int32_t dh = cDst.y; + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " "; + tmp_rectangle << "\n\tz"; + + d->mask |= wmr_mask; + d->dwRop3 = dwRop3; // we will try to approximate SOME of these + d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that + + tmp_path << tmp_rectangle.str().c_str(); + } + else { /* Not done yet, Bm16 image present */ } + double dx = pix_to_x_point( d, Dst.x, Dst.y); + double dy = pix_to_y_point( d, Dst.x, Dst.y); + double dw = pix_to_abs_size( d, cDst.x); + double dh = pix_to_abs_size( d, cDst.y); + //source position within the bitmap, in pixels + int sx = Src.x; + int sy = Src.y; + int sw = cSrc.x; // extract the specified amount of the image + int sh = cSrc.y; + if(sx<0)sx=0; + if(sy<0)sy=0; + common_bm16_to_image(d,Bm16,px,dx,dy,dw,dh,sx,sy,sw,sh); + break; + } + case U_WMR_POLYGON: + case U_WMR_POLYLINE: + { + dbg_str << "<!-- U_WMR_POLYGON/POLYLINE -->\n"; + nSize = U_WMRPOLYGON_get(contents, &cPts, &points); + uint32_t i; + + if (cPts < 2)break; + + d->mask |= wmr_mask; + memcpy(&pt16,points,U_SIZE_POINT16); points += U_SIZE_POINT16; + + tmp_str << "\n\tM " << pix_to_xy( d, pt16.x, pt16.y) << " "; + + for (i=1; i<cPts; i++) { + memcpy(&pt16,points,U_SIZE_POINT16); points+=U_SIZE_POINT16; + tmp_str << "\n\tL " << pix_to_xy( d, pt16.x, pt16.y) << " "; + } + + tmp_path << tmp_str.str().c_str(); + if(iType==U_WMR_POLYGON){ tmp_path << " z"; } + + break; + } + case U_WMR_ESCAPE: // only 3 types of escape are implemented + { + dbg_str << "<!-- U_WMR_ESCAPE -->\n"; + uint16_t Escape, elen; + nSize = U_WMRESCAPE_get(contents, &Escape, &elen, &text); + if(elen>=4){ + uint32_t utmp4; + memcpy(&utmp4, text ,4); + if(Escape == U_MFE_SETLINECAP){ + switch (utmp4 & U_PS_ENDCAP_MASK) { + case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND; break; } + case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; break; } + case U_PS_ENDCAP_FLAT: + default: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT; break; } + } + } + else if(Escape == U_MFE_SETLINEJOIN){ + switch (utmp4 & U_PS_JOIN_MASK) { + case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL; break; } + case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; break; } + case U_PS_JOIN_ROUND: + default: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND; break; } + } + } + else if(Escape == U_MFE_SETMITERLIMIT){ + //The function takes a float but uses a 32 bit int in the record. + float miterlimit = utmp4; + d->dc[d->level].style.stroke_miterlimit.value = miterlimit; //ratio, not a pt size + if (d->dc[d->level].style.stroke_miterlimit.value < 2) + d->dc[d->level].style.stroke_miterlimit.value = 2.0; + } + } + break; + } + case U_WMR_RESTOREDC: + { + dbg_str << "<!-- U_WMR_RESTOREDC -->\n"; + + int16_t DC; + nSize = U_WMRRESTOREDC_get(contents, &DC); + int old_level = d->level; + if (DC >= 0) { + if (DC < d->level) + d->level = DC; + } + else { + if (d->level + DC >= 0) + d->level = d->level + DC; + } + while (old_level > d->level) { + if (!d->dc[old_level].style.stroke_dasharray.values.empty() && + (old_level == 0 || (old_level > 0 && d->dc[old_level].style.stroke_dasharray != + d->dc[old_level - 1].style.stroke_dasharray))) { + d->dc[old_level].style.stroke_dasharray.values.clear(); + } + if(d->dc[old_level].font_name){ + free(d->dc[old_level].font_name); // else memory leak + d->dc[old_level].font_name = nullptr; + } + old_level--; + } + break; + } + case U_WMR_FILLREGION: dbg_str << "<!-- U_WMR_FILLREGION -->\n"; break; + case U_WMR_FRAMEREGION: dbg_str << "<!-- U_WMR_FRAMEREGION -->\n"; break; + case U_WMR_INVERTREGION: dbg_str << "<!-- U_WMR_INVERTREGION -->\n"; break; + case U_WMR_PAINTREGION: dbg_str << "<!-- U_WMR_PAINTREGION -->\n"; break; + case U_WMR_SELECTCLIPREGION: + { + dbg_str << "<!-- U_WMR_EXTSELECTCLIPRGN -->\n"; + nSize = U_WMRSELECTCLIPREGION_get(contents, &utmp16); + if (utmp16 == U_RGN_COPY) + clipset = false; + break; + } + case U_WMR_SELECTOBJECT: + { + dbg_str << "<!-- U_WMR_SELECTOBJECT -->\n"; + + nSize = U_WMRSELECTOBJECT_get(contents, &utmp16); + unsigned int index = utmp16; + + // WMF has no stock objects + if ( /*index >= 0 &&*/ index < (unsigned int) d->n_obj) { + switch (d->wmf_obj[index].type) + { + case U_WMR_CREATEPENINDIRECT: + select_pen(d, index); + break; + case U_WMR_CREATEBRUSHINDIRECT: + case U_WMR_DIBCREATEPATTERNBRUSH: + case U_WMR_CREATEPATTERNBRUSH: // <- this one did not display properly on XP, DIBCREATEPATTERNBRUSH works + select_brush(d, index); + break; + case U_WMR_CREATEFONTINDIRECT: + select_font(d, index); + break; + case U_WMR_CREATEPALETTE: + case U_WMR_CREATEBITMAPINDIRECT: + case U_WMR_CREATEBITMAP: + case U_WMR_CREATEREGION: + /* these do not do anything, but their objects must be kept in the count */ + break; + } + } + break; + } + case U_WMR_SETTEXTALIGN: + { + dbg_str << "<!-- U_WMR_SETTEXTALIGN -->\n"; + nSize = U_WMRSETTEXTALIGN_get(contents, &(d->dc[d->level].textAlign)); + break; + } + case U_WMR_DRAWTEXT: dbg_str << "<!-- U_WMR_DRAWTEXT -->\n"; break; + case U_WMR_CHORD: + { + dbg_str << "<!-- U_WMR_CHORD -->\n"; + U_POINT16 ArcStart, ArcEnd; + nSize = U_WMRCHORD_get(contents, &ArcStart, &ArcEnd, &rc); + U_PAIRF center,start,end,size; + int f1; + int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1); + if(!wmr_arc_points(rc, ArcStart, ArcEnd, &f1, f2, ¢er, &start, &end, &size)){ + tmp_path << "\n\tM " << pix_to_xy(d, start.x, start.y); + tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0 ; + tmp_path << " "; + tmp_path << 180.0 * current_rotation(d)/M_PI; + tmp_path << " "; + tmp_path << " " << f1 << "," << f2 << " "; + tmp_path << pix_to_xy(d, end.x, end.y) << " \n"; + tmp_path << " z "; + d->mask |= wmr_mask; + } + else { + dbg_str << "<!-- CHORD record is invalid -->\n"; + } + break; + } + case U_WMR_SETMAPPERFLAGS: dbg_str << "<!-- U_WMR_SETMAPPERFLAGS -->\n"; break; + case U_WMR_TEXTOUT: + case U_WMR_EXTTEXTOUT: + { + if(iType == U_WMR_TEXTOUT){ + dbg_str << "<!-- U_WMR_TEXTOUT -->\n"; + nSize = U_WMRTEXTOUT_get(contents, &Dst, &tlen, &text); + Opts=0; + } + else { + dbg_str << "<!-- U_WMR_EXTTEXTOUT -->\n"; + nSize = U_WMREXTTEXTOUT_get(contents, &Dst, &tlen, &Opts, &text, &dx, &rc ); + } + uint32_t fOptions = Opts; + + double x1,y1; + int cChars; + x1 = Dst.x; + y1 = Dst.y; + cChars = tlen; + + if (d->dc[d->level].textAlign & U_TA_UPDATECP) { + x1 = d->dc[d->level].cur.x; + y1 = d->dc[d->level].cur.y; + } + + double x = pix_to_x_point(d, x1, y1); + double y = pix_to_y_point(d, x1, y1); + + /* Rotation issues are handled entirely in libTERE now */ + + uint32_t *dup_wt = nullptr; + + dup_wt = U_Latin1ToUtf32le(text, cChars, nullptr); + if(!dup_wt)dup_wt = unknown_chars(cChars); + + msdepua(dup_wt); //convert everything in Microsoft's private use area. For Symbol, Wingdings, Dingbats + + if(NonToUnicode(dup_wt, d->dc[d->level].font_name)){ + free(d->dc[d->level].font_name); + d->dc[d->level].font_name = strdup("Times New Roman"); + } + + char *ansi_text; + ansi_text = (char *) U_Utf32leToUtf8((uint32_t *)dup_wt, 0, nullptr); + free(dup_wt); + // Empty text or starts with an invalid escape/control sequence, which is bogus text. Throw it out before g_markup_escape_text can make things worse + if(*((uint8_t *)ansi_text) <= 0x1F){ + free(ansi_text); + ansi_text=nullptr; + } + + if (ansi_text) { + + SVGOStringStream ts; + + gchar *escaped_text = g_markup_escape_text(ansi_text, -1); + + tsp.x = x*0.8; // TERE expects sizes in points + tsp.y = y*0.8; + tsp.color.Red = d->dc[d->level].textColor.Red; + tsp.color.Green = d->dc[d->level].textColor.Green; + tsp.color.Blue = d->dc[d->level].textColor.Blue; + tsp.color.Reserved = 0; + switch(d->dc[d->level].style.font_style.value){ + case SP_CSS_FONT_STYLE_OBLIQUE: + tsp.italics = FC_SLANT_OBLIQUE; break; + case SP_CSS_FONT_STYLE_ITALIC: + tsp.italics = FC_SLANT_ITALIC; break; + default: + case SP_CSS_FONT_STYLE_NORMAL: + tsp.italics = FC_SLANT_ROMAN; break; + } + switch(d->dc[d->level].style.font_weight.value){ + case SP_CSS_FONT_WEIGHT_100: tsp.weight = FC_WEIGHT_THIN ; break; + case SP_CSS_FONT_WEIGHT_200: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break; + case SP_CSS_FONT_WEIGHT_300: tsp.weight = FC_WEIGHT_LIGHT ; break; + case SP_CSS_FONT_WEIGHT_400: tsp.weight = FC_WEIGHT_NORMAL ; break; + case SP_CSS_FONT_WEIGHT_500: tsp.weight = FC_WEIGHT_MEDIUM ; break; + case SP_CSS_FONT_WEIGHT_600: tsp.weight = FC_WEIGHT_SEMIBOLD ; break; + case SP_CSS_FONT_WEIGHT_700: tsp.weight = FC_WEIGHT_BOLD ; break; + case SP_CSS_FONT_WEIGHT_800: tsp.weight = FC_WEIGHT_EXTRABOLD ; break; + case SP_CSS_FONT_WEIGHT_900: tsp.weight = FC_WEIGHT_HEAVY ; break; + case SP_CSS_FONT_WEIGHT_NORMAL: tsp.weight = FC_WEIGHT_NORMAL ; break; + case SP_CSS_FONT_WEIGHT_BOLD: tsp.weight = FC_WEIGHT_BOLD ; break; + case SP_CSS_FONT_WEIGHT_LIGHTER: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break; + case SP_CSS_FONT_WEIGHT_BOLDER: tsp.weight = FC_WEIGHT_EXTRABOLD ; break; + default: tsp.weight = FC_WEIGHT_NORMAL ; break; + } + // WMF only supports two types of text decoration + tsp.decoration = TXTDECOR_NONE; + if(d->dc[d->level].style.text_decoration_line.underline){ tsp.decoration |= TXTDECOR_UNDER; } + if(d->dc[d->level].style.text_decoration_line.line_through){ tsp.decoration |= TXTDECOR_STRIKE;} + + // WMF textalignment is a bit strange: 0x6 is center, 0x2 is right, 0x0 is left, the value 0x4 is also drawn left + tsp.taln = ((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_CENTER) ? ALICENTER : + (((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_LEFT) ? ALILEFT : + ALIRIGHT); + tsp.taln |= ((d->dc[d->level].textAlign & U_TA_BASEBIT) ? ALIBASE : + ((d->dc[d->level].textAlign & U_TA_BOTTOM) ? ALIBOT : + ALITOP)); + + // language direction can be encoded two ways, U_TA_RTLREADING is preferred + if( (fOptions & U_ETO_RTLREADING) || (d->dc[d->level].textAlign & U_TA_RTLREADING) ){ tsp.ldir = LDIR_RL; } + else{ tsp.ldir = LDIR_LR; } + + tsp.condensed = FC_WIDTH_NORMAL; // Not implemented well in libTERE (yet) + tsp.ori = d->dc[d->level].style.baseline_shift.value; // For now orientation is always the same as escapement + // There is no world transform, so ori need not be further rotated + tsp.string = (uint8_t *) U_strdup(escaped_text); // this will be free'd much later at a trinfo_clear(). + tsp.fs = d->dc[d->level].style.font_size.computed * 0.8; // Font size in points + char *fontspec = TR_construct_fontspec(&tsp, d->dc[d->level].font_name); + tsp.fi_idx = ftinfo_load_fontname(d->tri->fti,fontspec); + free(fontspec); + // when font name includes narrow it may not be set to "condensed". Narrow fonts do not work well anyway though + // as the metrics from fontconfig may not match, or the font may not be present. + if(0<= TR_findcasesub(d->dc[d->level].font_name, (char *) "Narrow")){ tsp.co=1; } + else { tsp.co=0; } + + int status; + + status = trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ori is actually escapement + if(status==-1){ // change of escapement, emit what we have and reset + TR_layout_analyze(d->tri); + if (d->dc[d->level].clip_id){ + SVGOStringStream tmp_clip; + tmp_clip << "\n<g\n\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n>"; + d->outsvg += tmp_clip.str().c_str(); + } + TR_layout_2_svg(d->tri); + ts << d->tri->out; + d->outsvg += ts.str().c_str(); + d->tri = trinfo_clear(d->tri); + (void) trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ignore return status, it must work + if (d->dc[d->level].clip_id){ + d->outsvg += "\n</g>\n"; + } + } + + g_free(escaped_text); + free(ansi_text); + } + + break; + } + case U_WMR_SETDIBTODEV: dbg_str << "<!-- U_WMR_EXTTEXTOUT -->\n"; break; + case U_WMR_SELECTPALETTE: dbg_str << "<!-- U_WMR_SELECTPALETTE -->\n"; break; + case U_WMR_REALIZEPALETTE: dbg_str << "<!-- U_WMR_REALIZEPALETTE -->\n"; break; + case U_WMR_ANIMATEPALETTE: dbg_str << "<!-- U_WMR_ANIMATEPALETTE -->\n"; break; + case U_WMR_SETPALENTRIES: dbg_str << "<!-- U_WMR_SETPALENTRIES -->\n"; break; + case U_WMR_POLYPOLYGON: + { + dbg_str << "<!-- U_WMR_POLYPOLYGON16 -->\n"; + uint16_t nPolys; + const uint16_t *aPolyCounts; + const char *Points; + int cpts; /* total number of points in Points*/ + nSize = U_WMRPOLYPOLYGON_get(contents, &nPolys, &aPolyCounts, &Points); + int n, i, j; + + d->mask |= wmr_mask; + + U_POINT16 apt; + for (n=cpts=0; n < nPolys; n++) { cpts += aPolyCounts[n]; } + i = 0; // offset in BYTES + cpts *= U_SIZE_POINT16; // limit for offset i, in BYTES + + for (n=0; n < nPolys && i<cpts; n++) { + SVGOStringStream poly_path; + + memcpy(&apt, Points + i, U_SIZE_POINT16); // points may not be aligned, copy them this way + + poly_path << "\n\tM " << pix_to_xy( d, apt.x, apt.y) << " "; + i += U_SIZE_POINT16; + + for (j=1; j < aPolyCounts[n] && i < cpts; j++) { + memcpy(&apt, Points + i, U_SIZE_POINT16); // points may not be aligned, copy them this way + poly_path << "\n\tL " << pix_to_xy( d, apt.x, apt.y) << " "; + i += U_SIZE_POINT16; + } + + tmp_str << poly_path.str().c_str(); + tmp_str << " z"; + tmp_str << " \n"; + } + + tmp_path << tmp_str.str().c_str(); + + break; + } + case U_WMR_RESIZEPALETTE: dbg_str << "<!-- U_WMR_RESIZEPALETTE -->\n"; break; + case U_WMR_3A: + case U_WMR_3B: + case U_WMR_3C: + case U_WMR_3D: + case U_WMR_3E: + case U_WMR_3F: + { + dbg_str << "<!-- U_WMR_3A..3F -->\n"; + break; + } + case U_WMR_DIBBITBLT: + { + dbg_str << "<!-- U_WMR_DIBBITBLT -->\n"; + nSize = U_WMRDIBBITBLT_get(contents, &Dst, &cwh, &Src, &dwRop3, &dib); + + // Treat all nonImage bitblts as a rectangular write. Definitely not correct, but at + // least it leaves objects where the operations should have been. + if (!dib) { + // should be an application of a DIBPATTERNBRUSHPT, use a solid color instead + + if(dwRop3 == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */ + int32_t dx = Dst.x; + int32_t dy = Dst.y; + int32_t dw = cwh.x; + int32_t dh = cwh.y; + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " "; + tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " "; + tmp_rectangle << "\n\tz"; + + d->mask |= wmr_mask; + d->dwRop3 = dwRop3; // we will try to approximate SOME of these + d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that + + tmp_path << tmp_rectangle.str().c_str(); + } + else { + double dx = pix_to_x_point( d, Dst.x, Dst.y); + double dy = pix_to_y_point( d, Dst.x, Dst.y); + double dw = pix_to_abs_size( d, cDst.x); + double dh = pix_to_abs_size( d, cDst.y); + //source position within the bitmap, in pixels + int sx = Src.x; + int sy = Src.y; + int sw = 0; // extract all of the image + int sh = 0; + if(sx<0)sx=0; + if(sy<0)sy=0; + // usageSrc not defined, implicitly it must be U_DIB_RGB_COLORS + common_dib_to_image(d,dib,dx,dy,dw,dh,sx,sy,sw,sh,U_DIB_RGB_COLORS); + } + break; + } + case U_WMR_DIBSTRETCHBLT: + { + dbg_str << "<!-- U_WMR_DIBSTRETCHBLT -->\n"; + nSize = U_WMRDIBSTRETCHBLT_get(contents, &Dst, &cDst, &Src, &cSrc, &dwRop3, &dib); + // Always grab image, ignore modes. + if (dib) { + double dx = pix_to_x_point( d, Dst.x, Dst.y); + double dy = pix_to_y_point( d, Dst.x, Dst.y); + double dw = pix_to_abs_size( d, cDst.x); + double dh = pix_to_abs_size( d, cDst.y); + //source position within the bitmap, in pixels + int sx = Src.x; + int sy = Src.y; + int sw = cSrc.x; // extract the specified amount of the image + int sh = cSrc.y; + // usageSrc not defined, implicitly it must be U_DIB_RGB_COLORS + common_dib_to_image(d,dib,dx,dy,dw,dh,sx,sy,sw,sh, U_DIB_RGB_COLORS); + } + break; + } + case U_WMR_DIBCREATEPATTERNBRUSH: + { + dbg_str << "<!-- U_WMR_DIBCREATEPATTERNBRUSH -->\n"; + insert_object(d, U_WMR_DIBCREATEPATTERNBRUSH, contents); + break; + } + case U_WMR_STRETCHDIB: + { + dbg_str << "<!-- U_WMR_STRETCHDIB -->\n"; + nSize = U_WMRSTRETCHDIB_get(contents, &Dst, &cDst, &Src, &cSrc, &cUsage, &dwRop3, &dib); + double dx = pix_to_x_point( d, Dst.x, Dst.y ); + double dy = pix_to_y_point( d, Dst.x, Dst.y ); + double dw = pix_to_abs_size( d, cDst.x); + double dh = pix_to_abs_size( d, cDst.y); + int sx = Src.x; //source position within the bitmap, in pixels + int sy = Src.y; + int sw = cSrc.x; // extract the specified amount of the image + int sh = cSrc.y; + uint32_t iUsageSrc; + iUsageSrc = cUsage; + common_dib_to_image(d,dib,dx,dy,dw,dh,sx,sy,sw,sh,iUsageSrc); + + break; + } + case U_WMR_44: + case U_WMR_45: + case U_WMR_46: + case U_WMR_47: + { + dbg_str << "<!-- U_WMR_44..47 -->\n"; + break; + } + case U_WMR_EXTFLOODFILL: dbg_str << "<!-- U_WMR_EXTFLOODFILL -->\n"; break; + case U_WMR_49: + case U_WMR_4A: + case U_WMR_4B: + case U_WMR_4C: + case U_WMR_4D: + case U_WMR_4E: + case U_WMR_4F: + case U_WMR_50: + case U_WMR_51: + case U_WMR_52: + case U_WMR_53: + case U_WMR_54: + case U_WMR_55: + case U_WMR_56: + case U_WMR_57: + case U_WMR_58: + case U_WMR_59: + case U_WMR_5A: + case U_WMR_5B: + case U_WMR_5C: + case U_WMR_5D: + case U_WMR_5E: + case U_WMR_5F: + case U_WMR_60: + case U_WMR_61: + case U_WMR_62: + case U_WMR_63: + case U_WMR_64: + case U_WMR_65: + case U_WMR_66: + case U_WMR_67: + case U_WMR_68: + case U_WMR_69: + case U_WMR_6A: + case U_WMR_6B: + case U_WMR_6C: + case U_WMR_6D: + case U_WMR_6E: + case U_WMR_6F: + case U_WMR_70: + case U_WMR_71: + case U_WMR_72: + case U_WMR_73: + case U_WMR_74: + case U_WMR_75: + case U_WMR_76: + case U_WMR_77: + case U_WMR_78: + case U_WMR_79: + case U_WMR_7A: + case U_WMR_7B: + case U_WMR_7C: + case U_WMR_7D: + case U_WMR_7E: + case U_WMR_7F: + case U_WMR_80: + case U_WMR_81: + case U_WMR_82: + case U_WMR_83: + case U_WMR_84: + case U_WMR_85: + case U_WMR_86: + case U_WMR_87: + case U_WMR_88: + case U_WMR_89: + case U_WMR_8A: + case U_WMR_8B: + case U_WMR_8C: + case U_WMR_8D: + case U_WMR_8E: + case U_WMR_8F: + case U_WMR_90: + case U_WMR_91: + case U_WMR_92: + case U_WMR_93: + case U_WMR_94: + case U_WMR_95: + case U_WMR_96: + case U_WMR_97: + case U_WMR_98: + case U_WMR_99: + case U_WMR_9A: + case U_WMR_9B: + case U_WMR_9C: + case U_WMR_9D: + case U_WMR_9E: + case U_WMR_9F: + case U_WMR_A0: + case U_WMR_A1: + case U_WMR_A2: + case U_WMR_A3: + case U_WMR_A4: + case U_WMR_A5: + case U_WMR_A6: + case U_WMR_A7: + case U_WMR_A8: + case U_WMR_A9: + case U_WMR_AA: + case U_WMR_AB: + case U_WMR_AC: + case U_WMR_AD: + case U_WMR_AE: + case U_WMR_AF: + case U_WMR_B0: + case U_WMR_B1: + case U_WMR_B2: + case U_WMR_B3: + case U_WMR_B4: + case U_WMR_B5: + case U_WMR_B6: + case U_WMR_B7: + case U_WMR_B8: + case U_WMR_B9: + case U_WMR_BA: + case U_WMR_BB: + case U_WMR_BC: + case U_WMR_BD: + case U_WMR_BE: + case U_WMR_BF: + case U_WMR_C0: + case U_WMR_C1: + case U_WMR_C2: + case U_WMR_C3: + case U_WMR_C4: + case U_WMR_C5: + case U_WMR_C6: + case U_WMR_C7: + case U_WMR_C8: + case U_WMR_C9: + case U_WMR_CA: + case U_WMR_CB: + case U_WMR_CC: + case U_WMR_CD: + case U_WMR_CE: + case U_WMR_CF: + case U_WMR_D0: + case U_WMR_D1: + case U_WMR_D2: + case U_WMR_D3: + case U_WMR_D4: + case U_WMR_D5: + case U_WMR_D6: + case U_WMR_D7: + case U_WMR_D8: + case U_WMR_D9: + case U_WMR_DA: + case U_WMR_DB: + case U_WMR_DC: + case U_WMR_DD: + case U_WMR_DE: + case U_WMR_DF: + case U_WMR_E0: + case U_WMR_E1: + case U_WMR_E2: + case U_WMR_E3: + case U_WMR_E4: + case U_WMR_E5: + case U_WMR_E6: + case U_WMR_E7: + case U_WMR_E8: + case U_WMR_E9: + case U_WMR_EA: + case U_WMR_EB: + case U_WMR_EC: + case U_WMR_ED: + case U_WMR_EE: + case U_WMR_EF: + { + dbg_str << "<!-- U_WMR_EXTFLOODFILL..EF -->\n"; + break; + } + case U_WMR_DELETEOBJECT: + { + dbg_str << "<!-- U_WMR_DELETEOBJECT -->\n"; + nSize = U_WMRDELETEOBJECT_get(contents, &utmp16); + delete_object(d, utmp16); + break; + } + case U_WMR_F1: + case U_WMR_F2: + case U_WMR_F3: + case U_WMR_F4: + case U_WMR_F5: + case U_WMR_F6: + { + dbg_str << "<!-- F1..F6 -->\n"; + break; + } + case U_WMR_CREATEPALETTE: + { + dbg_str << "<!-- U_WMR_CREATEPALETTE -->\n"; + insert_object(d, U_WMR_CREATEPALETTE, contents); + break; + } + case U_WMR_F8: dbg_str << "<!-- F8 -->\n"; break; + case U_WMR_CREATEPATTERNBRUSH: + { + dbg_str << "<!-- U_WMR_CREATEPATTERNBRUSH -->\n"; + insert_object(d, U_WMR_CREATEPATTERNBRUSH, contents); + break; + } + case U_WMR_CREATEPENINDIRECT: + { + dbg_str << "<!-- U_WMR_EXTCREATEPEN -->\n"; + insert_object(d, U_WMR_CREATEPENINDIRECT, contents); + break; + } + case U_WMR_CREATEFONTINDIRECT: + { + dbg_str << "<!-- U_WMR_CREATEFONTINDIRECT -->\n"; + insert_object(d, U_WMR_CREATEFONTINDIRECT, contents); + break; + } + case U_WMR_CREATEBRUSHINDIRECT: + { + dbg_str << "<!-- U_WMR_CREATEBRUSHINDIRECT -->\n"; + insert_object(d, U_WMR_CREATEBRUSHINDIRECT, contents); + break; + } + case U_WMR_CREATEBITMAPINDIRECT: + { + dbg_str << "<!-- U_WMR_CREATEBITMAPINDIRECT -->\n"; + insert_object(d, U_WMR_CREATEBITMAPINDIRECT, contents); + break; + } + case U_WMR_CREATEBITMAP: + { + dbg_str << "<!-- U_WMR_CREATEBITMAP -->\n"; + insert_object(d, U_WMR_CREATEBITMAP, contents); + break; + } + case U_WMR_CREATEREGION: + { + dbg_str << "<!-- U_WMR_CREATEREGION -->\n"; + insert_object(d, U_WMR_CREATEREGION, contents); + break; + } + default: + dbg_str << "<!-- U_WMR_??? -->\n"; + break; + } //end of switch +// At run time define environment variable INKSCAPE_DBG_WMF to include string COMMENT. +// Users may employ this to to place a comment for each processed WMR record in the SVG + if(wDbgComment){ + d->outsvg += dbg_str.str().c_str(); + } + d->path += tmp_path.str().c_str(); + if(!nSize){ // There was some problem with the processing of this record, it is not safe to continue + file_status = 0; + break; + } + + } //end of while on OK +// At run time define environment variable INKSCAPE_DBG_WMF to include string FINAL +// Users may employ this to to show the final SVG derived from the WMF + if(wDbgFinal){ + std::cout << d->outsvg << std::endl; + } + (void) U_wmr_properties(U_WMR_INVALID); // force the release of the lookup table memory, returned value is irrelevant + + return(file_status); +} + +void Wmf::free_wmf_strings(WMF_STRINGS name){ + if(name.count){ + for(int i=0; i< name.count; i++){ free(name.strings[i]); } + free(name.strings); + } + name.count = 0; + name.size = 0; +} + +SPDocument * +Wmf::open( Inkscape::Extension::Input * /*mod*/, const gchar *uri ) +{ + + if (uri == nullptr) { + return nullptr; + } + + // ensure usage of dot as decimal separator in scanf/printf functions (indepentendly of current locale) + char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr)); + setlocale(LC_NUMERIC, "C"); + + WMF_CALLBACK_DATA d; + + d.n_obj = 0; //these might not be set otherwise if the input file is corrupt + d.wmf_obj=nullptr; + + // Default font, WMF spec says device can pick whatever it wants. + // WMF files that do not specify a font are unlikely to look very good! + d.dc[0].style.font_size.computed = 16.0; + d.dc[0].style.font_weight.value = SP_CSS_FONT_WEIGHT_400; + d.dc[0].style.font_style.value = SP_CSS_FONT_STYLE_NORMAL; + d.dc[0].style.text_decoration_line.underline = false; + d.dc[0].style.text_decoration_line.line_through = false; + d.dc[0].style.baseline_shift.value = 0; + + // Default pen, WMF files that do not specify a pen are unlikely to look very good! + d.dc[0].style.stroke_dasharray.set = false; + d.dc[0].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; // U_PS_ENDCAP_SQUARE; + d.dc[0].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; // U_PS_JOIN_MITER; + d.dc[0].style.stroke_width.value = 1.0; // will be reset to something reasonable once WMF drawing size is known + d.dc[0].style.stroke.value.color.set( 0, 0, 0 ); + d.dc[0].stroke_set = true; + + // Default brush is none - no fill. WMF files that do not specify a brush are unlikely to look very good! + d.dc[0].fill_set = false; + + d.dc[0].font_name = strdup("Arial"); // Default font, set only on lowest level, it copies up from there WMF spec says device can pick whatever it wants + + // set up the size default for patterns in defs. This might not be referenced if there are no patterns defined in the drawing. + + d.defs += "\n"; + d.defs += " <pattern id=\"WMFhbasepattern\" \n"; + d.defs += " patternUnits=\"userSpaceOnUse\"\n"; + d.defs += " width=\"6\" \n"; + d.defs += " height=\"6\" \n"; + d.defs += " x=\"0\" \n"; + d.defs += " y=\"0\"> \n"; + d.defs += " </pattern> \n"; + + + size_t length; + char *contents; + if(wmf_readdata(uri, &contents, &length))return(nullptr); + + // set up the text reassembly system + if(!(d.tri = trinfo_init(nullptr)))return(nullptr); + (void) trinfo_load_ft_opts(d.tri, 1, + FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, + FT_KERNING_UNSCALED); + + int good = myMetaFileProc(contents,length, &d); + free(contents); + +// std::cout << "SVG Output: " << std::endl << d.outsvg << std::endl; + + SPDocument *doc = nullptr; + if (good) { + doc = SPDocument::createNewDocFromMem(d.outsvg.c_str(), strlen(d.outsvg.c_str()), TRUE); + } + + free_wmf_strings(d.hatches); + free_wmf_strings(d.images); + free_wmf_strings(d.clips); + + if (d.wmf_obj) { + int i; + for (i=0; i<d.n_obj; i++) + delete_object(&d, i); + delete[] d.wmf_obj; + } + + d.dc[0].style.stroke_dasharray.values.clear(); + + for(int i=0; i<=WMF_MAX_DC; i++){ + if(d.dc[i].font_name)free(d.dc[i].font_name); + } + + d.tri = trinfo_release_except_FC(d.tri); + + // in earlier versions no viewbox was generated and a call to setViewBoxIfMissing() was needed here. + + // restore decimal separator used in scanf/printf functions to initial value + setlocale(LC_NUMERIC, oldlocale); + g_free(oldlocale); + + return doc; +} + + +void +Wmf::init () +{ + /* WMF in */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("WMF Input") "</name>\n" + "<id>org.inkscape.input.wmf</id>\n" + "<input>\n" + "<extension>.wmf</extension>\n" + "<mimetype>image/x-wmf</mimetype>\n" + "<filetypename>" N_("Windows Metafiles (*.wmf)") "</filetypename>\n" + "<filetypetooltip>" N_("Windows Metafiles") "</filetypetooltip>\n" + "<output_extension>org.inkscape.output.wmf</output_extension>\n" + "</input>\n" + "</inkscape-extension>", new Wmf()); + + /* WMF out */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("WMF Output") "</name>\n" + "<id>org.inkscape.output.wmf</id>\n" + "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"bool\">true</param>\n" + "<param name=\"TnrToSymbol\" gui-text=\"" N_("Map Unicode to Symbol font") "\" type=\"bool\">true</param>\n" + "<param name=\"TnrToWingdings\" gui-text=\"" N_("Map Unicode to Wingdings") "\" type=\"bool\">true</param>\n" + "<param name=\"TnrToZapfDingbats\" gui-text=\"" N_("Map Unicode to Zapf Dingbats") "\" type=\"bool\">true</param>\n" + "<param name=\"UsePUA\" gui-text=\"" N_("Use MS Unicode PUA (0xF020-0xF0FF) for converted characters") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTCharPos\" gui-text=\"" N_("Compensate for PPT font bug") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTDashLine\" gui-text=\"" N_("Convert dashed/dotted lines to single lines") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTGrad2Polys\" gui-text=\"" N_("Convert gradients to colored polygon series") "\" type=\"bool\">false</param>\n" + "<param name=\"FixPPTPatternAsHatch\" gui-text=\"" N_("Map all fill patterns to standard WMF hatches") "\" type=\"bool\">false</param>\n" + "<output>\n" + "<extension>.wmf</extension>\n" + "<mimetype>image/x-wmf</mimetype>\n" + "<filetypename>" N_("Windows Metafile (*.wmf)") "</filetypename>\n" + "<filetypetooltip>" N_("Windows Metafile") "</filetypetooltip>\n" + "</output>\n" + "</inkscape-extension>", new Wmf()); + + return; +} + + +} } } /* namespace Inkscape, Extension, Implementation */ + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/wmf-inout.h b/src/extension/internal/wmf-inout.h new file mode 100644 index 0000000..6190129 --- /dev/null +++ b/src/extension/internal/wmf-inout.h @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Windows Metafile Input/Output + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_EXTENSION_INTERNAL_WMF_H +#define SEEN_EXTENSION_INTERNAL_WMF_H + +#include <3rdparty/libuemf/uwmf.h> +#include "extension/internal/metafile-inout.h" // picks up PNG +#include "extension/implementation/implementation.h" +#include "style.h" +#include "text_reassemble.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +#define DIRTY_NONE 0x00 +#define DIRTY_TEXT 0x01 +#define DIRTY_FILL 0x02 +#define DIRTY_STROKE 0x04 // not used currently + +struct WMF_OBJECT { + int type = 0; + int level = 0; + char *record = nullptr; +}; +using PWMF_OBJECT = WMF_OBJECT *; + +struct WMF_STRINGS { + int size = 0; // number of slots allocated in strings + int count = 0; // number of slots used in strings + char **strings = nullptr; // place to store strings +}; +using PWMF_STRINGS = WMF_STRINGS *; + +struct WMF_DEVICE_CONTEXT { + WMF_DEVICE_CONTEXT() : + // SPStyle: class with constructor + font_name(nullptr), + clip_id(0), + stroke_set(false), stroke_mode(0), stroke_idx(0), stroke_recidx(0), + fill_set(false), fill_mode(0), fill_idx(0), fill_recidx(0), + dirty(0), + active_pen(-1), active_brush(-1), active_font(-1), // -1 when the default is used + // sizeWnd, sizeView, winorg, vieworg, + ScaleInX(0), ScaleInY(0), + ScaleOutX(0), ScaleOutY(0), + bkMode(U_TRANSPARENT), + // bkColor, textColor + textAlign(0) + // worldTransform, cur + { + font_name = nullptr; + sizeWnd = point16_set( 0.0, 0.0 ); + sizeView = point16_set( 0.0, 0.0 ); + winorg = point16_set( 0.0, 0.0 ); + vieworg = point16_set( 0.0, 0.0 ); + bkColor = U_RGB(255, 255, 255); // default foreground color (white) + textColor = U_RGB(0, 0, 0); // default foreground color (black) + cur = point16_set( 0.0, 0.0 ); + }; + SPStyle style; + char *font_name; + int clip_id; // 0 if none, else 1 + index into clips + bool stroke_set; + int stroke_mode; // enumeration from drawmode, not used if fill_set is not True + int stroke_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill + int stroke_recidx;// record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change + bool fill_set; + int fill_mode; // enumeration from drawmode, not used if fill_set is not True + int fill_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill + int fill_recidx; // record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change + int dirty; // holds the dirty bits for text, stroke, fill + int active_pen; // used when the active object is deleted to set the default values, -1 is none active + int active_brush; // ditto + int active_font; // ditto. also used to hold object number in case font needs to be remade due to textcolor change. + U_POINT16 sizeWnd; + U_POINT16 sizeView; + U_POINT16 winorg; + U_POINT16 vieworg; + double ScaleInX, ScaleInY; + double ScaleOutX, ScaleOutY; + uint16_t bkMode; + U_COLORREF bkColor; + U_COLORREF textColor; + uint16_t textAlign; + U_POINT16 cur; +}; +using PWMF_DEVICE_CONTEXT = WMF_DEVICE_CONTEXT *; + +#define WMF_MAX_DC 128 + + +// like this causes a mysterious crash on the return from Wmf::open +//typedef struct emf_callback_data { +// this fixes it, so some confusion between this struct and the one in emf-inout??? +//typedef struct wmf_callback_data { +// as does this +struct WMF_CALLBACK_DATA { + + WMF_CALLBACK_DATA() : + // dc: array, structure w/ constructor + level(0), + E2IdirY(1.0), + D2PscaleX(1.0), D2PscaleY(1.0), + PixelsInX(0), PixelsInY(0), + PixelsOutX(0), PixelsOutY(0), + ulCornerInX(0), ulCornerInY(0), + ulCornerOutX(0), ulCornerOutY(0), + mask(0), + arcdir(U_AD_COUNTERCLOCKWISE), + dwRop2(U_R2_COPYPEN), dwRop3(0), + id(0), drawtype(0), + // hatches, images, gradients, struct w/ constructor + tri(nullptr), + n_obj(0), + low_water(0) + //wmf_obj + {}; + + Glib::ustring outsvg; + Glib::ustring path; + Glib::ustring outdef; + Glib::ustring defs; + + WMF_DEVICE_CONTEXT dc[WMF_MAX_DC+1]; // FIXME: This should be dynamic.. + int level; + + double E2IdirY; // WMF Y direction relative to Inkscape Y direction. Will be negative for MM_LOMETRIC etc. + double D2PscaleX,D2PscaleY; // WMF device to Inkscape Page scale. + float PixelsInX, PixelsInY; // size of the drawing, in WMF device pixels + float PixelsOutX, PixelsOutY; // size of the drawing, in Inkscape pixels + double ulCornerInX,ulCornerInY; // Upper left corner, from header rclBounds, in logical units + double ulCornerOutX,ulCornerOutY; // Upper left corner, in Inkscape pixels + uint32_t mask; // Draw properties + int arcdir; // U_AD_COUNTERCLOCKWISE 1 or U_AD_CLOCKWISE 2 + + uint32_t dwRop2; // Binary raster operation, 0 if none (use brush/pen unmolested) + uint32_t dwRop3; // Ternary raster operation, 0 if none (use brush/pen unmolested) + + unsigned int id; + unsigned int drawtype; // one of 0 or U_WMR_FILLPATH, U_WMR_STROKEPATH, U_WMR_STROKEANDFILLPATH + // both of these end up in <defs> under the names shown here. These structures allow duplicates to be avoided. + WMF_STRINGS hatches; // hold pattern names, all like WMFhatch#_$$$$$$ where # is the WMF hatch code and $$$$$$ is the color + WMF_STRINGS images; // hold images, all like Image#, where # is the slot the image lives. + WMF_STRINGS clips; // hold clipping paths, referred to be the slot where the clipping path lives + TR_INFO *tri; // Text Reassembly data structure + + + int n_obj; + int low_water; // first object slot which _might_ be unoccupied. Everything below is filled. + PWMF_OBJECT wmf_obj; +}; +using PWMF_CALLBACK_DATA = WMF_CALLBACK_DATA *; + +class Wmf : public Metafile +{ + +public: + Wmf(); // Empty constructor + + ~Wmf() override;//Destructor + + bool check(Inkscape::Extension::Extension *module) override; //Can this module load (always yes for now) + + void save(Inkscape::Extension::Output *mod, // Save the given document to the given filename + SPDocument *doc, + gchar const *filename) override; + + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ) override; + + static void init();//Initialize the class + +private: +protected: + static void print_document_to_file(SPDocument *doc, const gchar *filename); + static double current_scale(PWMF_CALLBACK_DATA d); + static std::string current_matrix(PWMF_CALLBACK_DATA d, double x, double y, int useoffset); + static double current_rotation(PWMF_CALLBACK_DATA d); + static void enlarge_hatches(PWMF_CALLBACK_DATA d); + static int in_hatches(PWMF_CALLBACK_DATA d, char *test); + static uint32_t add_hatch(PWMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor); + static void enlarge_images(PWMF_CALLBACK_DATA d); + static int in_images(PWMF_CALLBACK_DATA d, char *test); + static uint32_t add_dib_image(PWMF_CALLBACK_DATA d, const char *dib, uint32_t iUsage); + static uint32_t add_bm16_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px); + + static void enlarge_clips(PWMF_CALLBACK_DATA d); + static int in_clips(PWMF_CALLBACK_DATA d, const char *test); + static void add_clips(PWMF_CALLBACK_DATA d, const char *clippath, unsigned int logic); + + static void output_style(PWMF_CALLBACK_DATA d); + static double _pix_x_to_point(PWMF_CALLBACK_DATA d, double px); + static double _pix_y_to_point(PWMF_CALLBACK_DATA d, double py); + static double pix_to_x_point(PWMF_CALLBACK_DATA d, double px, double py); + static double pix_to_y_point(PWMF_CALLBACK_DATA d, double px, double py); + static double pix_to_abs_size(PWMF_CALLBACK_DATA d, double px); + static std::string pix_to_xy(PWMF_CALLBACK_DATA d, double x, double y); + static void select_brush(PWMF_CALLBACK_DATA d, int index); + static void select_font(PWMF_CALLBACK_DATA d, int index); + static void select_pen(PWMF_CALLBACK_DATA d, int index); + static int insertable_object(PWMF_CALLBACK_DATA d); + static void delete_object(PWMF_CALLBACK_DATA d, int index); + static int insert_object(PWMF_CALLBACK_DATA d, int type, const char *record); + static uint32_t *unknown_chars(size_t count); + static void common_dib_to_image(PWMF_CALLBACK_DATA d, const char *dib, + double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh, uint32_t iUsage); + static void common_bm16_to_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px, + double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh); + static int myMetaFileProc(const char *contents, unsigned int length, PWMF_CALLBACK_DATA d); + static void free_wmf_strings(WMF_STRINGS name); + +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + + +#endif /* EXTENSION_INTERNAL_WMF_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/extension/internal/wmf-print.cpp b/src/extension/internal/wmf-print.cpp new file mode 100644 index 0000000..7d2bbbb --- /dev/null +++ b/src/extension/internal/wmf-print.cpp @@ -0,0 +1,1612 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Windows Metafile printing + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * David Mathog + * + * Copyright (C) 2006-2009 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +/* + * References: + * - How to Create & Play Enhanced Metafiles in Win32 + * http://support.microsoft.com/kb/q145999/ + * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles + * http://support.microsoft.com/kb/q66949/ + * - Metafile Functions + * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp + * - Metafile Structures + * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp + */ + +#include <2geom/sbasis-to-bezier.h> +#include <2geom/elliptical-arc.h> + +#include <2geom/path.h> +#include <2geom/pathvector.h> +#include <2geom/rect.h> +#include <2geom/curves.h> +#include "helper/geom.h" +#include "helper/geom-curves.h" + +#include "inkscape-version.h" + +#include "util/units.h" + +#include "extension/system.h" +#include "extension/print.h" +#include "document.h" +#include "path-prefix.h" + +#include "object/sp-pattern.h" +#include "object/sp-image.h" +#include "object/sp-gradient.h" +#include "object/sp-radial-gradient.h" +#include "object/sp-linear-gradient.h" +#include "object/sp-root.h" +#include "object/sp-item.h" + +#include "splivarot.h" // pieces for union on shapes +#include <2geom/svg-path-parser.h> // to get from SVG text to Geom::Path +#include "display/canvas-bpath.h" // for SPWindRule +#include "display/cairo-utils.h" // for Inkscape::Pixbuf::PF_CAIRO + +#include "wmf-print.h" + +#include <cstring> +#include <3rdparty/libuemf/symbol_convert.h> + +namespace Inkscape { +namespace Extension { +namespace Internal { + +#define PXPERMETER 2835 +#define MAXDISP 2.0 // This should be set in the output dialog. This is ok for experimenting, no more than 2 pixel deviation. Not actually used at present + + +/* globals */ +static double PX2WORLD; // value set in begin() +static bool FixPPTCharPos, FixPPTDashLine, FixPPTGrad2Polys, FixPPTPatternAsHatch; +static WMFTRACK *wt = nullptr; +static WMFHANDLES *wht = nullptr; + +void PrintWmf::smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *rtl, int *ndx, float scale) +{ + float fdx; + int i; + int16_t *ladx; + const char *cptr = &string[strlen(string) + 1]; // this works because of the first fake terminator + + *adx = nullptr; + *ky = 0.0; // set a default value + sscanf(cptr, "%7d", ndx); + if (!*ndx) { + return; // this could happen with an empty string + } + cptr += 7; + ladx = (int16_t *) malloc(*ndx * sizeof(int16_t)); + if (!ladx) { + g_error("Out of memory"); + } + *adx = ladx; + for (i = 0; i < *ndx; i++, cptr += 7, ladx++) { + sscanf(cptr, "%7f", &fdx); + *ladx = (int16_t) round(fdx * scale); + } + cptr++; // skip 2nd fake terminator + sscanf(cptr, "%7f", &fdx); + *ky = fdx; + cptr += 7; // advance over ky and its space + sscanf(cptr, "%07d", rtl); +} + +PrintWmf::PrintWmf() +{ + // all of the class variables are initialized elsewhere, many in PrintWmf::Begin, +} + + +unsigned int PrintWmf::setup(Inkscape::Extension::Print * /*mod*/) +{ + return TRUE; +} + + +unsigned int PrintWmf::begin(Inkscape::Extension::Print *mod, SPDocument *doc) +{ + char *rec; + gchar const *utf8_fn = mod->get_param_string("destination"); + + // Typically PX2WORLD is 1200/90, using inkscape's default dpi + PX2WORLD = 1200.0 / Inkscape::Util::Quantity::convert(1.0, "in", "px"); + FixPPTCharPos = mod->get_param_bool("FixPPTCharPos"); + FixPPTDashLine = mod->get_param_bool("FixPPTDashLine"); + FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys"); + FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch"); + + (void) wmf_start(utf8_fn, 1000000, 250000, &wt); // Initialize the wt structure + (void) wmf_htable_create(128, 128, &wht); // Initialize the wht structure + + // WMF header the only things that can be set are the page size in inches (w,h) and the dpi + // width and height in px + _width = doc->getWidth().value("px"); + _height = doc->getHeight().value("px"); + + // initialize a few global variables + hbrush = hpen = 0; + htextalignment = U_TA_BASELINE | U_TA_LEFT; + use_stroke = use_fill = simple_shape = usebk = false; + + Inkscape::XML::Node *nv = doc->getReprNamedView(); + if (nv) { + const char *p1 = nv->attribute("pagecolor"); + char *p2; + uint32_t lc = strtoul(&p1[1], &p2, 16); // it looks like "#ABC123" + if (*p2) { + lc = 0; + } + gv.bgc = _gethexcolor(lc); + gv.rgb[0] = (float) U_RGBAGetR(gv.bgc) / 255.0; + gv.rgb[1] = (float) U_RGBAGetG(gv.bgc) / 255.0; + gv.rgb[2] = (float) U_RGBAGetB(gv.bgc) / 255.0; + } + + bool pageBoundingBox; + pageBoundingBox = mod->get_param_bool("pageBoundingBox"); + + Geom::Rect d; + if (pageBoundingBox) { + d = Geom::Rect::from_xywh(0, 0, _width, _height); + } else { + SPItem *doc_item = doc->getRoot(); + Geom::OptRect bbox = doc_item->desktopVisualBounds(); + if (bbox) { + d = *bbox; + } + } + + d *= Geom::Scale(Inkscape::Util::Quantity::convert(1, "px", "in")); // 90 dpi inside inkscape, wmf file will be 1200 dpi + + /* -1/1200 in next two lines so that WMF read in will write out again at exactly the same size */ + float dwInchesX = d.width() - 1.0 / 1200.0; + float dwInchesY = d.height() - 1.0 / 1200.0; + int dwPxX = round(dwInchesX * 1200.0); + int dwPxY = round(dwInchesY * 1200.0); +#if 0 + float dwInchesX = d.width(); + float dwInchesY = d.height(); + int dwPxX = round(d.width() * 1200.0); + int dwPxY = round(d.height() * 1200.0); +#endif + + U_PAIRF *ps = U_PAIRF_set(dwInchesX, dwInchesY); + rec = U_WMRHEADER_set(ps, 1200); // Example: drawing is A4 horizontal, 1200 dpi + free(ps); + if (!rec) { + g_warning("Failed in PrintWmf::begin at WMRHEADER"); + return -1; + } + (void) wmf_header_append((U_METARECORD *)rec, wt, 1); + + rec = U_WMRSETWINDOWEXT_set(point16_set(dwPxX, dwPxY)); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at WMRSETWINDOWEXT"); + return -1; + } + + rec = U_WMRSETWINDOWORG_set(point16_set(0, 0)); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at WMRSETWINDOWORG"); + return -1; + } + + rec = U_WMRSETMAPMODE_set(U_MM_ANISOTROPIC); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at WMRSETMAPMODE"); + return -1; + } + + /* set some parameters, else the program that reads the WMF may default to other values */ + + rec = U_WMRSETBKMODE_set(U_TRANSPARENT); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at U_WMRSETBKMODE"); + return -1; + } + + hpolyfillmode = U_WINDING; + rec = U_WMRSETPOLYFILLMODE_set(U_WINDING); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at U_WMRSETPOLYFILLMODE"); + return -1; + } + + // Text alignment: (only changed if RTL text is encountered ) + // - (x,y) coordinates received by this filter are those of the point where the text + // actually starts, and already takes into account the text object's alignment; + // - for this reason, the WMF text alignment must always be TA_BASELINE|TA_LEFT. + rec = U_WMRSETTEXTALIGN_set(U_TA_BASELINE | U_TA_LEFT); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at U_WMRSETTEXTALIGN_set"); + return -1; + } + + htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0; + rec = U_WMRSETTEXTCOLOR_set(U_RGB(0, 0, 0)); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at U_WMRSETTEXTCOLOR_set"); + return -1; + } + + rec = U_WMRSETROP2_set(U_R2_COPYPEN); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at U_WMRSETROP2"); + return -1; + } + + hmiterlimit = 5; + rec = wmiterlimit_set(5); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at wmiterlimit_set"); + return -1; + } + + + // create a pen as object 0. We never use it (except by mistake). Its purpose it to make all of the other object indices >=1 + U_PEN up = U_PEN_set(U_PS_SOLID, 1, colorref_set(0, 0, 0)); + uint32_t Pen; + rec = wcreatepenindirect_set(&Pen, wht, up); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at wcreatepenindirect_set"); + return -1; + } + + // create a null pen. If no specific pen is set, this is used + up = U_PEN_set(U_PS_NULL, 1, colorref_set(0, 0, 0)); + rec = wcreatepenindirect_set(&hpen_null, wht, up); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at wcreatepenindirect_set"); + return -1; + } + destroy_pen(); // make this pen active + + // create a null brush. If no specific brush is set, this is used + U_WLOGBRUSH lb = U_WLOGBRUSH_set(U_BS_NULL, U_RGB(0, 0, 0), U_HS_HORIZONTAL); + rec = wcreatebrushindirect_set(&hbrush_null, wht, lb); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_warning("Failed in PrintWmf::begin at wcreatebrushindirect_set"); + return -1; + } + destroy_brush(); // make this brush active + + return 0; +} + + +unsigned int PrintWmf::finish(Inkscape::Extension::Print * /*mod*/) +{ + char *rec; + if (!wt) { + return 0; + } + + // get rid of null brush + rec = wdeleteobject_set(&hbrush_null, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::finish at wdeleteobject_set null brush"); + } + + // get rid of null pen + rec = wdeleteobject_set(&hpen_null, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::finish at wdeleteobject_set null pen"); + } + + // get rid of object 0, which was a pen that was used to shift the other object indices to >=1. + hpen = 0; + rec = wdeleteobject_set(&hpen, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::finish at wdeleteobject_set filler object"); + } + + rec = U_WMREOF_set(); // generate the EOF record + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::finish"); + } + (void) wmf_finish(wt); // Finalize and write out the WMF + uwmf_free(&wt); // clean up + wmf_htable_free(&wht); // clean up + + return 0; +} + + +unsigned int PrintWmf::comment(Inkscape::Extension::Print * /*module*/, const char * /*comment*/) +{ + if (!wt) { + return 0; + } + + // earlier versions had flush of fill here, but it never executed and was removed + + return 0; +} + + +// fcolor is defined when gradients are being expanded, it is the color of one stripe or ring. +int PrintWmf::create_brush(SPStyle const *style, U_COLORREF *fcolor) +{ + float rgb[3]; + char *rec; + U_WLOGBRUSH lb; + uint32_t brush, fmode; + MFDrawMode fill_mode; + Inkscape::Pixbuf *pixbuf; + uint32_t brushStyle; + int hatchType; + U_COLORREF hatchColor; + U_COLORREF bkColor; + uint32_t width = 0; // quiets a harmless compiler warning, initialization not otherwise required. + uint32_t height = 0; + + if (!wt) { + return 0; + } + + // set a default fill in case we can't figure out a better way to do it + fmode = U_ALTERNATE; + fill_mode = DRAW_PAINT; + brushStyle = U_BS_SOLID; + hatchType = U_HS_SOLIDCLR; + bkColor = U_RGB(0, 0, 0); + if (fcolor) { + hatchColor = *fcolor; + } else { + hatchColor = U_RGB(0, 0, 0); + } + + if (!fcolor && style) { + if (style->fill.isColor()) { + fill_mode = DRAW_PAINT; + /* Dead assignment: Value stored to 'opacity' is never read + float opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + if (opacity <= 0.0) { + opacity = 0.0; // basically the same as no fill + } + */ + style->fill.value.color.get_rgb_floatv(rgb); + hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); + + fmode = style->fill_rule.computed == 0 ? U_WINDING : (style->fill_rule.computed == 2 ? U_ALTERNATE : U_ALTERNATE); + } else if (SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style))) { // must be paint-server + SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPPattern *pat = SP_PATTERN(paintserver); + double dwidth = pat->width(); + double dheight = pat->height(); + width = dwidth; + height = dheight; + brush_classify(pat, 0, &pixbuf, &hatchType, &hatchColor, &bkColor); + if (pixbuf) { + fill_mode = DRAW_IMAGE; + } else { // pattern + fill_mode = DRAW_PATTERN; + if (hatchType == -1) { // Not a standard hatch, so force it to something + hatchType = U_HS_CROSS; + hatchColor = U_RGB(0xFF, 0xC3, 0xC3); + } + } + if (FixPPTPatternAsHatch) { + if (hatchType == -1) { // image or unclassified + fill_mode = DRAW_PATTERN; + hatchType = U_HS_DIAGCROSS; + hatchColor = U_RGB(0xFF, 0xC3, 0xC3); + } + } + brushStyle = U_BS_HATCHED; + } else if (SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))) { // must be a gradient + // currently we do not do anything with gradients, the code below just sets the color to the average of the stops + SPPaintServer *paintserver = style->fill.value.href->getObject(); + SPLinearGradient *lg = nullptr; + SPRadialGradient *rg = nullptr; + + if (SP_IS_LINEARGRADIENT(paintserver)) { + lg = SP_LINEARGRADIENT(paintserver); + SP_GRADIENT(lg)->ensureVector(); // when exporting from commandline, vector is not built + fill_mode = DRAW_LINEAR_GRADIENT; + } else if (SP_IS_RADIALGRADIENT(paintserver)) { + rg = SP_RADIALGRADIENT(paintserver); + SP_GRADIENT(rg)->ensureVector(); // when exporting from commandline, vector is not built + fill_mode = DRAW_RADIAL_GRADIENT; + } else { + // default fill + } + + if (rg) { + if (FixPPTGrad2Polys) { + return hold_gradient(rg, fill_mode); + } else { + hatchColor = avg_stop_color(rg); + } + } else if (lg) { + if (FixPPTGrad2Polys) { + return hold_gradient(lg, fill_mode); + } else { + hatchColor = avg_stop_color(lg); + } + } + } + } else { // if (!style) + // default fill + } + + switch (fill_mode) { + case DRAW_LINEAR_GRADIENT: // fill with average color unless gradients are converted to slices + case DRAW_RADIAL_GRADIENT: // ditto + case DRAW_PAINT: + case DRAW_PATTERN: + // SVG text has no background attribute, so OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output. + if (usebk) { + rec = U_WMRSETBKCOLOR_set(bkColor); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_brush at U_WMRSETBKCOLOR_set"); + } + rec = U_WMRSETBKMODE_set(U_OPAQUE); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_brush at U_WMRSETBKMODE_set"); + } + } + lb = U_WLOGBRUSH_set(brushStyle, hatchColor, hatchType); + rec = wcreatebrushindirect_set(&brush, wht, lb); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_brush at createbrushindirect_set"); + } + break; + case DRAW_IMAGE: + char *px; + char *rgba_px; + uint32_t cbPx; + uint32_t colortype; + U_RGBQUAD *ct; + int numCt; + U_BITMAPINFOHEADER Bmih; + U_BITMAPINFO *Bmi; + rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!! + colortype = U_BCBM_COLOR32; + (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1); + // pixbuf can be either PF_CAIRO or PF_GDK, and these have R and B bytes swapped + if (pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { swapRBinRGBA(px, width * height); } + Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0); + Bmi = bitmapinfo_set(Bmih, ct); + rec = wcreatedibpatternbrush_srcdib_set(&brush, wht, U_DIB_RGB_COLORS, Bmi, cbPx, px); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_brush at createdibpatternbrushpt_set"); + } + free(px); + free(Bmi); // ct will be NULL because of colortype + break; + } + + hbrush = brush; // need this later for destroy_brush + rec = wselectobject_set(brush, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_brush at wselectobject_set"); + } + + if (fmode != hpolyfillmode) { + hpolyfillmode = fmode; + rec = U_WMRSETPOLYFILLMODE_set(fmode); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_brush at U_WMRSETPOLYFILLMODE_set"); + } + } + + return 0; +} + + +void PrintWmf::destroy_brush() +{ + char *rec; + // WMF lets any object be deleted whenever, and the chips fall where they may... + if (hbrush) { + rec = wdeleteobject_set(&hbrush, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::destroy_brush"); + } + hbrush = 0; + } + + // (re)select the null brush + + rec = wselectobject_set(hbrush_null, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::destroy_brush"); + } +} + + +int PrintWmf::create_pen(SPStyle const *style, const Geom::Affine &transform) +{ + char *rec = nullptr; + uint32_t pen; + uint32_t penstyle; + U_COLORREF penColor; + U_PEN up; + int modstyle; + + if (!wt) { + return 0; + } + + // set a default stroke in case we can't figure out a better way to do it + penstyle = U_PS_SOLID; + modstyle = 0; + penColor = U_RGB(0, 0, 0); + uint32_t linewidth = 1; + + if (style) { // override some or all of the preceding + float rgb[3]; + + // WMF does not support hatched, bitmap, or gradient pens, just set the color. + style->stroke.value.color.get_rgb_floatv(rgb); + penColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]); + + using Geom::X; + using Geom::Y; + + Geom::Point zero(0, 0); + Geom::Point one(1, 1); + Geom::Point p0(zero * transform); + Geom::Point p1(one * transform); + Geom::Point p(p1 - p0); + + double scale = sqrt((p[X] * p[X]) + (p[Y] * p[Y])) / sqrt(2); + + if (!style->stroke_width.computed) { + return 0; //if width is 0 do not (reset) the pen, it should already be NULL_PEN + } + linewidth = MAX(1, (uint32_t) round(scale * style->stroke_width.computed * PX2WORLD)); + + // most WMF readers will ignore linecap and linejoin, but set them anyway. Inkscape itself can read them back in. + + if (style->stroke_linecap.computed == 0) { + modstyle |= U_PS_ENDCAP_FLAT; + } else if (style->stroke_linecap.computed == 1) { + modstyle |= U_PS_ENDCAP_ROUND; + } else { + modstyle |= U_PS_ENDCAP_SQUARE; + } + + if (style->stroke_linejoin.computed == 0) { + float miterlimit = style->stroke_miterlimit.value; // This is a ratio. + if (miterlimit < 1) { + miterlimit = 1; + } + + // most WMF readers will ignore miterlimit, but set it anyway. Inkscape itself can read it back in + if ((uint32_t)miterlimit != hmiterlimit) { + hmiterlimit = (uint32_t)miterlimit; + rec = wmiterlimit_set((uint32_t) miterlimit); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_pen at wmiterlimit_set"); + } + } + modstyle |= U_PS_JOIN_MITER; + } else if (style->stroke_linejoin.computed == 1) { + modstyle |= U_PS_JOIN_ROUND; + } else { + modstyle |= U_PS_JOIN_BEVEL; + } + + if (!style->stroke_dasharray.values.empty()) { + if (!FixPPTDashLine) { // if this is set code elsewhere will break dots/dashes into many smaller lines. + int n_dash = style->stroke_dasharray.values.size(); + /* options are dash, dot, dashdot and dashdotdot. Try to pick the closest one. */ + int mark_short=INT_MAX; + int mark_long =0; + int i; + for (i=0;i<n_dash;i++) { + int mark = style->stroke_dasharray.values[i].value; + if (mark > mark_long) { + mark_long = mark; + } + if (mark < mark_short) { + mark_short = mark; + } + } + if(mark_long == mark_short){ // only one mark size + penstyle = U_PS_DOT; + } + else if (n_dash==2) { + penstyle = U_PS_DASH; + } + else if (n_dash==4) { + penstyle = U_PS_DASHDOT; + } + else { + penstyle = U_PS_DASHDOTDOT; + } + } + } + + } + + up = U_PEN_set(penstyle | modstyle, linewidth, penColor); + rec = wcreatepenindirect_set(&pen, wht, up); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_pen at wcreatepenindirect_set"); + } + + rec = wselectobject_set(pen, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::create_pen at wselectobject_set"); + } + hpen = pen; // need this later for destroy_pen + + return 0; +} + +// delete the defined pen object +void PrintWmf::destroy_pen() +{ + char *rec = nullptr; + // WMF lets any object be deleted whenever, and the chips fall where they may... + if (hpen) { + rec = wdeleteobject_set(&hpen, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::destroy_pen"); + } + hpen = 0; + } + + // (re)select the null pen + + rec = wselectobject_set(hpen_null, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::destroy_pen"); + } +} + + +unsigned int PrintWmf::fill( + Inkscape::Extension::Print * /*mod*/, + Geom::PathVector const &pathv, Geom::Affine const & /*transform*/, SPStyle const *style, + Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/) +{ + using Geom::X; + using Geom::Y; + + Geom::Affine tf = m_tr_stack.top(); + + use_fill = true; + use_stroke = false; + + fill_transform = tf; + + if (create_brush(style, nullptr)) { + /* + Handle gradients. Uses modified livarot as 2geom boolops is currently broken. + Can handle gradients with multiple stops. + + The overlap is needed to avoid antialiasing artifacts when edges are not strictly aligned on pixel boundaries. + There is an inevitable loss of accuracy saving through an WMF file because of the integer coordinate system. + Keep the overlap quite large so that loss of accuracy does not remove an overlap. + */ + destroy_pen(); //this sets the NULL_PEN, otherwise gradient slices may display with boundaries, see longer explanation below + Geom::Path cutter; + float rgb[3]; + U_COLORREF wc, c1, c2; + FillRule frb = SPWR_to_LVFR((SPWindRule) style->fill_rule.computed); + double doff, doff_base, doff_range; + double divisions = 128.0; + int nstops; + int istop = 1; + float opa; // opacity at stop + + SPRadialGradient *tg = (SPRadialGradient *)(gv.grad); // linear/radial are the same here + nstops = tg->vector.stops.size(); + tg->vector.stops[0].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[0].opacity; + c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + tg->vector.stops[nstops - 1].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[nstops - 1].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + + doff = 0.0; + doff_base = 0.0; + doff_range = tg->vector.stops[1].offset; // next or last stop + + if (gv.mode == DRAW_RADIAL_GRADIENT) { + Geom::Point xv = gv.p2 - gv.p1; // X' vector + Geom::Point yv = gv.p3 - gv.p1; // Y' vector + Geom::Point xuv = Geom::unit_vector(xv); // X' unit vector + double rx = hypot(xv[X], xv[Y]); + double ry = hypot(yv[X], yv[Y]); + double range = fmax(rx, ry); // length along the gradient + double step = range / divisions; // adequate approximation for gradient + double overlap = step / 4.0; // overlap slices slightly + double start; + double stop; + Geom::PathVector pathvc, pathvr; + + /* radial gradient might stop part way through the shape, fill with outer color from there to "infinity". + Do this first so that outer colored ring will overlay it. + */ + pathvc = center_elliptical_hole_as_SVG_PathV(gv.p1, rx * (1.0 - overlap / range), ry * (1.0 - overlap / range), asin(xuv[Y])); + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_oddEven, frb); + wc = weight_opacity(c2); + (void) create_brush(style, &wc); + print_pathv(pathvr, fill_transform); + + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + + for (start = 0.0; start < range; start += step, doff += 1. / divisions) { + stop = start + step + overlap; + if (stop > range) { + stop = range; + } + wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base)); + (void) create_brush(style, &wc); + + pathvc = center_elliptical_ring_as_SVG_PathV(gv.p1, rx * start / range, ry * start / range, rx * stop / range, ry * stop / range, asin(xuv[Y])); + + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); // show the intersection + + if (doff >= doff_range - doff_base) { + istop++; + if (istop >= nstops) { + continue; // could happen on a rounding error + } + doff_base = doff_range; + doff_range = tg->vector.stops[istop].offset; // next or last stop + c1 = c2; + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + } + } + } else if (gv.mode == DRAW_LINEAR_GRADIENT) { + Geom::Point uv = Geom::unit_vector(gv.p2 - gv.p1); // unit vector + Geom::Point puv = uv.cw(); // perp. to unit vector + double range = Geom::distance(gv.p1, gv.p2); // length along the gradient + double step = range / divisions; // adequate approximation for gradient + double overlap = step / 4.0; // overlap slices slightly + double start; + double stop; + Geom::PathVector pathvc, pathvr; + + /* before lower end of gradient, overlap first slice position */ + wc = weight_opacity(c1); + (void) create_brush(style, &wc); + pathvc = rect_cutter(gv.p1, uv * (overlap), uv * (-50000.0), puv * 50000.0); + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); + + /* after high end of gradient, overlap last slice position */ + wc = weight_opacity(c2); + (void) create_brush(style, &wc); + pathvc = rect_cutter(gv.p2, uv * (-overlap), uv * (50000.0), puv * 50000.0); + pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); + + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + + for (start = 0.0; start < range; start += step, doff += 1. / divisions) { + stop = start + step + overlap; + if (stop > range) { + stop = range; + } + pathvc = rect_cutter(gv.p1, uv * start, uv * stop, puv * 50000.0); + + wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base)); + (void) create_brush(style, &wc); + Geom::PathVector pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb); + print_pathv(pathvr, fill_transform); // show the intersection + + if (doff >= doff_range - doff_base) { + istop++; + if (istop >= nstops) { + continue; // could happen on a rounding error + } + doff_base = doff_range; + doff_range = tg->vector.stops[istop].offset; // next or last stop + c1 = c2; + tg->vector.stops[istop].color.get_rgb_floatv(rgb); + opa = tg->vector.stops[istop].opacity; + c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + } + } + } else { + g_error("Fatal programming error in PrintWmf::fill, invalid gradient type detected"); + } + use_fill = false; // gradients handled, be sure stroke does not use stroke and fill + } else { + /* + Inkscape was not calling create_pen for objects with no border. + This was because it never called stroke() (next method). + PPT, and presumably others, pick whatever they want for the border if it is not specified, so no border can + become a visible border. + To avoid this force the pen to NULL_PEN if we can determine that no pen will be needed after the fill. + */ + if (style->stroke.noneSet || style->stroke_width.computed == 0.0) { + destroy_pen(); //this sets the NULL_PEN + } + + /* postpone fill in case stroke also required AND all stroke paths closed + Dashes converted to line segments will "open" a closed path. + */ + bool all_closed = true; + for (const auto & pit : pathv) { + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + if (pit.end_default() != pit.end_closed()) { + all_closed = false; + } + } + } + if ( + (style->stroke.isNone() || style->stroke.noneSet || style->stroke_width.computed == 0.0) || + (!style->stroke_dasharray.values.empty() && FixPPTDashLine) || + !all_closed + ) { + print_pathv(pathv, fill_transform); // do any fills. side effect: clears fill_pathv + use_fill = false; + } + } + + return 0; +} + + +unsigned int PrintWmf::stroke( + Inkscape::Extension::Print * /*mod*/, + Geom::PathVector const &pathv, const Geom::Affine &/*transform*/, const SPStyle *style, + Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/) +{ + + char *rec = nullptr; + Geom::Affine tf = m_tr_stack.top(); + + use_stroke = true; + // use_fill was set in ::fill, if it is needed, if not, the null brush is used, it should be already set + + if (create_pen(style, tf)) { + return 0; + } + + if (!style->stroke_dasharray.values.empty() && FixPPTDashLine) { + // convert the path, gets its complete length, and then make a new path with parameter length instead of t + Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw; // pathv-> sbasis + Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw2; // sbasis using arc length parameter + Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw3; // new (discontinuous) path, composed of dots/dashes + Geom::Piecewise<Geom::D2<Geom::SBasis> > first_frag; // first fragment, will be appended at end + int n_dash = style->stroke_dasharray.values.size(); + int i = 0; //dash index + double tlength; // length of tmp_pathpw + double slength = 0.0; // start of gragment + double elength; // end of gragment + for (const auto & i : pathv) { + tmp_pathpw.concat(i.toPwSb()); + } + tlength = length(tmp_pathpw, 0.1); + tmp_pathpw2 = arc_length_parametrization(tmp_pathpw); + + // go around the dash array repeatedly until the entire path is consumed (but not beyond). + while (slength < tlength) { + elength = slength + style->stroke_dasharray.values[i++].value; + if (elength > tlength) { + elength = tlength; + } + Geom::Piecewise<Geom::D2<Geom::SBasis> > fragment(portion(tmp_pathpw2, slength, elength)); + if (slength) { + tmp_pathpw3.concat(fragment); + } else { + first_frag = fragment; + } + slength = elength; + slength += style->stroke_dasharray.values[i++].value; // the gap + if (i >= n_dash) { + i = 0; + } + } + tmp_pathpw3.concat(first_frag); // may merge line around start point + Geom::PathVector out_pathv = Geom::path_from_piecewise(tmp_pathpw3, 0.01); + print_pathv(out_pathv, tf); + } else { + print_pathv(pathv, tf); + } + + use_stroke = false; + use_fill = false; + + if (usebk) { // OPAQUE was set, revert to TRANSPARENT + usebk = false; + rec = U_WMRSETBKMODE_set(U_TRANSPARENT); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::stroke at U_WMRSETBKMODE_set"); + } + } + + return 0; +} + + +// Draws simple_shapes, those with closed WMR_* primitives, like polygons, rectangles and ellipses. +// These use whatever the current pen/brush are and need not be followed by a FILLPATH or STROKEPATH. +// For other paths it sets a few flags and returns. +bool PrintWmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Affine &transform) +{ + + Geom::PathVector pv = pathv_to_linear(pathv * transform, MAXDISP); + + int nodes = 0; + int moves = 0; + int lines = 0; + int curves = 0; + char *rec = nullptr; + + for (const auto & pit : pv) { + moves++; + nodes++; + + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + nodes++; + + if (is_straight_curve(*cit)) { + lines++; + } else if (dynamic_cast<Geom::CubicBezier const *>(&*cit)) { + curves++; + } + } + } + + if (!nodes) { + return false; + } + + U_POINT16 *lpPoints = new U_POINT16[moves + lines + curves * 3]; + int i = 0; + + /** For all Subpaths in the <path> */ + + for (const auto & pit : pv) { + using Geom::X; + using Geom::Y; + + Geom::Point p0 = pit.initialPoint(); + + p0[X] = (p0[X] * PX2WORLD); + p0[Y] = (p0[Y] * PX2WORLD); + + int32_t const x0 = (int32_t) round(p0[X]); + int32_t const y0 = (int32_t) round(p0[Y]); + + lpPoints[i].x = x0; + lpPoints[i].y = y0; + i = i + 1; + + /** For all segments in the subpath */ + + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + if (is_straight_curve(*cit)) { + //Geom::Point p0 = cit->initialPoint(); + Geom::Point p1 = cit->finalPoint(); + + //p0[X] = (p0[X] * PX2WORLD); + p1[X] = (p1[X] * PX2WORLD); + //p0[Y] = (p0[Y] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + + //int32_t const x0 = (int32_t) round(p0[X]); + //int32_t const y0 = (int32_t) round(p0[Y]); + int32_t const x1 = (int32_t) round(p1[X]); + int32_t const y1 = (int32_t) round(p1[Y]); + + lpPoints[i].x = x1; + lpPoints[i].y = y1; + i = i + 1; + } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) { + std::vector<Geom::Point> points = cubic->controlPoints(); + //Geom::Point p0 = points[0]; + Geom::Point p1 = points[1]; + Geom::Point p2 = points[2]; + Geom::Point p3 = points[3]; + + //p0[X] = (p0[X] * PX2WORLD); + p1[X] = (p1[X] * PX2WORLD); + p2[X] = (p2[X] * PX2WORLD); + p3[X] = (p3[X] * PX2WORLD); + //p0[Y] = (p0[Y] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + p2[Y] = (p2[Y] * PX2WORLD); + p3[Y] = (p3[Y] * PX2WORLD); + + //int32_t const x0 = (int32_t) round(p0[X]); + //int32_t const y0 = (int32_t) round(p0[Y]); + int32_t const x1 = (int32_t) round(p1[X]); + int32_t const y1 = (int32_t) round(p1[Y]); + int32_t const x2 = (int32_t) round(p2[X]); + int32_t const y2 = (int32_t) round(p2[Y]); + int32_t const x3 = (int32_t) round(p3[X]); + int32_t const y3 = (int32_t) round(p3[Y]); + + lpPoints[i].x = x1; + lpPoints[i].y = y1; + lpPoints[i + 1].x = x2; + lpPoints[i + 1].y = y2; + lpPoints[i + 2].x = x3; + lpPoints[i + 2].y = y3; + i = i + 3; + } + } + } + + bool done = false; + bool closed = (lpPoints[0].x == lpPoints[i - 1].x) && (lpPoints[0].y == lpPoints[i - 1].y); + bool polygon = false; + bool rectangle = false; + bool ellipse = false; + + if (moves == 1 && moves + lines == nodes && closed) { + polygon = true; + // if (nodes==5) { // disable due to LP Bug 407394 + // if (lpPoints[0].x == lpPoints[3].x && lpPoints[1].x == lpPoints[2].x && + // lpPoints[0].y == lpPoints[1].y && lpPoints[2].y == lpPoints[3].y) + // { + // rectangle = true; + // } + // } + } else if (moves == 1 && nodes == 5 && moves + curves == nodes && closed) { + // if (lpPoints[0].x == lpPoints[1].x && lpPoints[1].x == lpPoints[11].x && + // lpPoints[5].x == lpPoints[6].x && lpPoints[6].x == lpPoints[7].x && + // lpPoints[2].x == lpPoints[10].x && lpPoints[3].x == lpPoints[9].x && lpPoints[4].x == lpPoints[8].x && + // lpPoints[2].y == lpPoints[3].y && lpPoints[3].y == lpPoints[4].y && + // lpPoints[8].y == lpPoints[9].y && lpPoints[9].y == lpPoints[10].y && + // lpPoints[5].y == lpPoints[1].y && lpPoints[6].y == lpPoints[0].y && lpPoints[7].y == lpPoints[11].y) + // { // disable due to LP Bug 407394 + // ellipse = true; + // } + } + + if (polygon || ellipse) { + // pens and brushes already set by caller, do not touch them + + if (polygon) { + if (rectangle) { + U_RECT16 rcl = U_RECT16_set((U_POINT16) { + lpPoints[0].x, lpPoints[0].y + }, (U_POINT16) { + lpPoints[2].x, lpPoints[2].y + }); + rec = U_WMRRECTANGLE_set(rcl); + } else { + rec = U_WMRPOLYGON_set(nodes, lpPoints); + } + } else if (ellipse) { + U_RECT16 rcl = U_RECT16_set((U_POINT16) { + lpPoints[6].x, lpPoints[3].y + }, (U_POINT16) { + lpPoints[0].x, lpPoints[9].y + }); + rec = U_WMRELLIPSE_set(rcl); + } + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::print_simple_shape at retangle/ellipse/polygon"); + } + + done = true; + + } + + delete[] lpPoints; + + return done; +} + +/** Some parts based on win32.cpp by Lauris Kaplinski <lauris@kaplinski.com>. Was a part of Inkscape + in the past (or will be in the future?) Not in current trunk. (4/19/2012) + + Limitations of this code: + 1. Images lose their rotation, one corner stays in the same place. + 2. Transparency is lost on export. (A limitation of the WMF format.) + 3. Probably messes up if row stride != w*4 + 4. There is still a small memory leak somewhere, possibly in a pixbuf created in a routine + that calls this one and passes px, but never removes the rest of the pixbuf. The first time + this is called it leaked 5M (in one test) and each subsequent call leaked around 200K more. + If this routine is reduced to + if(1)return(0); + and called for a single 1280 x 1024 image then the program leaks 11M per call, or roughly the + size of two bitmaps. +*/ + +unsigned int PrintWmf::image( + Inkscape::Extension::Print * /* module */, /** not used */ + unsigned char *rgba_px, /** array of pixel values, Gdk::Pixbuf bitmap format */ + unsigned int w, /** width of bitmap */ + unsigned int h, /** height of bitmap */ + unsigned int rs, /** row stride (normally w*4) */ + Geom::Affine const &tf_rect, /** affine transform only used for defining location and size of rect, for all other transforms, use the one from m_tr_stack */ + SPStyle const * /*style*/) /** provides indirect link to image object */ +{ + double x1, y1, dw, dh; + char *rec = nullptr; + Geom::Affine tf = m_tr_stack.top(); + + rec = U_WMRSETSTRETCHBLTMODE_set(U_COLORONCOLOR); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::image at EMRHEADER"); + } + + x1 = tf_rect[4]; + y1 = tf_rect[5]; + dw = ((double) w) * tf_rect[0]; + dh = ((double) h) * tf_rect[3]; + Geom::Point pLL(x1, y1); + Geom::Point pLL2 = pLL * tf; //location of LL corner in Inkscape coordinates + + /* adjust scale of w and h. This works properly when there is no rotation. The values are + a bit strange when there is rotation, but since WMF cannot handle rotation in any case, all + answers are equally wrong. + */ + Geom::Point pWH(dw, dh); + Geom::Point pWH2 = pWH * tf.withoutTranslation(); + + char *px; + uint32_t cbPx; + uint32_t colortype; + U_RGBQUAD *ct; + int numCt; + U_BITMAPINFOHEADER Bmih; + U_BITMAPINFO *Bmi; + colortype = U_BCBM_COLOR32; + (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, (char *) rgba_px, w, h, w * 4, colortype, 0, 1); + Bmih = bitmapinfoheader_set(w, h, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0); + Bmi = bitmapinfo_set(Bmih, ct); + + U_POINT16 Dest = point16_set(round(pLL2[Geom::X] * PX2WORLD), round(pLL2[Geom::Y] * PX2WORLD)); + U_POINT16 cDest = point16_set(round(pWH2[Geom::X] * PX2WORLD), round(pWH2[Geom::Y] * PX2WORLD)); + U_POINT16 Src = point16_set(0, 0); + U_POINT16 cSrc = point16_set(w, h); + rec = U_WMRSTRETCHDIB_set( + Dest, //! Destination UL corner in logical units + cDest, //! Destination W & H in logical units + Src, //! Source UL corner in logical units + cSrc, //! Source W & H in logical units + U_DIB_RGB_COLORS, //! DIBColors Enumeration + U_SRCCOPY, //! RasterOPeration Enumeration + Bmi, //! (Optional) bitmapbuffer (U_BITMAPINFO section) + h * rs, //! size in bytes of px + px //! (Optional) bitmapbuffer (U_BITMAPINFO section) + ); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::image at U_WMRSTRETCHDIB_set"); + } + free(px); + free(Bmi); + if (numCt) { + free(ct); + } + return 0; +} + +// may also be called with a simple_shape or an empty path, whereupon it just returns without doing anything +unsigned int PrintWmf::print_pathv(Geom::PathVector const &pathv, const Geom::Affine &transform) +{ + char *rec = nullptr; + U_POINT16 *pt16hold, *pt16ptr; + uint16_t *n16hold; + uint16_t *n16ptr; + + simple_shape = print_simple_shape(pathv, transform); + if (!simple_shape && !pathv.empty()) { + // WMF does not have beziers, need to convert to ONLY linears with something like this: + Geom::PathVector pv = pathv_to_linear(pathv * transform, MAXDISP); + + /** For all Subpaths in the <path> */ + + /* If the path consists entirely of closed subpaths use one polypolygon. + Otherwise use a mix of polygon or polyline separately on each path. + If the polyline turns out to be single line segments, use a series of MOVETO/LINETO instead, + because WMF has no POLYPOLYLINE. + The former allows path delimited donuts and the like, which + cannot be represented in WMF with polygon or polyline because there is no external way to combine paths + as there is in EMF or SVG. + For polygons specify the last point the same as the first. The WMF/EMF manuals say that the + reading program SHOULD close the path, which allows a conforming program not to, potentially rendering + a closed path as an open one. */ + int nPolys = 0; + int totPoints = 0; + for (const auto & pit : pv) { + totPoints += 1 + pit.size_default(); // big array, will hold all points, for all polygons. Size_default ignores first point in each path. + if (pit.end_default() == pit.end_closed()) { + nPolys++; + } else { + nPolys = 0; + break; + } + } + + if (nPolys > 1) { // a single polypolygon, a single polygon falls through to the else + pt16hold = pt16ptr = (U_POINT16 *) malloc(totPoints * sizeof(U_POINT16)); + if (!pt16ptr) { + return(false); + } + + n16hold = n16ptr = (uint16_t *) malloc(nPolys * sizeof(uint16_t)); + if (!n16ptr) { + free(pt16hold); + return(false); + } + + for (const auto & pit : pv) { + using Geom::X; + using Geom::Y; + + + *n16ptr++ = pit.size_default(); // points in the subpath + + /** For each segment in the subpath */ + + Geom::Point p1 = pit.initialPoint(); // This point is special, it isn't in the iterator + + p1[X] = (p1[X] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y])); + + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) { + Geom::Point p1 = cit->finalPoint(); + + p1[X] = (p1[X] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y])); + } + + } + rec = U_WMRPOLYPOLYGON_set(nPolys, n16hold, pt16hold); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRPOLYPOLYGON_set"); + } + free(pt16hold); + free(n16hold); + } else { // one or more polyline or polygons (but not all polygons, that would be the preceding case) + for (const auto & pit : pv) { + using Geom::X; + using Geom::Y; + + /* Malformatted Polylines with a sequence like M L M M L have been seen, the 2nd M does nothing + and that point must not go into the output. */ + if (!(pit.size_default())) { + continue; + } + /* Figure out how many points there are, make an array big enough to hold them, and store + all the points. This is the same for open or closed path. This gives the upper bound for + the number of points. The actual number used is calculated on the fly. + */ + int nPoints = 1 + pit.size_default(); + + pt16hold = pt16ptr = (U_POINT16 *) malloc(nPoints * sizeof(U_POINT16)); + if (!pt16ptr) { + break; + } + + /** For each segment in the subpath */ + + Geom::Point p1 = pit.initialPoint(); // This point is special, it isn't in the iterator + + p1[X] = (p1[X] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y])); + nPoints = 1; + + for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_default(); ++cit, nPoints++) { + Geom::Point p1 = cit->finalPoint(); + + p1[X] = (p1[X] * PX2WORLD); + p1[Y] = (p1[Y] * PX2WORLD); + *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y])); + } + + if (pit.end_default() == pit.end_closed()) { + rec = U_WMRPOLYGON_set(nPoints, pt16hold); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRPOLYGON_set"); + } + } else if (nPoints > 2) { + rec = U_WMRPOLYLINE_set(nPoints, pt16hold); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::print_pathv at U_POLYLINE_set"); + } + } else if (nPoints == 2) { + rec = U_WMRMOVETO_set(pt16hold[0]); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRMOVETO_set"); + } + rec = U_WMRLINETO_set(pt16hold[1]); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRLINETO_set"); + } + } + free(pt16hold); + } + } + } + + // WMF has no fill or stroke commands, the draw does it with active pen/brush + + // clean out brush and pen, but only after all parts of the draw complete + if (use_fill) { + destroy_brush(); + } + if (use_stroke) { + destroy_pen(); + } + + return TRUE; +} + + +unsigned int PrintWmf::text(Inkscape::Extension::Print * /*mod*/, char const *text, Geom::Point const &p, + SPStyle const *const style) +{ + if (!wt || !text) { + return 0; + } + + char *rec = nullptr; + int ccount, newfont; + int fix90n = 0; + uint32_t hfont = 0; + Geom::Affine tf = m_tr_stack.top(); + double rot = -1800.0 * std::atan2(tf[1], tf[0]) / M_PI; // 0.1 degree rotation, - sign for MM_TEXT + double rotb = -std::atan2(tf[1], tf[0]); // rotation for baseline offset for superscript/subscript, used below + double dx, dy; + double ky; + + // the dx array is smuggled in like: text<nul>w1 w2 w3 ...wn<nul><nul>, where the widths are floats 7 characters wide, including the space + int ndx = 0; + int rtl = 0; + int16_t *adx; + smuggle_adxky_out(text, &adx, &ky, &rtl, &ndx, PX2WORLD * std::min(tf.expansionX(), tf.expansionY())); // side effect: free() adx + + uint32_t textalignment; + if (rtl > 0) { + textalignment = U_TA_BASELINE | U_TA_LEFT; + } else { + textalignment = U_TA_BASELINE | U_TA_RIGHT | U_TA_RTLREADING; + } + if (textalignment != htextalignment) { + htextalignment = textalignment; + rec = U_WMRSETTEXTALIGN_set(textalignment); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTALIGN_set"); + } + } + + char *text2 = strdup(text); // because U_Utf8ToUtf16le calls iconv which does not like a const char * + uint16_t *unicode_text = U_Utf8ToUtf16le(text2, 0, nullptr); + free(text2); + //translates Unicode as Utf16le to NonUnicode, if possible. If any translate, all will, and all to + //the same font, because of code in Layout::print + UnicodeToNon(unicode_text, &ccount, &newfont); + // The preceding hopefully handled conversions to symbol, wingdings or zapf dingbats. Now slam everything + // else down into latin1, which is all WMF can handle. If the language isn't English expect terrible results. + char *latin1_text = U_Utf16leToLatin1(unicode_text, 0, nullptr); + free(unicode_text); + + // in some cases a UTF string may reduce to NO latin1 characters, which returns NULL + if(!latin1_text){free(adx); return 0; } + + //PPT gets funky with text within +-1 degree of a multiple of 90, but only for SOME fonts.Snap those to the central value + //Some funky ones: Arial, Times New Roman + //Some not funky ones: Symbol and Verdana. + //Without a huge table we cannot catch them all, so just the most common problem ones. + FontfixParams params; + + if (FixPPTCharPos) { + switch (newfont) { + case CVTSYM: + _lookup_ppt_fontfix("Convert To Symbol", params); + break; + case CVTZDG: + _lookup_ppt_fontfix("Convert To Zapf Dingbats", params); + break; + case CVTWDG: + _lookup_ppt_fontfix("Convert To Wingdings", params); + break; + default: //also CVTNON + _lookup_ppt_fontfix(style->font_family.value(), params); + break; + } + if (params.f2 != 0 || params.f3 != 0) { + int irem = ((int) round(rot)) % 900 ; + if (irem <= 9 && irem >= -9) { + fix90n = 1; //assume vertical + rot = (double)(((int) round(rot)) - irem); + rotb = rot * M_PI / 1800.0; + if (std::abs(rot) == 900.0) { + fix90n = 2; + } + } + } + } + + /* + Note that text font sizes are stored into the WMF as fairly small integers and that limits their precision. + The WMF output files produced here have been designed so that the integer valued pt sizes + land right on an integer value in the WMF file, so those are exact. However, something like 18.1 pt will be + somewhat off, so that when it is read back in it becomes 18.11 pt. (For instance.) + */ + int textheight = round(-style->font_size.computed * PX2WORLD * std::min(tf.expansionX(), tf.expansionY())); + if (!hfont) { + + // Get font face name. Use changed font name if unicode mapped to one + // of the special fonts. + char *facename; + if (!newfont) { + facename = U_Utf8ToLatin1(style->font_family.value(), 0, nullptr); + } else { + facename = U_Utf8ToLatin1(FontName(newfont), 0, nullptr); + } + + // Scale the text to the minimum stretch. (It tends to stay within bounding rectangles even if + // it was streteched asymmetrically.) Few applications support text from WMF which is scaled + // differently by height/width, so leave lfWidth alone. + + U_FONT *puf = U_FONT_set( + textheight, + 0, + round(rot), + round(rot), + _translate_weight(style->font_weight.computed), + (style->font_style.computed == SP_CSS_FONT_STYLE_ITALIC), + style->text_decoration_line.underline, + style->text_decoration_line.line_through, + U_DEFAULT_CHARSET, + U_OUT_DEFAULT_PRECIS, + U_CLIP_DEFAULT_PRECIS, + U_DEFAULT_QUALITY, + U_DEFAULT_PITCH | U_FF_DONTCARE, + facename); + free(facename); + + rec = wcreatefontindirect_set(&hfont, wht, puf); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::text at wcreatefontindirect_set"); + } + free(puf); + } + + rec = wselectobject_set(hfont, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::text at wselectobject_set"); + } + + float rgb[3]; + style->fill.value.color.get_rgb_floatv(rgb); + // only change the text color when it needs to be changed + if (memcmp(htextcolor_rgb, rgb, 3 * sizeof(float))) { + memcpy(htextcolor_rgb, rgb, 3 * sizeof(float)); + rec = U_WMRSETTEXTCOLOR_set(U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2])); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTCOLOR_set"); + } + } + + + // Text alignment: + // - (x,y) coordinates received by this filter are those of the point where the text + // actually starts, and already takes into account the text object's alignment; + // - for this reason, the WMF text alignment must always be TA_BASELINE|TA_LEFT. + // this is set at the beginning of the file and never changed + + // Transparent text background, never changes, set at the beginning of the file + + Geom::Point p2 = p * tf; + + //Handle super/subscripts and vertical kerning + /* Previously used this, but vertical kerning was not supported + p2[Geom::X] -= style->baseline_shift.computed * std::sin( rotb ); + p2[Geom::Y] -= style->baseline_shift.computed * std::cos( rotb ); + */ + p2[Geom::X] += ky * std::sin(rotb); + p2[Geom::Y] += ky * std::cos(rotb); + + //Conditionally handle compensation for PPT WMF import bug (affects PPT 2003-2010, at least) + if (FixPPTCharPos) { + if (fix90n == 1) { //vertical + dx = 0.0; + dy = params.f3 * style->font_size.computed * std::cos(rotb); + } else if (fix90n == 2) { //horizontal + dx = params.f2 * style->font_size.computed * std::sin(rotb); + dy = 0.0; + } else { + dx = params.f1 * style->font_size.computed * std::sin(rotb); + dy = params.f1 * style->font_size.computed * std::cos(rotb); + } + p2[Geom::X] += dx; + p2[Geom::Y] += dy; + } + + p2[Geom::X] = (p2[Geom::X] * PX2WORLD); + p2[Geom::Y] = (p2[Geom::Y] * PX2WORLD); + + int32_t const xpos = (int32_t) round(p2[Geom::X]); + int32_t const ypos = (int32_t) round(p2[Geom::Y]); + + // The number of characters in the string is a bit fuzzy. ndx, the number of entries in adx is + // the number of VISIBLE characters, since some may combine from the UTF (8 originally, + // now 16) encoding. Conversely strlen() or wchar16len() would give the absolute number of + // encoding characters. Unclear if emrtext wants the former or the latter but for now assume the former. + + // This is currently being smuggled in from caller as part of text, works + // MUCH better than the fallback hack below + // uint32_t *adx = dx_set(textheight, U_FW_NORMAL, slen); // dx is needed, this makes one up + if (rtl > 0) { + rec = U_WMREXTTEXTOUT_set((U_POINT16) { + (int16_t) xpos, (int16_t) ypos + }, + ndx, U_ETO_NONE, latin1_text, adx, U_RCL16_DEF); + } else { // RTL text, U_TA_RTLREADING should be enough, but set this one too just in case + rec = U_WMREXTTEXTOUT_set((U_POINT16) { + (int16_t) xpos, (int16_t) ypos + }, + ndx, U_ETO_RTLREADING, latin1_text, adx, U_RCL16_DEF); + } + free(latin1_text); + free(adx); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::text at U_WMREXTTEXTOUTW_set"); + } + + rec = wdeleteobject_set(&hfont, wht); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::text at wdeleteobject_set"); + } + + return 0; +} + +void PrintWmf::init() +{ + /* WMF print */ + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>Windows Metafile Print</name>\n" + "<id>org.inkscape.print.wmf</id>\n" + "<param gui-hidden=\"true\" name=\"destination\" type=\"string\"></param>\n" + "<param gui-hidden=\"true\" name=\"textToPath\" type=\"bool\">true</param>\n" + "<param gui-hidden=\"true\" name=\"pageBoundingBox\" type=\"bool\">true</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTCharPos\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTDashLine\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTGrad2Polys\" type=\"bool\">false</param>\n" + "<param gui-hidden=\"true\" name=\"FixPPTPatternAsHatch\" type=\"bool\">false</param>\n" + "<print/>\n" + "</inkscape-extension>", new PrintWmf()); + + return; +} + +} /* namespace Internal */ +} /* namespace Extension */ +} /* 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/internal/wmf-print.h b/src/extension/internal/wmf-print.h new file mode 100644 index 0000000..92f5577 --- /dev/null +++ b/src/extension/internal/wmf-print.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * @brief Windows Metafile printing - implementation + */ +/* Author: + * Ulf Erikson <ulferikson@users.sf.net> + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_WMF_PRINT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_WMF_PRINT_H + +#include <3rdparty/libuemf/uwmf.h> +#include "extension/internal/metafile-print.h" + +#include "splivarot.h" // pieces for union on shapes +#include "display/canvas-bpath.h" // for SPWindRule + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class PrintWmf : public PrintMetafile +{ + uint32_t hbrush, hpen, hbrush_null, hpen_null; + uint32_t hmiterlimit; // used to minimize redundant records that set this + + unsigned int print_pathv (Geom::PathVector const &pathv, const Geom::Affine &transform); + bool print_simple_shape (Geom::PathVector const &pathv, const Geom::Affine &transform); + +public: + PrintWmf(); + + /* Print functions */ + unsigned int setup (Inkscape::Extension::Print * module) override; + + unsigned int begin (Inkscape::Extension::Print * module, SPDocument *doc) override; + unsigned int finish (Inkscape::Extension::Print * module) override; + + /* Rendering methods */ + unsigned int fill (Inkscape::Extension::Print *module, + Geom::PathVector const &pathv, + Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, + Geom::OptRect const &bbox) override; + unsigned int stroke (Inkscape::Extension::Print * module, + Geom::PathVector const &pathv, + Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, + Geom::OptRect const &bbox) override; + unsigned int image(Inkscape::Extension::Print *module, + unsigned char *px, + unsigned int w, + unsigned int h, + unsigned int rs, + Geom::Affine const &transform, + SPStyle const *style) override; + unsigned int comment(Inkscape::Extension::Print *module, const char * comment) override; + unsigned int text(Inkscape::Extension::Print *module, char const *text, + Geom::Point const &p, SPStyle const *style) override; + + static void init (); +protected: + static void smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *rtl, int *ndx, float scale); + + int create_brush(SPStyle const *style, PU_COLORREF fcolor) override; + void destroy_brush() override; + int create_pen(SPStyle const *style, const Geom::Affine &transform) override; + void destroy_pen() override; +}; + +} /* namespace Internal */ +} /* namespace Extension */ +} /* namespace Inkscape */ + + +#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_WMF_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/extension/internal/wpg-input.cpp b/src/extension/internal/wpg-input.cpp new file mode 100644 index 0000000..2041cd2 --- /dev/null +++ b/src/extension/internal/wpg-input.cpp @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This file came from libwpg as a source, their utility wpg2svg + * specifically. It has been modified to work as an Inkscape extension. + * The Inkscape extension code is covered by this copyright, but the + * rest is covered by the one below. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Abhishek Sharma + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + * + */ + +/* libwpg + * Copyright (C) 2006 Ariya Hidayat (ariya@kde.org) + * Copyright (C) 2005 Fridrich Strba (fridrich.strba@bluewin.ch) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * For further information visit http://libwpg.sourceforge.net + */ + +/* "This product is not manufactured, approved, or supported by + * Corel Corporation or Corel Corporation Limited." + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#include <cstdio> + +#ifdef WITH_LIBWPG + +#include "wpg-input.h" +#include "extension/system.h" +#include "extension/input.h" +#include "document.h" +#include "object/sp-root.h" +#include "util/units.h" +#include <cstring> + +// Take a guess and fallback to 0.2.x if no configure has run +#if !defined(WITH_LIBWPG03) && !defined(WITH_LIBWPG02) +#define WITH_LIBWPG02 1 +#endif + +#include "libwpg/libwpg.h" +#if WITH_LIBWPG03 + #include <librevenge-stream/librevenge-stream.h> + + using librevenge::RVNGString; + using librevenge::RVNGFileStream; + using librevenge::RVNGInputStream; +#else + #include "libwpd-stream/libwpd-stream.h" + + typedef WPXString RVNGString; + typedef WPXFileStream RVNGFileStream; + typedef WPXInputStream RVNGInputStream; +#endif + +using namespace libwpg; + +namespace Inkscape { +namespace Extension { +namespace Internal { + + +SPDocument *WpgInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri) +{ + #ifdef _WIN32 + // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows + // therefore attempt to convert uri to the system codepage + // even if this is not possible the alternate short (8.3) file name will be used if available + gchar * converted_uri = g_win32_locale_filename_from_utf8(uri); + RVNGInputStream* input = new RVNGFileStream(converted_uri); + g_free(converted_uri); + #else + RVNGInputStream* input = new RVNGFileStream(uri); + #endif + +#if WITH_LIBWPG03 + if (input->isStructured()) { + RVNGInputStream* olestream = input->getSubStreamByName("PerfectOffice_MAIN"); +#else + if (input->isOLEStream()) { + RVNGInputStream* olestream = input->getDocumentOLEStream("PerfectOffice_MAIN"); +#endif + + if (olestream) { + delete input; + input = olestream; + } + } + + if (!WPGraphics::isSupported(input)) { + //! \todo Dialog here + // fprintf(stderr, "ERROR: Unsupported file format (unsupported version) or file is encrypted!\n"); + // printf("I'm giving up not supported\n"); + delete input; + return nullptr; + } + +#if WITH_LIBWPG03 + librevenge::RVNGStringVector vec; + librevenge::RVNGSVGDrawingGenerator generator(vec, ""); + + if (!libwpg::WPGraphics::parse(input, &generator) || vec.empty() || vec[0].empty()) { + delete input; + return nullptr; + } + + RVNGString output("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n"); + output.append(vec[0]); +#else + RVNGString output; + if (!libwpg::WPGraphics::generateSVG(input, output)) { + delete input; + return NULL; + } +#endif + + //printf("I've got a doc: \n%s", painter.document.c_str()); + + SPDocument * doc = SPDocument::createNewDocFromMem(output.cstr(), strlen(output.cstr()), TRUE); + + // Set viewBox if it doesn't exist + if (doc && !doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit()))); + } + + delete input; + return doc; +} + +#include "clear-n_.h" + +void WpgInput::init() { + Inkscape::Extension::build_from_mem( + "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" + "<name>" N_("WPG Input") "</name>\n" + "<id>org.inkscape.input.wpg</id>\n" + "<input>\n" + "<extension>.wpg</extension>\n" + "<mimetype>image/x-wpg</mimetype>\n" + "<filetypename>" N_("WordPerfect Graphics (*.wpg)") "</filetypename>\n" + "<filetypetooltip>" N_("Vector graphics format used by Corel WordPerfect") "</filetypetooltip>\n" + "</input>\n" + "</inkscape-extension>", new WpgInput()); +} // init + +} } } /* namespace Inkscape, Extension, Implementation */ +#endif /* WITH_LIBWPG */ + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/internal/wpg-input.h b/src/extension/internal/wpg-input.h new file mode 100644 index 0000000..67e4d91 --- /dev/null +++ b/src/extension/internal/wpg-input.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This code abstracts the libwpg interfaces into the Inkscape + * input extension interface. + * + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __EXTENSION_INTERNAL_WPGOUTPUT_H__ +#define __EXTENSION_INTERNAL_WPGOUTPUT_H__ + +#ifdef HAVE_CONFIG_H +# include "config.h" // only include where actually required! +#endif + +#ifdef WITH_LIBWPG + +#include "../implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class WpgInput : public Inkscape::Extension::Implementation::Implementation { + WpgInput () = default;; +public: + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ) override; + static void init( ); + +}; + +} } } /* namespace Inkscape, Extension, Implementation */ + +#endif /* WITH_LIBWPG */ +#endif /* __EXTENSION_INTERNAL_WPGOUTPUT_H__ */ + + +/* + 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: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : diff --git a/src/extension/loader.cpp b/src/extension/loader.cpp new file mode 100644 index 0000000..d409d09 --- /dev/null +++ b/src/extension/loader.cpp @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Loader for external plug-ins. + * + * Authors: + * Moritz Eberl <moritz@semiodesk.com> + * + * Copyright (C) 2016 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "loader.h" + +#include <gmodule.h> + +#include "system.h" +#include <cstring> +#include "dependency.h" +#include "inkscape-version.h" + +namespace Inkscape { +namespace Extension { + +typedef Implementation::Implementation *(*_getImplementation)(); +typedef const gchar *(*_getInkscapeVersion)(); + +bool Loader::load_dependency(Dependency *dep) +{ + GModule *module = nullptr; + module = g_module_open(dep->get_name(), (GModuleFlags)0); + if (module == nullptr) { + return false; + } + return true; +} + +/** + * @brief Load the actual implementation of a plugin supplied by the plugin. + * @param doc The xml representation of the INX extension configuration. + * @return The implementation of the extension loaded from the plugin. + */ +Implementation::Implementation *Loader::load_implementation(Inkscape::XML::Document *doc) +{ + try { + + Inkscape::XML::Node *repr = doc->root(); + Inkscape::XML::Node *child_repr = repr->firstChild(); + + // Iterate over the xml content + while (child_repr != nullptr) { + char const *chname = child_repr->name(); + if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + chname += strlen(INKSCAPE_EXTENSION_NS); + } + + // Deal with dependencies if we have them + if (!strcmp(chname, "dependency")) { + Dependency dep = Dependency(child_repr, nullptr); // TODO: Why is "this" not an extension? + // try to load it + bool success = load_dependency(&dep); + if( !success ){ + // Could not load dependency, we abort + const char *res = g_module_error(); + g_warning("Unable to load dependency %s of plugin %s.\nDetails: %s\n", dep.get_name(), "<todo>", res); + return nullptr; + } + } + + // Found a plugin to load + if (!strcmp(chname, "plugin")) { + + // The name of the plugin is actually the library file we want to load + if (const gchar *name = child_repr->attribute("name")) { + GModule *module = nullptr; + _getImplementation GetImplementation = nullptr; + _getInkscapeVersion GetInkscapeVersion = nullptr; + + // build the path where to look for the plugin + gchar *path = g_build_filename(_baseDirectory.c_str(), name, (char *) nullptr); + module = g_module_open(path, G_MODULE_BIND_LOCAL); + g_free(path); + + if (module == nullptr) { + // we were not able to load the plugin, write warning and abort + const char *res = g_module_error(); + g_warning("Unable to load extension %s.\nDetails: %s\n", name, res); + return nullptr; + } + + // Get a handle to the version function of the module + if (g_module_symbol(module, "GetInkscapeVersion", (gpointer *) &GetInkscapeVersion) == FALSE) { + // This didn't work, write warning and abort + const char *res = g_module_error(); + g_warning("Unable to load extension %s.\nDetails: %s\n", name, res); + return nullptr; + } + + // Get a handle to the function that delivers the implementation + if (g_module_symbol(module, "GetImplementation", (gpointer *) &GetImplementation) == FALSE) { + // This didn't work, write warning and abort + const char *res = g_module_error(); + g_warning("Unable to load extension %s.\nDetails: %s\n", name, res); + return nullptr; + } + + // Get version and test against this version + const gchar* version = GetInkscapeVersion(); + if( strcmp(version, version_string) != 0) { + // The versions are different, display warning. + g_warning("Plugin was built against Inkscape version %s, this is %s. The plugin might not be compatible.", version, version_string); + } + + + Implementation::Implementation *i = GetImplementation(); + return i; + } + } + + child_repr = child_repr->next(); + } + } catch (std::exception &e) { + g_warning("Unable to load extension."); + } + return nullptr; +} + +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99: diff --git a/src/extension/loader.h b/src/extension/loader.h new file mode 100644 index 0000000..c6adbe2 --- /dev/null +++ b/src/extension/loader.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Loader for external plug-ins. + *//* + * + * Authors: + * Moritz Eberl <moritz@semiodesk.com> + * + * Copyright (C) 2016 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_LOADER_H_ +#define INKSCAPE_EXTENSION_LOADER_H_ + +#include "extension.h" + + +namespace Inkscape { + +namespace XML { +class Document; +} + +namespace Extension { + +/** This class contains the mechanism to load c++ plugins dynamically. +*/ +class Loader { + +public: + /** + * Sets a base directory where to look for the actual plugin to load. + * + * @param dir is the path where the plugin should be loaded from. + */ + void set_base_directory(std::string dir) { + _baseDirectory = dir; + } + + /** + * Loads plugin dependencies which are needed for the plugin to load. + * + * @param dep + */ + bool load_dependency(Dependency *dep); + + /** + * Load the actual implementation of a plugin supplied by the plugin. + * + * @param doc The xml representation of the INX extension configuration. + * @return The implementation of the extension loaded from the plugin. + */ + Implementation::Implementation *load_implementation(Inkscape::XML::Document *doc); + +private: + std::string _baseDirectory; /**< The base directory to load a plugin from */ + + +}; + +} // namespace Extension +} // namespace Inkscape */ + +#endif // _LOADER_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/extension/output.cpp b/src/extension/output.cpp new file mode 100644 index 0000000..07c1120 --- /dev/null +++ b/src/extension/output.cpp @@ -0,0 +1,253 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl> + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "output.h" + +#include "document.h" + +#include "implementation/implementation.h" + +#include "prefdialog/prefdialog.h" + +#include "xml/repr.h" + + +/* Inkscape::Extension::Output */ + +namespace Inkscape { +namespace Extension { + +/** + \return None + \brief Builds a SPModuleOutput object from a XML description + \param module The module to be initialized + \param repr The XML description in a Inkscape::XML::Node tree + + Okay, so you want to build a SPModuleOutput object. + + This function first takes and does the build of the parent class, + which is SPModule. Then, it looks for the <output> section of the + XML description. Under there should be several fields which + describe the output module to excruciating detail. Those are parsed, + copied, and put into the structure that is passed in as module. + Overall, there are many levels of indentation, just to handle the + levels of indentation in the XML file. +*/ +Output::Output (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory) + : Extension(in_repr, in_imp, base_directory) +{ + mimetype = nullptr; + extension = nullptr; + filetypename = nullptr; + filetypetooltip = nullptr; + dataloss = TRUE; + + if (repr != nullptr) { + Inkscape::XML::Node * child_repr; + + child_repr = repr->firstChild(); + + while (child_repr != nullptr) { + if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "output")) { + child_repr = child_repr->firstChild(); + while (child_repr != nullptr) { + char const * chname = child_repr->name(); + if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + chname += strlen(INKSCAPE_EXTENSION_NS); + } + if (chname[0] == '_') /* Allow _ for translation of tags */ + chname++; + if (!strcmp(chname, "extension")) { + g_free (extension); + extension = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "mimetype")) { + g_free (mimetype); + mimetype = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "filetypename")) { + g_free (filetypename); + filetypename = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "filetypetooltip")) { + g_free (filetypetooltip); + filetypetooltip = g_strdup(child_repr->firstChild()->content()); + } + if (!strcmp(chname, "dataloss")) { + if (!strcmp(child_repr->firstChild()->content(), "false")) { + dataloss = FALSE; + } + } + + child_repr = child_repr->next(); + } + + break; + } + + child_repr = child_repr->next(); + } + + } +} + +/** + \brief Destroy an output extension +*/ +Output::~Output () +{ + g_free(mimetype); + g_free(extension); + g_free(filetypename); + g_free(filetypetooltip); + return; +} + +/** + \return Whether this extension checks out + \brief Validate this extension + + This function checks to make sure that the output extension has + a filename extension and a MIME type. Then it calls the parent + class' check function which also checks out the implementation. +*/ +bool +Output::check () +{ + if (extension == nullptr) + return FALSE; + if (mimetype == nullptr) + return FALSE; + + return Extension::check(); +} + +/** + \return IETF mime-type for the extension + \brief Get the mime-type that describes this extension +*/ +gchar * +Output::get_mimetype() +{ + return mimetype; +} + +/** + \return Filename extension for the extension + \brief Get the filename extension for this extension +*/ +gchar * +Output::get_extension() +{ + return extension; +} + +/** + \return The name of the filetype supported + \brief Get the name of the filetype supported +*/ +const char * +Output::get_filetypename(bool translated) +{ + const char *name; + + if (filetypename) + name = filetypename; + else + name = get_name(); + + if (name && translated) { + return get_translation(name); + } else { + return name; + } +} + +/** + \return Tooltip giving more information on the filetype + \brief Get the tooltip for more information on the filetype +*/ +const char * +Output::get_filetypetooltip(bool translated) +{ + if (filetypetooltip && translated) { + return get_translation(filetypetooltip); + } else { + return filetypetooltip; + } +} + +/** + \return A dialog to get settings for this extension + \brief Create a dialog for preference for this extension + + Calls the implementation to get the preferences. +*/ +bool +Output::prefs () +{ + if (!loaded()) + set_state(Extension::STATE_LOADED); + if (!loaded()) return false; + + Gtk::Widget * controls; + controls = imp->prefs_output(this); + if (controls == nullptr) { + // std::cout << "No preferences for Output" << std::endl; + return true; + } + + Glib::ustring title = get_translation(this->get_name()); + PrefDialog *dialog = new PrefDialog(title, controls); + int response = dialog->run(); + dialog->hide(); + + delete dialog; + + return (response == Gtk::RESPONSE_OK); +} + +/** + \return None + \brief Save a document as a file + \param doc Document to save + \param filename File to save the document as + + This function does a little of the dirty work involved in saving + a document so that the implementation only has to worry about getting + bits on the disk. + + The big thing that it does is remove and read the fields that are + only used at runtime and shouldn't be saved. One that may surprise + people is the output extension. This is not saved so that the IDs + could be changed, and old files will still work properly. +*/ +void +Output::save(SPDocument *doc, gchar const *filename, bool detachbase) +{ + imp->setDetachBase(detachbase); + imp->save(this, doc, filename); + + return; +} + +} } /* namespace Inkscape, Extension */ + +/* + 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/extension/output.h b/src/extension/output.h new file mode 100644 index 0000000..8210858 --- /dev/null +++ b/src/extension/output.h @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl> + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + + +#ifndef INKSCAPE_EXTENSION_OUTPUT_H__ +#define INKSCAPE_EXTENSION_OUTPUT_H__ + +#include "extension.h" +class SPDocument; + +namespace Inkscape { +namespace Extension { + +class Output : public Extension { + gchar *mimetype; /**< What is the mime type this inputs? */ + gchar *extension; /**< The extension of the input files */ + gchar *filetypename; /**< A userfriendly name for the file type */ + gchar *filetypetooltip; /**< A more detailed description of the filetype */ + bool dataloss; /**< The extension causes data loss on save */ + +public: + class save_failed {}; /**< Generic failure for an undescribed reason */ + class save_cancelled {}; /**< Saving was cancelled */ + class no_extension_found {}; /**< Failed because we couldn't find an extension to match the filename */ + class file_read_only {}; /**< The existing file can not be opened for writing */ + class export_id_not_found { /**< The object ID requested for export could not be found in the document */ + public: + const gchar * const id; + export_id_not_found(const gchar * const id = nullptr) : id{id} {}; + }; + + Output(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory); + ~Output () override; + + bool check() override; + + void save (SPDocument *doc, + gchar const *filename, + bool detachbase = false); + bool prefs (); + gchar * get_mimetype(); + gchar * get_extension(); + const char * get_filetypename(bool translated=false); + const char * get_filetypetooltip(bool translated=false); + bool causes_dataloss() { return dataloss; }; +}; + +} } /* namespace Inkscape, Extension */ +#endif /* INKSCAPE_EXTENSION_OUTPUT_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/extension/patheffect.cpp b/src/extension/patheffect.cpp new file mode 100644 index 0000000..3ed53e7 --- /dev/null +++ b/src/extension/patheffect.cpp @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + + +#include "patheffect.h" + +#include "db.h" + +#include "object/sp-defs.h" + +#include "xml/repr.h" + + +namespace Inkscape { +namespace Extension { + +PathEffect::PathEffect (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory) + : Extension(in_repr, in_imp, base_directory) +{ + +} + +PathEffect::~PathEffect (void) += default; + +void +PathEffect::processPath (SPDocument * /*doc*/, Inkscape::XML::Node * /*path*/, Inkscape::XML::Node * /*def*/) +{ + + +} + +void +PathEffect::processPathEffects (SPDocument * doc, Inkscape::XML::Node * path) +{ + gchar const * patheffectlist = path->attribute("inkscape:path-effects"); + if (patheffectlist == nullptr) + return; + + gchar ** patheffects = g_strsplit(patheffectlist, ";", 128); + Inkscape::XML::Node * defs = doc->getDefs()->getRepr(); + + for (int i = 0; (i < 128) && (patheffects[i] != nullptr); i++) { + gchar * patheffect = patheffects[i]; + + // This is weird, they should all be references... but anyway + if (patheffect[0] != '#') continue; + + Inkscape::XML::Node * prefs = sp_repr_lookup_child(defs, "id", &(patheffect[1])); + if (prefs == nullptr) { + + continue; + } + + gchar const * ext_id = prefs->attribute("extension"); + if (ext_id == nullptr) { + + continue; + } + + Inkscape::Extension::PathEffect * peffect; + peffect = dynamic_cast<Inkscape::Extension::PathEffect *>(Inkscape::Extension::db.get(ext_id)); + if (peffect != nullptr) { + peffect->processPath(doc, path, prefs); + } + } + + g_strfreev(patheffects); + return; +} + + +} } /* namespace Inkscape, Extension */ + +/* + 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/extension/patheffect.h b/src/extension/patheffect.h new file mode 100644 index 0000000..b2dd7a0 --- /dev/null +++ b/src/extension/patheffect.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_PATHEFFECT_H__ +#define INKSCAPE_EXTENSION_PATHEFFECT_H__ + +#include "document.h" +#include "extension.h" + +namespace Inkscape { +namespace Extension { + +class PathEffect : public Extension { + +public: + PathEffect(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory); + ~PathEffect() override; + + void processPath (SPDocument * doc, + Inkscape::XML::Node * path, + Inkscape::XML::Node * def); + static void processPathEffects (SPDocument * doc, + Inkscape::XML::Node * path); +}; /* PathEffect */ + + +} } /* namespace Inkscape, Extension */ +#endif /* INKSCAPE_EXTENSION_PATHEFFECT_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/extension/plugins/CMakeLists.txt b/src/extension/plugins/CMakeLists.txt new file mode 100644 index 0000000..dc15b4a --- /dev/null +++ b/src/extension/plugins/CMakeLists.txt @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +add_subdirectory(grid2) diff --git a/src/extension/plugins/grid2/CMakeLists.txt b/src/extension/plugins/grid2/CMakeLists.txt new file mode 100644 index 0000000..1d23d83 --- /dev/null +++ b/src/extension/plugins/grid2/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +set(grid_PART_SRCS grid.cpp) + +include_directories( ${CMAKE_BINARY_DIR}/src ) + +add_library(grid2 SHARED EXCLUDE_FROM_ALL ${grid_PART_SRCS}) + +target_link_libraries(grid2 inkscape_base) + diff --git a/src/extension/plugins/grid2/grid.cpp b/src/extension/plugins/grid2/grid.cpp new file mode 100644 index 0000000..53dcecd --- /dev/null +++ b/src/extension/plugins/grid2/grid.cpp @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + \file grid.cpp + + A plug-in to add a grid creation effect into Inkscape. +*/ +/* + * Copyright (C) 2004-2005 Ted Gould <ted@gould.cx> + * Copyright (C) 2007 MenTaLguY <mental@rydia.net> + * Abhishek Sharma + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <gtkmm/box.h> +#include <gtkmm/adjustment.h> +#include <gtkmm/spinbutton.h> + +#include "desktop.h" + +#include "document.h" +#include "selection.h" +#include "2geom/geom.h" + +#include "object/sp-object.h" + +#include "svg/path-string.h" + +#include "extension/effect.h" +#include "extension/system.h" + +#include "util/units.h" + +#include "grid.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +/** + \brief A function to allocated anything -- just an example here + \param module Unused + \return Whether the load was successful +*/ +bool +Grid::load (Inkscape::Extension::Extension */*module*/) +{ + // std::cout << "Hey, I'm Grid, I'm loading!" << std::endl; + return TRUE; +} + +namespace { + +Glib::ustring build_lines(Geom::Rect bounding_area, + Geom::Point const &offset, Geom::Point const &spacing) +{ + + std::cout << "Building lines" << std::endl; + + Geom::Point point_offset(0.0, 0.0); + + SVG::PathString path_data; + + for ( int axis = Geom::X ; axis <= Geom::Y ; ++axis ) { + point_offset[axis] = offset[axis]; + + for (Geom::Point start_point = bounding_area.min(); + start_point[axis] + offset[axis] <= (bounding_area.max())[axis]; + start_point[axis] += spacing[axis]) { + Geom::Point end_point = start_point; + end_point[1-axis] = (bounding_area.max())[1-axis]; + + path_data.moveTo(start_point + point_offset) + .lineTo(end_point + point_offset); + } + } + std::cout << "Path data:" << path_data.c_str() << std::endl; + return path_data; +} + +} // namespace + +/** + \brief This actually draws the grid. + \param module The effect that was called (unused) + \param document What should be edited. +*/ +void +Grid::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + + std::cout << "Executing effect" << std::endl; + + Inkscape::Selection * selection = ((SPDesktop *)document)->selection; + + Geom::Rect bounding_area = Geom::Rect(Geom::Point(0,0), Geom::Point(100,100)); + if (selection->isEmpty()) { + /* get page size */ + SPDocument * doc = document->doc(); + bounding_area = Geom::Rect( Geom::Point(0,0), + Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px")) ); + } else { + Geom::OptRect bounds = selection->visualBounds(); + if (bounds) { + bounding_area = *bounds; + } + + gdouble doc_height = (document->doc())->getHeight().value("px"); + Geom::Rect temprec = Geom::Rect(Geom::Point(bounding_area.min()[Geom::X], doc_height - bounding_area.min()[Geom::Y]), + Geom::Point(bounding_area.max()[Geom::X], doc_height - bounding_area.max()[Geom::Y])); + + bounding_area = temprec; + } + + double scale = document->doc()->getDocumentScale().inverse()[Geom::X]; + + bounding_area *= Geom::Scale(scale); + Geom::Point spacings( scale * module->get_param_float("xspacing"), + scale * module->get_param_float("yspacing") ); + gdouble line_width = scale * module->get_param_float("lineWidth"); + Geom::Point offsets( scale * module->get_param_float("xoffset"), + scale * module->get_param_float("yoffset") ); + + Glib::ustring path_data(""); + + path_data = build_lines(bounding_area, offsets, spacings); + Inkscape::XML::Document * xml_doc = document->doc()->getReprDoc(); + + //XML Tree being used directly here while it shouldn't be. + Inkscape::XML::Node * current_layer = static_cast<SPDesktop *>(document)->currentLayer()->getRepr(); + Inkscape::XML::Node * path = xml_doc->createElement("svg:path"); + + path->setAttribute("d", path_data); + + std::ostringstream stringstream; + stringstream << "fill:none;stroke:#000000;stroke-width:" << line_width << "px"; + path->setAttribute("style", stringstream.str()); + + current_layer->appendChild(path); + Inkscape::GC::release(path); +} + +/** \brief A class to make an adjustment that uses Extension params */ +class PrefAdjustment : public Gtk::Adjustment { + /** Extension that this relates to */ + Inkscape::Extension::Extension * _ext; + /** The string which represents the parameter */ + char * _pref; +public: + /** \brief Make the adjustment using an extension and the string + describing the parameter. */ + PrefAdjustment(Inkscape::Extension::Extension * ext, char * pref) : + Gtk::Adjustment(0.0, 0.0, 10.0, 0.1), _ext(ext), _pref(pref) { + this->set_value(_ext->get_param_float(_pref)); + this->signal_value_changed().connect(sigc::mem_fun(this, &PrefAdjustment::val_changed)); + return; + }; + + void val_changed (); +}; /* class PrefAdjustment */ + +/** \brief A function to respond to the value_changed signal from the + adjustment. + + This function just grabs the value from the adjustment and writes + it to the parameter. Very simple, but yet beautiful. +*/ +void +PrefAdjustment::val_changed () +{ + // std::cout << "Value Changed to: " << this->get_value() << std::endl; + _ext->set_param_float(_pref, this->get_value()); + return; +} + +/** \brief A function to get the prefences for the grid + \param moudule Module which holds the params + \param view Unused today - may get style information in the future. + + Uses AutoGUI for creating the GUI. +*/ +Gtk::Widget * +Grid::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/) +{ + SPDocument * current_document = view->doc(); + + auto selected = ((SPDesktop *) view)->getSelection()->items(); + Inkscape::XML::Node * first_select = nullptr; + if (!selected.empty()) { + first_select = selected.front()->getRepr(); + } + + return module->autogui(current_document, first_select, changeSignal); +} + + + + +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* 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/extension/plugins/grid2/grid.h b/src/extension/plugins/grid2/grid.h new file mode 100644 index 0000000..f6d7d57 --- /dev/null +++ b/src/extension/plugins/grid2/grid.h @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2004-2005 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef __GRID_H + +#include "extension/implementation/implementation.h" + + +#include <glib.h> +#include <gmodule.h> +#include "inkscape-version.cpp" + + + +namespace Inkscape { +namespace Extension { + +class Effect; +class Extension; + +namespace Internal { + +/** \brief Implementation class of the GIMP gradient plugin. This mostly + just creates a namespace for the GIMP gradient plugin today. +*/ +class Grid : public Inkscape::Extension::Implementation::Implementation { + +public: + bool load(Inkscape::Extension::Extension *module) override; + void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + Gtk::Widget * prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override; + +}; + +}; /* namespace Internal */ +}; /* namespace Extension */ +}; /* namespace Inkscape */ + +extern "C" G_MODULE_EXPORT Inkscape::Extension::Implementation::Implementation* GetImplementation() { return new Inkscape::Extension::Internal::Grid(); } +extern "C" G_MODULE_EXPORT const gchar* GetInkscapeVersion() { return Inkscape::version_string; } +#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 : diff --git a/src/extension/plugins/grid2/libgrid2.inx b/src/extension/plugins/grid2/libgrid2.inx new file mode 100644 index 0000000..db95cd5 --- /dev/null +++ b/src/extension/plugins/grid2/libgrid2.inx @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="UTF-8" ?> +<!-- SPDX-License-Identifier: GPL-2.0-or-later --> +<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension"> +<_name>Grid2</_name> +<id>org.inkscape.effect.grid2</id> +<param name="lineWidth" _gui-text="Line Width:" type="float">1.0</param> +<param name="xspacing" _gui-text="Horizontal Spacing:" type="float" min="0.1" max="1000">10.0</param> +<param name="yspacing" _gui-text="Vertical Spacing:" type="float" min="0.1" max="1000">10.0</param> +<param name="xoffset" _gui-text="Horizontal Offset:" type="float" min="0.0" max="1000">0.0</param> +<param name="yoffset" _gui-text="Vertical Offset:" type="float" min="0.0" max="1000">0.0</param> +<effect> + <object-type>all</object-type> + <effects-menu> + <submenu _name="Render" > + <submenu _name="Grids" /> + </submenu> + </effects-menu> +<_menu-tip>Draw a path which is a grid</_menu-tip> +</effect> +<plugin name="libgrid2" /> +</inkscape-extension> diff --git a/src/extension/prefdialog/parameter-bool.cpp b/src/extension/prefdialog/parameter-bool.cpp new file mode 100644 index 0000000..609e727 --- /dev/null +++ b/src/extension/prefdialog/parameter-bool.cpp @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-bool.h" + +#include <gtkmm/box.h> +#include <gtkmm/checkbutton.h> + +#include "xml/node.h" +#include "extension/extension.h" +#include "preferences.h" + +namespace Inkscape { +namespace Extension { + +ParamBool::ParamBool(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // get value + if (xml->firstChild()) { + const char *value = xml->firstChild()->content(); + if (value) { + if (!strcmp(value, "true")) { + _value = true; + } else if (!strcmp(value, "false")) { + _value = false; + } else { + g_warning("Invalid default value ('%s') for parameter '%s' in extension '%s'", + value, _name, _extension->get_id()); + } + } + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getBool(pref_name(), _value); +} + +bool ParamBool::set(bool in) +{ + _value = in; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool(pref_name(), _value); + + return _value; +} + +bool ParamBool::get() const +{ + return _value; +} + +/** + * A check button which is Param aware. It works with the + * parameter to change it's value as the check button changes + * value. + */ +class ParamBoolCheckButton : public Gtk::CheckButton { +public: + /** + * Initialize the check button. + * This function sets the value of the checkbox to be that of the + * parameter, and then sets up a callback to \c on_toggle. + * + * @param param Which parameter to adjust on changing the check button + */ + ParamBoolCheckButton(ParamBool *param, char *label, sigc::signal<void> *changeSignal) + : Gtk::CheckButton(label) + , _pref(param) + , _changeSignal(changeSignal) { + this->set_active(_pref->get()); + this->signal_toggled().connect(sigc::mem_fun(this, &ParamBoolCheckButton::on_toggle)); + return; + } + + /** + * A function to respond to the check box changing. + * Adjusts the value of the preference to match that in the check box. + */ + void on_toggle (); + +private: + /** Param to change. */ + ParamBool *_pref; + sigc::signal<void> *_changeSignal; +}; + +void ParamBoolCheckButton::on_toggle() +{ + _pref->set(this->get_active()); + if (_changeSignal != nullptr) { + _changeSignal->emit(); + } + return; +} + +std::string ParamBool::value_to_string() const +{ + if (_value) { + return "true"; + } + return "false"; +} + +Gtk::Widget *ParamBool::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING)); + hbox->set_homogeneous(false); + + ParamBoolCheckButton * checkbox = Gtk::manage(new ParamBoolCheckButton(this, _text, changeSignal)); + checkbox->show(); + hbox->pack_start(*checkbox, false, false); + + hbox->show(); + + return dynamic_cast<Gtk::Widget *>(hbox); +} + +} /* namespace Extension */ +} /* 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/prefdialog/parameter-bool.h b/src/extension/prefdialog/parameter-bool.h new file mode 100644 index 0000000..52fe06a --- /dev/null +++ b/src/extension/prefdialog/parameter-bool.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INK_EXTENSION_PARAMBOOL_H +#define SEEN_INK_EXTENSION_PARAMBOOL_H +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" + +namespace Gtk { +class Widget; +} + +namespace Inkscape { +namespace XML { +class Node; +} + +namespace Extension { + +/** + * A boolean parameter. + */ +class ParamBool : public InxParameter { +public: + ParamBool(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + /** + * Returns the current state/value. + */ + bool get() const; + + /** + * A function to set the state/value. + * This function sets the internal value, but it also sets the value + * in the preferences structure. To put it in the right place pref_name() is used. + * + * @param in The value to set to + */ + bool set(bool in); + + /** + * Creates a bool check button for a bool parameter. + * Builds a hbox with a label and a check button in it. + */ + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + /** + * Appends 'true' or 'false'. + * @todo investigate. Returning a value that can then be appended would probably work better/safer. + */ + std::string value_to_string() const override; + +private: + /** Internal value. */ + bool _value = true; +}; + +} // namespace Extension +} // namespace Inkscape + +#endif // SEEN_INK_EXTENSION_PARAMBOOL_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/extension/prefdialog/parameter-color.cpp b/src/extension/prefdialog/parameter-color.cpp new file mode 100644 index 0000000..a17ca1b --- /dev/null +++ b/src/extension/prefdialog/parameter-color.cpp @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> + * Christopher Brown <audiere@gmail.com> + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-color.h" + +#include <iostream> +#include <sstream> + +#include <gtkmm/box.h> +#include <gtkmm/colorbutton.h> +#include <gtkmm/label.h> + +#include "color.h" +#include "preferences.h" + +#include "extension/extension.h" + +#include "ui/widget/color-notebook.h" + +#include "xml/node.h" + + +namespace Inkscape { +namespace Extension { + +ParamColor::ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // get value + unsigned int _value = 0x000000ff; // default to black + if (xml->firstChild()) { + const char *value = xml->firstChild()->content(); + if (value) { + _value = strtoul(value, nullptr, 0); + } + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getUInt(pref_name(), _value); + + _color.setValue(_value); + + _color_changed = _color.signal_changed.connect(sigc::mem_fun(this, &ParamColor::_onColorChanged)); + // TODO: SelectedColor does not properly emit signal_changed after dragging, so we also need the following + _color_released = _color.signal_released.connect(sigc::mem_fun(this, &ParamColor::_onColorChanged)); + + // parse appearance + if (_appearance) { + if (!strcmp(_appearance, "colorbutton")) { + _mode = COLOR_BUTTON; + } else { + g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'", + _appearance, _name, _extension->get_id()); + } + } +} + +ParamColor::~ParamColor() +{ + _color_changed.disconnect(); + _color_released.disconnect(); +} + +unsigned int ParamColor::set(unsigned int in) +{ + _color.setValue(in); + + return in; +} + +Gtk::Widget *ParamColor::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + if (changeSignal) { + _changeSignal = new sigc::signal<void>(*changeSignal); + } + + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox(false, GUI_PARAM_WIDGETS_SPACING)); + if (_mode == COLOR_BUTTON) { + Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START)); + label->show(); + hbox->pack_start(*label, true, true); + + Gdk::RGBA rgba; + rgba.set_red_u (((_color.value() >> 24) & 255) << 8); + rgba.set_green_u(((_color.value() >> 16) & 255) << 8); + rgba.set_blue_u (((_color.value() >> 8) & 255) << 8); + rgba.set_alpha_u(((_color.value() >> 0) & 255) << 8); + + // TODO: It would be nicer to have a custom Gtk::ColorButton() implementation here, + // that wraps an Inkscape::UI::Widget::ColorNotebook into a new dialog + _color_button = Gtk::manage(new Gtk::ColorButton(rgba)); + _color_button->set_title(_text); + _color_button->set_use_alpha(); + _color_button->show(); + hbox->pack_end(*_color_button, false, false); + + _color_button->signal_color_set().connect(sigc::mem_fun(this, &ParamColor::_onColorButtonChanged)); + } else { + Gtk::Widget *selector = Gtk::manage(new Inkscape::UI::Widget::ColorNotebook(_color)); + hbox->pack_start(*selector, true, true, 0); + selector->show(); + } + hbox->show(); + return hbox; + +} + +void ParamColor::_onColorChanged() +{ + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setUInt(pref_name(), _color.value()); + + if (_changeSignal) + _changeSignal->emit(); +} + +void ParamColor::_onColorButtonChanged() +{ + Gdk::RGBA rgba = _color_button->get_rgba(); + unsigned int value = ((rgba.get_red_u() >> 8) << 24) + + ((rgba.get_green_u() >> 8) << 16) + + ((rgba.get_blue_u() >> 8) << 8) + + ((rgba.get_alpha_u() >> 8) << 0); + set(value); +} + +std::string ParamColor::value_to_string() const +{ + char value_string[16]; + snprintf(value_string, 16, "%u", _color.value()); + return value_string; +} + +}; /* namespace Extension */ +}; /* namespace Inkscape */ diff --git a/src/extension/prefdialog/parameter-color.h b/src/extension/prefdialog/parameter-color.h new file mode 100644 index 0000000..4fef4b6 --- /dev/null +++ b/src/extension/prefdialog/parameter-color.h @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef SEEN_INK_EXTENSION_PARAMCOLOR_H__ +#define SEEN_INK_EXTENSION_PARAMCOLOR_H__ +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" +#include "ui/selected-color.h" + +namespace Gtk { +class Widget; +class ColorButton; +} + +namespace Inkscape { +namespace XML { +class Node; +} + +namespace Extension { + +class ParamColor : public InxParameter { +public: + enum AppearanceMode { + DEFAULT, COLOR_BUTTON + }; + + ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + ~ParamColor() override; + + /** Returns \c _value, with a \i const to protect it. */ + unsigned int get() const { return _color.value(); } + + unsigned int set(unsigned int in); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + std::string value_to_string() const override; + + sigc::signal<void> *_changeSignal; + +private: + void _onColorChanged(); + void _onColorButtonChanged(); + + /** Internal value of this parameter */ + Inkscape::UI::SelectedColor _color; + + sigc::connection _color_changed; + sigc::connection _color_released; + + Gtk::ColorButton *_color_button; + + /** appearance mode **/ + AppearanceMode _mode = DEFAULT; +}; // class ParamColor + +} // namespace Extension +} // namespace Inkscape + +#endif // SEEN_INK_EXTENSION_PARAMCOLOR_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/extension/prefdialog/parameter-float.cpp b/src/extension/prefdialog/parameter-float.cpp new file mode 100644 index 0000000..ad8dc94 --- /dev/null +++ b/src/extension/prefdialog/parameter-float.cpp @@ -0,0 +1,193 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-float.h" + +#include <gtkmm/adjustment.h> +#include <gtkmm/box.h> + +#include "preferences.h" + +#include "extension/extension.h" + +#include "ui/widget/spin-scale.h" +#include "ui/widget/spinbutton.h" + +#include "xml/node.h" + + +namespace Inkscape { +namespace Extension { + +ParamFloat::ParamFloat(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // get value + if (xml->firstChild()) { + const char *value = xml->firstChild()->content(); + if (value) { + _value = g_ascii_strtod(value, nullptr); + } + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getDouble(pref_name(), _value); + + // parse and apply limits + const char *min = xml->attribute("min"); + if (min) { + _min = g_ascii_strtod(min, nullptr); + } + + const char *max = xml->attribute("max"); + if (max) { + _max = g_ascii_strtod(max, nullptr); + } + + if (_value < _min) { + _value = _min; + } + + if (_value > _max) { + _value = _max; + } + + // parse precision + const char *precision = xml->attribute("precision"); + if (precision != nullptr) { + _precision = strtol(precision, nullptr, 0); + } + + + // parse appearance + if (_appearance) { + if (!strcmp(_appearance, "full")) { + _mode = FULL; + } else { + g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'", + _appearance, _name, _extension->get_id()); + } + } +} + +/** + * A function to set the \c _value. + * + * This function sets the internal value, but it also sets the value + * in the preferences structure. To put it in the right place \c pref_name() is used. + * + * @param in The value to set to. + */ +float ParamFloat::set(float in) +{ + _value = in; + if (_value > _max) { + _value = _max; + } + if (_value < _min) { + _value = _min; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setDouble(pref_name(), _value); + + return _value; +} + +std::string ParamFloat::value_to_string() const +{ + char value_string[G_ASCII_DTOSTR_BUF_SIZE]; + // TODO: Some strange rounding is going on here, resulting in parameter values quite different + // from the original string value. Needs some investigation to make it less bad. + // See also https://gitlab.gnome.org/GNOME/glib/issues/964 + g_ascii_dtostr(value_string, G_ASCII_DTOSTR_BUF_SIZE, _value); + return value_string; +} + +/** A class to make an adjustment that uses Extension params. */ +class ParamFloatAdjustment : public Gtk::Adjustment { + /** The parameter to adjust. */ + ParamFloat *_pref; + sigc::signal<void> *_changeSignal; +public: + /** Make the adjustment using an extension and the string + describing the parameter. */ + ParamFloatAdjustment(ParamFloat *param, sigc::signal<void> *changeSignal) + : Gtk::Adjustment(0.0, param->min(), param->max(), 0.1, 1.0, 0) + , _pref(param) + , _changeSignal(changeSignal) { + this->set_value(_pref->get()); + this->signal_value_changed().connect(sigc::mem_fun(this, &ParamFloatAdjustment::val_changed)); + return; + }; + + void val_changed (); +}; /* class ParamFloatAdjustment */ + +/** + * A function to respond to the value_changed signal from the adjustment. + * + * This function just grabs the value from the adjustment and writes + * it to the parameter. Very simple, but yet beautiful. + */ +void ParamFloatAdjustment::val_changed() +{ + _pref->set(this->get_value()); + if (_changeSignal != nullptr) { + _changeSignal->emit(); + } + return; +} + +/** + * Creates a Float Adjustment for a float parameter. + * + * Builds a hbox with a label and a float adjustment in it. + */ +Gtk::Widget *ParamFloat::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox(false, GUI_PARAM_WIDGETS_SPACING)); + + auto pfa = new ParamFloatAdjustment(this, changeSignal); + Glib::RefPtr<Gtk::Adjustment> fadjust(pfa); + + if (_mode == FULL) { + + Glib::ustring text; + if (_text != nullptr) + text = _text; + UI::Widget::SpinScale *scale = Gtk::manage(new UI::Widget::SpinScale(text, fadjust, _precision)); + scale->set_size_request(400, -1); + scale->show(); + hbox->pack_start(*scale, true, true); + + } + else if (_mode == DEFAULT) { + + Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START)); + label->show(); + hbox->pack_start(*label, true, true); + + auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 0.1, _precision)); + spin->show(); + hbox->pack_start(*spin, false, false); + } + + hbox->show(); + + return dynamic_cast<Gtk::Widget *>(hbox); +} + + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/parameter-float.h b/src/extension/prefdialog/parameter-float.h new file mode 100644 index 0000000..4c28cb5 --- /dev/null +++ b/src/extension/prefdialog/parameter-float.h @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INK_EXTENSION_PARAMFLOAT_H_SEEN +#define INK_EXTENSION_PARAMFLOAT_H_SEEN + +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" + +namespace Gtk { +class Widget; +} + +namespace Inkscape { +namespace XML { +class Node; +} + +namespace Extension { + +class ParamFloat : public InxParameter { +public: + enum AppearanceMode { + DEFAULT, FULL + }; + + ParamFloat(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + /** Returns \c _value. */ + float get() const { return _value; } + + float set(float in); + + float max () { return _max; } + + float min () { return _min; } + + float precision () { return _precision; } + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + std::string value_to_string() const override; + +private: + /** Internal value. */ + float _value = 0; + + /** limits */ + // TODO: do these defaults make sense or should we be unbounded by default? + float _min = 0; + float _max = 10; + + /** numeric precision (i.e. number of digits) */ + int _precision = 1; + + /** appearance mode **/ + AppearanceMode _mode = DEFAULT; +}; + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* INK_EXTENSION_PARAMFLOAT_H_SEEN */ + +/* + 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/extension/prefdialog/parameter-int.cpp b/src/extension/prefdialog/parameter-int.cpp new file mode 100644 index 0000000..8a8e49e --- /dev/null +++ b/src/extension/prefdialog/parameter-int.cpp @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-int.h" + +#include <gtkmm/adjustment.h> +#include <gtkmm/box.h> + +#include "preferences.h" + +#include "extension/extension.h" + +#include "ui/widget/spinbutton.h" +#include "ui/widget/spin-scale.h" + +#include "xml/node.h" + + +namespace Inkscape { +namespace Extension { + + +ParamInt::ParamInt(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // get value + if (xml->firstChild()) { + const char *value = xml->firstChild()->content(); + if (value) { + _value = strtol(value, nullptr, 0); + } + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getInt(pref_name(), _value); + + // parse and apply limits + const char *min = xml->attribute("min"); + if (min) { + _min = strtol(min, nullptr, 0); + } + + const char *max = xml->attribute("max"); + if (max) { + _max = strtol(max, nullptr, 0); + } + + if (_value < _min) { + _value = _min; + } + + if (_value > _max) { + _value = _max; + } + + // parse appearance + if (_appearance) { + if (!strcmp(_appearance, "full")) { + _mode = FULL; + } else { + g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'", + _appearance, _name, _extension->get_id()); + } + } +} + +/** + * A function to set the \c _value. + * This function sets the internal value, but it also sets the value + * in the preferences structure. To put it in the right place \c pref_name() is used. + * + * @param in The value to set to. + */ +int ParamInt::set(int in) +{ + _value = in; + if (_value > _max) { + _value = _max; + } + if (_value < _min) { + _value = _min; + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setInt(pref_name(), _value); + + return _value; +} + +/** A class to make an adjustment that uses Extension params. */ +class ParamIntAdjustment : public Gtk::Adjustment { + /** The parameter to adjust. */ + ParamInt *_pref; + sigc::signal<void> *_changeSignal; +public: + /** Make the adjustment using an extension and the string describing the parameter. */ + ParamIntAdjustment(ParamInt *param, sigc::signal<void> *changeSignal) + : Gtk::Adjustment(0.0, param->min(), param->max(), 1.0, 10.0, 0) + , _pref(param) + , _changeSignal(changeSignal) + { + this->set_value(_pref->get()); + this->signal_value_changed().connect(sigc::mem_fun(this, &ParamIntAdjustment::val_changed)); + }; + + void val_changed (); +}; /* class ParamIntAdjustment */ + +/** + * A function to respond to the value_changed signal from the adjustment. + * + * This function just grabs the value from the adjustment and writes + * it to the parameter. Very simple, but yet beautiful. + */ +void ParamIntAdjustment::val_changed() +{ + _pref->set((int)this->get_value()); + if (_changeSignal != nullptr) { + _changeSignal->emit(); + } +} + +/** + * Creates a Int Adjustment for a int parameter. + * + * Builds a hbox with a label and a int adjustment in it. + */ +Gtk::Widget * +ParamInt::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox(false, GUI_PARAM_WIDGETS_SPACING)); + + auto pia = new ParamIntAdjustment(this, changeSignal); + Glib::RefPtr<Gtk::Adjustment> fadjust(pia); + + if (_mode == FULL) { + + Glib::ustring text; + if (_text != nullptr) + text = _text; + UI::Widget::SpinScale *scale = Gtk::manage(new UI::Widget::SpinScale(text, fadjust, 0)); + scale->set_size_request(400, -1); + scale->show(); + hbox->pack_start(*scale, true, true); + } else if (_mode == DEFAULT) { + Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START)); + label->show(); + hbox->pack_start(*label, true, true); + + auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 1.0, 0)); + spin->show(); + hbox->pack_start(*spin, false, false); + } + + hbox->show(); + + return dynamic_cast<Gtk::Widget *>(hbox); +} + +std::string ParamInt::value_to_string() const +{ + char value_string[32]; + snprintf(value_string, 32, "%d", _value); + return value_string; +} + +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/prefdialog/parameter-int.h b/src/extension/prefdialog/parameter-int.h new file mode 100644 index 0000000..da43eb7 --- /dev/null +++ b/src/extension/prefdialog/parameter-int.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INK_EXTENSION_PARAMINT_H_SEEN +#define INK_EXTENSION_PARAMINT_H_SEEN + +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" + +namespace Gtk { +class Widget; +} + +namespace Inkscape { +namespace XML { +class Node; +} + +namespace Extension { + +class ParamInt : public InxParameter { +public: + enum AppearanceMode { + DEFAULT, FULL + }; + + ParamInt(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + /** Returns \c _value. */ + int get() const { return _value; } + + int set(int in); + + int max () { return _max; } + + int min () { return _min; } + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + std::string value_to_string() const override; + +private: + /** Internal value. */ + int _value = 0; + + /** limits */ + // TODO: do these defaults make sense or should we be unbounded by default? + int _min = 0; + int _max = 10; + + /** appearance mode **/ + AppearanceMode _mode = DEFAULT; +}; + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* INK_EXTENSION_PARAMINT_H_SEEN */ + +/* + 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/extension/prefdialog/parameter-notebook.cpp b/src/extension/prefdialog/parameter-notebook.cpp new file mode 100644 index 0000000..050c84f --- /dev/null +++ b/src/extension/prefdialog/parameter-notebook.cpp @@ -0,0 +1,285 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * Notebook and NotebookPage parameters for extensions. + */ + +/* + * Authors: + * Johan Engelen <johan@shouraizou.nl> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2006 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-notebook.h" + +#include <unordered_set> + +#include <gtkmm/box.h> +#include <gtkmm/notebook.h> + +#include "preferences.h" + +#include "extension/extension.h" + +#include "xml/node.h" + +namespace Inkscape { +namespace Extension { + + +ParamNotebook::ParamNotebookPage::ParamNotebookPage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // Read XML tree of page and parse parameters + if (xml) { + Inkscape::XML::Node *child_repr = xml->firstChild(); + while (child_repr) { + const char *chname = child_repr->name(); + if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + chname += strlen(INKSCAPE_EXTENSION_NS); + } + if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility + chname++; + } + + if (InxWidget::is_valid_widget_name(chname)) { + InxWidget *widget = InxWidget::make(child_repr, _extension); + if (widget) { + _children.push_back(widget); + } + } else if (child_repr->type() == XML::ELEMENT_NODE) { + g_warning("Invalid child element ('%s') in notebook page in extension '%s'.", + chname, _extension->get_id()); + } else if (child_repr->type() != XML::COMMENT_NODE){ + g_warning("Invalid child element found in notebook page in extension '%s'.", _extension->get_id()); + } + + child_repr = child_repr->next(); + } + } +} + + +/** + * Creates a notebookpage widget for a notebook. + * + * Builds a notebook page (a vbox) and puts parameters on it. + */ +Gtk::Widget *ParamNotebook::ParamNotebookPage::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::VBox * vbox = Gtk::manage(new Gtk::VBox); + vbox->set_border_width(GUI_BOX_MARGIN); + vbox->set_spacing(GUI_BOX_SPACING); + + // add parameters onto page (if any) + for (auto child : _children) { + Gtk::Widget *child_widget = child->get_widget(changeSignal); + if (child_widget) { + int indent = child->get_indent(); + child_widget->set_margin_start(indent *GUI_INDENTATION); + vbox->pack_start(*child_widget, false, true, 0); // fill=true does not have an effect here, but allows the + // child to choose to expand by setting hexpand/vexpand + + const char *tooltip = child->get_tooltip(); + if (tooltip) { + child_widget->set_tooltip_text(tooltip); + } + } + } + + vbox->show(); + + return dynamic_cast<Gtk::Widget *>(vbox); +} + +/** End ParamNotebookPage **/ + + + +/** ParamNotebook **/ + +ParamNotebook::ParamNotebook(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // Read XML tree to add pages (allow _page for backwards compatibility) + if (xml) { + Inkscape::XML::Node *child_repr = xml->firstChild(); + while (child_repr) { + const char *chname = child_repr->name(); + if (chname && (!strcmp(chname, INKSCAPE_EXTENSION_NS "page") || + !strcmp(chname, INKSCAPE_EXTENSION_NS "_page") )) { + ParamNotebookPage *page; + page = new ParamNotebookPage(child_repr, ext); + + if (page) { + _children.push_back(page); + } + } else if (child_repr->type() == XML::ELEMENT_NODE) { + g_warning("Invalid child element ('%s') for parameter '%s' in extension '%s'. Expected 'page'.", + chname, _name, _extension->get_id()); + } else if (child_repr->type() != XML::COMMENT_NODE){ + g_warning("Invalid child element found in parameter '%s' in extension '%s'. Expected 'page'.", + _name, _extension->get_id()); + } + child_repr = child_repr->next(); + } + } + if (_children.empty()) { + g_warning("No (valid) pages for parameter '%s' in extension '%s'", _name, _extension->get_id()); + } + + // check for duplicate page names + std::unordered_set<std::string> names; + for (auto child : _children) { + ParamNotebookPage *page = static_cast<ParamNotebookPage *>(child); + auto ret = names.emplace(page->_name); + if (!ret.second) { + g_warning("Duplicate page name ('%s') for parameter '%s' in extension '%s'.", + page->_name, _name, _extension->get_id()); + } + } + + // get value (initialize with value of first page if pref is empty) + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getString(pref_name()); + + if (_value.empty()) { + if (!_children.empty()) { + ParamNotebookPage *first_page = dynamic_cast<ParamNotebookPage *>(_children[0]); + _value = first_page->_name; + } + } +} + + +/** + * A function to set the \c _value. + * + * This function sets the internal value, but it also sets the value + * in the preferences structure. To put it in the right place \c pref_name() is used. + * + * @param in The number of the page to set as new value. + */ +const Glib::ustring& ParamNotebook::set(const int in) +{ + int i = in < _children.size() ? in : _children.size()-1; + ParamNotebookPage *page = dynamic_cast<ParamNotebookPage *>(_children[i]); + + if (page) { + _value = page->_name; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(pref_name(), _value); + } + + return _value; +} + +std::string ParamNotebook::value_to_string() const +{ + return _value; +} + + +/** A special category of Gtk::Notebook to handle notebook parameters. */ +class NotebookWidget : public Gtk::Notebook { +private: + ParamNotebook *_pref; +public: + /** + * Build a notebookpage preference for the given parameter. + * @param pref Where to get the string (pagename) from, and where to put it when it changes. + */ + NotebookWidget(ParamNotebook *pref) + : Gtk::Notebook() + , _pref(pref) + , activated(false) + { + // don't have to set the correct page: this is done in ParamNotebook::get_widget hook function + this->signal_switch_page().connect(sigc::mem_fun(this, &NotebookWidget::changed_page)); + } + + void changed_page(Gtk::Widget *page, guint pagenum); + + bool activated; +}; + +/** + * Respond to the selected page of notebook changing. + * This function responds to the changing by reporting it to + * ParamNotebook. The change is only reported when the notebook + * is actually visible. This to exclude 'fake' changes when the + * notebookpages are added or removed. + */ +void NotebookWidget::changed_page(Gtk::Widget * /*page*/, guint pagenum) +{ + if (get_visible()) { + _pref->set((int)pagenum); + } +} + +/** + * Creates a Notebook widget for a notebook parameter. + * + * Builds a notebook and puts pages in it. + */ +Gtk::Widget *ParamNotebook::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + NotebookWidget *notebook = Gtk::manage(new NotebookWidget(this)); + + // add pages (if any) and switch to previously selected page + int current_page = -1; + int selected_page = -1; + for (auto child : _children) { + ParamNotebookPage *page = dynamic_cast<ParamNotebookPage *>(child); + g_assert(child); // A ParamNotebook has only children of type ParamNotebookPage. + // If we receive a non-page child here something is very wrong! + current_page++; + + Gtk::Widget *page_widget = page->get_widget(changeSignal); + + Glib::ustring page_text = page->_text; + if (page->_translatable != NO) { // translate unless explicitly marked untranslatable + page_text = page->get_translation(page_text.c_str()); + } + + notebook->append_page(*page_widget, page_text); + + if (_value == page->_name) { + selected_page = current_page; + } + } + if (selected_page >= 0) { + notebook->set_current_page(selected_page); + } + + notebook->show(); + + return static_cast<Gtk::Widget *>(notebook); +} + + +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/prefdialog/parameter-notebook.h b/src/extension/prefdialog/parameter-notebook.h new file mode 100644 index 0000000..b64e5c6 --- /dev/null +++ b/src/extension/prefdialog/parameter-notebook.h @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INK_EXTENSION_PARAMNOTEBOOK_H_SEEN +#define INK_EXTENSION_PARAMNOTEBOOK_H_SEEN + +/** \file + * Notebook parameter for extensions. + */ + +/* + * Author: + * Johan Engelen <johan@shouraizou.nl> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2006 Author + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" + +#include <vector> + +#include <glibmm/ustring.h> + + +namespace Gtk { +class Widget; +} + + +namespace Inkscape { +namespace Extension { + +class Extension; + + +/** A class to represent a notebook parameter of an extension. */ +class ParamNotebook : public InxParameter { +private: + /** Internal value. */ + Glib::ustring _value; + + /** + * A class to represent the pages of a notebook parameter of an extension. + */ + class ParamNotebookPage : public InxParameter { + friend class ParamNotebook; + public: + ParamNotebookPage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + // ParamNotebookPage is not a real parameter (it has no value), so make sure it does not return one + std::string value_to_string() const override { return ""; }; + }; /* class ParamNotebookPage */ + +public: + ParamNotebook(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + std::string value_to_string() const override; + + const Glib::ustring& get() { return _value; } + const Glib::ustring& set(const int in); +}; /* class ParamNotebook */ + + + + + +} // namespace Extension +} // namespace Inkscape + +#endif /* INK_EXTENSION_PARAMNOTEBOOK_H_SEEN */ + +/* + 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/extension/prefdialog/parameter-optiongroup.cpp b/src/extension/prefdialog/parameter-optiongroup.cpp new file mode 100644 index 0000000..954627e --- /dev/null +++ b/src/extension/prefdialog/parameter-optiongroup.cpp @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + *extension parameter for options with multiple predefined value choices + * + * Currently implemented as either Gtk::RadioButton or Gtk::ComboBoxText + */ + +/* + * Author: + * Johan Engelen <johan@shouraizou.nl> + * + * Copyright (C) 2006-2007 Johan Engelen + * Copyright (C) 2008 Jon A. Cruz + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-optiongroup.h" + +#include <unordered_set> + +#include <gtkmm/box.h> +#include <gtkmm/comboboxtext.h> +#include <gtkmm/radiobutton.h> + +#include "xml/node.h" +#include "extension/extension.h" +#include "preferences.h" + + +namespace Inkscape { +namespace Extension { + +ParamOptionGroup::ParamOptionGroup(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // Read valid optiongroup choices from XML tree, i,e. + // - <option> elements + // - <item> elements (for backwards-compatibility with params of type enum) + // - underscored variants of both (for backwards-compatibility) + if (xml) { + Inkscape::XML::Node *child_repr = xml->firstChild(); + while (child_repr) { + const char *chname = child_repr->name(); + if (chname && (!strcmp(chname, INKSCAPE_EXTENSION_NS "option") || + !strcmp(chname, INKSCAPE_EXTENSION_NS "_option") || + !strcmp(chname, INKSCAPE_EXTENSION_NS "item") || + !strcmp(chname, INKSCAPE_EXTENSION_NS "_item")) ) { + child_repr->setAttribute("name", "option"); // TODO: hack to allow options to be parameters + child_repr->setAttribute("gui-text", "option"); // TODO: hack to allow options to be parameters + ParamOptionGroupOption *param = new ParamOptionGroupOption(child_repr, ext, this); + choices.push_back(param); + } else if (child_repr->type() == XML::ELEMENT_NODE) { + g_warning("Invalid child element ('%s') for parameter '%s' in extension '%s'. Expected 'option'.", + chname, _name, _extension->get_id()); + } else if (child_repr->type() != XML::COMMENT_NODE){ + g_warning("Invalid child element found in parameter '%s' in extension '%s'. Expected 'option'.", + _name, _extension->get_id()); + } + child_repr = child_repr->next(); + } + } + if (choices.empty()) { + g_warning("No (valid) choices for parameter '%s' in extension '%s'", _name, _extension->get_id()); + } + + // check for duplicate option texts and values + std::unordered_set<std::string> texts; + std::unordered_set<std::string> values; + for (auto choice : choices) { + auto ret1 = texts.emplace(choice->_text); + if (!ret1.second) { + g_warning("Duplicate option text ('%s') for parameter '%s' in extension '%s'.", + choice->_text.c_str(), _name, _extension->get_id()); + } + auto ret2 = values.emplace(choice->_value); + if (!ret2.second) { + g_warning("Duplicate option value ('%s') for parameter '%s' in extension '%s'.", + choice->_value.c_str(), _name, _extension->get_id()); + } + } + + // get value (initialize with value of first choice if pref is empty) + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getString(pref_name()); + + if (_value.empty()) { + if (!choices.empty()) { + _value = choices[0]->_value; + } + } + + // parse appearance + // (we support "combo" and "radio"; "minimal" is for backwards-compatibility) + if (_appearance) { + if (!strcmp(_appearance, "combo") || !strcmp(_appearance, "minimal")) { + _mode = COMBOBOX; + } else if (!strcmp(_appearance, "radio")) { + _mode = RADIOBUTTON; + } else { + g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'", + _appearance, _name, _extension->get_id()); + } + } +} + +ParamOptionGroup::~ParamOptionGroup () +{ + // destroy choice strings + for (auto choice : choices) { + delete choice; + } +} + + +/** + * A function to set the \c _value. + * + * This function sets ONLY the internal value, but it also sets the value + * in the preferences structure. To put it in the right place \c pref_name() is used. + * + * @param in The value to set. + */ +const Glib::ustring& ParamOptionGroup::set(Glib::ustring in) +{ + if (contains(in)) { + _value = in; + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(pref_name(), _value.c_str()); + } else { + g_warning("Could not set value ('%s') for parameter '%s' in extension '%s'. Not a valid choice.", + in.c_str(), _name, _extension->get_id()); + } + + return _value; +} + +bool ParamOptionGroup::contains(const Glib::ustring text) const +{ + for (auto choice : choices) { + if (choice->_value == text) { + return true; + } + } + + return false; +} + +std::string ParamOptionGroup::value_to_string() const +{ + return _value; +} + +/** + * Returns the value for the options label parameter + */ +Glib::ustring ParamOptionGroup::value_from_label(const Glib::ustring label) +{ + Glib::ustring value; + + for (auto choice : choices) { + if (choice->_text == label) { + value = choice->_value; + break; + } + } + + return value; +} + + + +/** A special RadioButton class to use in ParamOptionGroup. */ +class RadioWidget : public Gtk::RadioButton { +private: + ParamOptionGroup *_pref; + sigc::signal<void> *_changeSignal; +public: + RadioWidget(Gtk::RadioButtonGroup& group, const Glib::ustring& label, + ParamOptionGroup *pref, sigc::signal<void> *changeSignal) + : Gtk::RadioButton(group, label) + , _pref(pref) + , _changeSignal(changeSignal) + { + add_changesignal(); + }; + + void add_changesignal() { + this->signal_toggled().connect(sigc::mem_fun(this, &RadioWidget::changed)); + }; + + void changed(); +}; + +/** + * Respond to the selected radiobutton changing. + * + * This function responds to the radiobutton selection changing by grabbing the value + * from the text box and putting it in the parameter. + */ +void RadioWidget::changed() +{ + if (this->get_active()) { + Glib::ustring value = _pref->value_from_label(this->get_label()); + _pref->set(value.c_str()); + } + + if (_changeSignal) { + _changeSignal->emit(); + } +} + + +/** A special ComboBoxText class to use in ParamOptionGroup. */ +class ComboWidget : public Gtk::ComboBoxText { +private: + ParamOptionGroup *_pref; + sigc::signal<void> *_changeSignal; + +public: + ComboWidget(ParamOptionGroup *pref, sigc::signal<void> *changeSignal) + : _pref(pref) + , _changeSignal(changeSignal) + { + this->signal_changed().connect(sigc::mem_fun(this, &ComboWidget::changed)); + } + + ~ComboWidget() override = default; + + void changed(); +}; + +void ComboWidget::changed() +{ + if (_pref) { + Glib::ustring value = _pref->value_from_label(get_active_text()); + _pref->set(value.c_str()); + } + + if (_changeSignal) { + _changeSignal->emit(); + } +} + + + +/** + * Creates the widget for the optiongroup parameter. + */ +Gtk::Widget *ParamOptionGroup::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING)); + + Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START)); + hbox->pack_start(*label, false, false); + + if (_mode == COMBOBOX) { + ComboWidget *combo = Gtk::manage(new ComboWidget(this, changeSignal)); + + for (auto choice : choices) { + combo->append(choice->_text); + if (choice->_value == _value) { + combo->set_active_text(choice->_text); + } + } + + if (combo->get_active_row_number() == -1) { + combo->set_active(0); + } + + hbox->pack_end(*combo, false, false); + } else if (_mode == RADIOBUTTON) { + label->set_valign(Gtk::ALIGN_START); // align label and first radio + + Gtk::Box *radios = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0)); + Gtk::RadioButtonGroup group; + + for (auto choice : choices) { + RadioWidget *radio = Gtk::manage(new RadioWidget(group, choice->_text, this, changeSignal)); + radios->pack_start(*radio, true, true); + if (choice->_value == _value) { + radio->set_active(); + } + } + + hbox->pack_end(*radios, false, false); + } + + hbox->show_all(); + return static_cast<Gtk::Widget *>(hbox); +} + + +ParamOptionGroup::ParamOptionGroupOption::ParamOptionGroupOption(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext, + const Inkscape::Extension::ParamOptionGroup *parent) + : InxParameter(xml, ext) +{ + // get content (=label) of option and translate it + const char *text = nullptr; + if (xml->firstChild()) { + text = xml->firstChild()->content(); + } + if (text) { + if (_translatable != NO) { // translate unless explicitly marked untranslatable + _text = get_translation(text); + } else { + _text = text; + } + } else { + g_warning("Missing content in option of parameter '%s' in extension '%s'.", + parent->_name, _extension->get_id()); + } + + // get string value of option + const char *value = xml->attribute("value"); + if (value) { + _value = value; + } else { + if (text) { + const char *name = xml->name(); + if (!strcmp(name, INKSCAPE_EXTENSION_NS "item") || !strcmp(name, INKSCAPE_EXTENSION_NS "_item")) { + _value = text; // use untranslated UI text as value (for backwards-compatibility) + } else { + _value = _text; // use translated UI text as value + } + } else { + g_warning("Missing value for option '%s' of parameter '%s' in extension '%s'.", + _text.c_str(), parent->_name, _extension->get_id()); + } + } +} + + + +} /* namespace Extension */ +} /* 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/extension/prefdialog/parameter-optiongroup.h b/src/extension/prefdialog/parameter-optiongroup.h new file mode 100644 index 0000000..533b131 --- /dev/null +++ b/src/extension/prefdialog/parameter-optiongroup.h @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN +#define INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN + +/** \file + * extension parameter for options with multiple predefined value choices + * + * Currently implemented as either Gtk::RadioButton or Gtk::ComboBoxText + */ + +/* + * Authors: + * Johan Engelen <johan@shouraizou.nl> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2006-2007 Johan Engelen + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" + +#include <vector> + +#include <glibmm/ustring.h> + +namespace Gtk { +class Widget; +} + +namespace Inkscape { +namespace Extension { + +class Extension; + + + +// \brief A class to represent an optiongroup (option with multiple predefined value choices) parameter of an extension +class ParamOptionGroup : public InxParameter { +public: + enum AppearanceMode { + RADIOBUTTON, COMBOBOX + }; + + ParamOptionGroup(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + ~ParamOptionGroup() override; + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + std::string value_to_string() const override; + + Glib::ustring value_from_label(const Glib::ustring label); + + const Glib::ustring& get() const { return _value; } + + const Glib::ustring& set(const Glib::ustring in); + + /** + * @returns true if text is a valid choice for this option group + * @param text string value to check (this is an actual option value, not the user-visible option name!) + */ + bool contains(const Glib::ustring text) const; + +private: + /** \brief Internal value. */ + Glib::ustring _value; + + /** appearance mode **/ + AppearanceMode _mode = RADIOBUTTON; + + /* For internal use only. */ + class ParamOptionGroupOption : public InxParameter { + friend class ParamOptionGroup; + public: + ParamOptionGroupOption(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext, + const Inkscape::Extension::ParamOptionGroup *parent); + private: + Glib::ustring _value; + Glib::ustring _text; + }; + + std::vector<ParamOptionGroupOption *> choices; /**< List of available options for the option group */ +}; /* class ParamOptionGroup */ + + + + + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /*INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN */ + +/* + 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/extension/prefdialog/parameter-path.cpp b/src/extension/prefdialog/parameter-path.cpp new file mode 100644 index 0000000..2784c09 --- /dev/null +++ b/src/extension/prefdialog/parameter-path.cpp @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Path parameter for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-path.h" + +#include <boost/algorithm/string/case_conv.hpp> +#include <boost/algorithm/string/join.hpp> + +#include <glibmm/i18n.h> +#include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> +#include <glibmm/regex.h> + +#include <gtkmm/box.h> +#include <gtkmm/button.h> +#include <gtkmm/dialog.h> +#include <gtkmm/entry.h> + +#include "include/gtkmm_version.h" +#if GTKMM_CHECK_VERSION(3,24,0) // unfortunately GtkFileChooserNative (since gtk 3.20) was not wrapped until gtkmm 3.24 +# include <gtkmm/filechoosernative.h> +#else +# include <gtkmm/filechooserdialog.h> +#endif + +#include "xml/node.h" +#include "extension/extension.h" +#include "preferences.h" + +namespace Inkscape { +namespace Extension { + +ParamPath::ParamPath(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // get value + const char *value = nullptr; + if (xml->firstChild()) { + value = xml->firstChild()->content(); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getString(pref_name()); + + if (_value.empty() && value) { + _value = value; + } + + // parse selection mode + const char *mode = xml->attribute("mode"); + if (mode) { + if (!strcmp(mode, "file")) { + // this is the default + } else if (!strcmp(mode, "files")) { + _select_multiple = true; + } else if (!strcmp(mode, "folder")) { + _mode = FOLDER; + } else if (!strcmp(mode, "folders")) { + _mode = FOLDER; + _select_multiple = true; + } else if (!strcmp(mode, "file_new")) { + _mode = FILE_NEW; + } else if (!strcmp(mode, "folder_new")) { + _mode = FOLDER_NEW; + } else { + g_warning("Invalid value ('%s') for mode of parameter '%s' in extension '%s'", + mode, _name, _extension->get_id()); + } + } + + // parse filetypes + const char *filetypes = xml->attribute("filetypes"); + if (filetypes) { + _filetypes = Glib::Regex::split_simple("," , filetypes); + } +} + +/** + * A function to set the \c _value. + * + * This function sets the internal value, but it also sets the value + * in the preferences structure. To put it in the right place \c pref_name() is used. + * + * @param in The value to set to. + */ +const std::string& ParamPath::set(const std::string in) +{ + _value = in; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(pref_name(), _value); + + return _value; +} + +std::string ParamPath::value_to_string() const +{ + if (!Glib::path_is_absolute(_value)) { + return Glib::build_filename(_extension->get_base_directory(), _value); + } else { + return _value; + } +} + + +/** A special type of Gtk::Entry to handle path parameters. */ +class ParamPathEntry : public Gtk::Entry { +private: + ParamPath *_pref; + sigc::signal<void> *_changeSignal; +public: + /** + * Build a string preference for the given parameter. + * @param pref Where to get the string from, and where to put it + * when it changes. + */ + ParamPathEntry(ParamPath *pref, sigc::signal<void> *changeSignal) + : Gtk::Entry() + , _pref(pref) + , _changeSignal(changeSignal) + { + this->set_text(_pref->get()); + this->signal_changed().connect(sigc::mem_fun(this, &ParamPathEntry::changed_text)); + }; + void changed_text(); +}; + + +/** + * Respond to the text box changing. + * + * This function responds to the box changing by grabbing the value + * from the text box and putting it in the parameter. + */ +void ParamPathEntry::changed_text() +{ + std::string data = this->get_text(); + _pref->set(data.c_str()); + if (_changeSignal != nullptr) { + _changeSignal->emit(); + } +} + +/** + * Creates a text box for the string parameter. + * + * Builds a hbox with a label and a text box in it. + */ +Gtk::Widget *ParamPath::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox(false, GUI_PARAM_WIDGETS_SPACING)); + Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START)); + label->show(); + hbox->pack_start(*label, false, false); + + ParamPathEntry *textbox = Gtk::manage(new ParamPathEntry(this, changeSignal)); + textbox->show(); + hbox->pack_start(*textbox, true, true); + _entry = textbox; + + Gtk::Button *button = Gtk::manage(new Gtk::Button("…")); + button->show(); + hbox->pack_end(*button, false, false); + button->signal_clicked().connect(sigc::mem_fun(*this, &ParamPath::on_button_clicked)); + + hbox->show(); + + return dynamic_cast<Gtk::Widget *>(hbox); +} + +/** + * Create and show the file chooser dialog when the "…" button is clicked + * Then set the value of the ParamPathEntry holding the current value accordingly + */ +void ParamPath::on_button_clicked() +{ + // set-up action and dialog title according to 'mode' + Gtk::FileChooserAction action; + std::string dialog_title; + if (_mode == FILE) { + // pick the "save" variants here - otherwise the dialog will only accept existing files + action = Gtk::FILE_CHOOSER_ACTION_OPEN; + if (_select_multiple) { + dialog_title = _("Select existing files"); + } else { + dialog_title = _("Select existing file"); + } + } else if (_mode == FOLDER) { + // pick the "create" variant here - otherwise the dialog will only accept existing folders + action = Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER; + if (_select_multiple) { + dialog_title = _("Select existing folders"); + } else { + dialog_title = _("Select existing folder"); + } + } else if (_mode == FILE_NEW) { + action = Gtk::FILE_CHOOSER_ACTION_SAVE; + dialog_title = _("Choose file name"); + } else if (_mode == FOLDER_NEW) { + action = Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER; + dialog_title = _("Choose folder name"); + } else { + g_assert_not_reached(); + } + + // create file chooser dialog +#if GTKMM_CHECK_VERSION(3,24,0) // unfortunately GtkFileChooserNative (since gtk 3.20) was not wrapped until gtkmm 3.24 + Glib::RefPtr<Gtk::FileChooserNative> file_chooser = + Gtk::FileChooserNative::create(dialog_title + "…", action, _("Select")); +#else + Gtk::FileChooserDialog file_chooser_instance(dialog_title + "…", action); + Gtk::FileChooserDialog *file_chooser = &file_chooser_instance; + file_chooser->add_button(_("Select"), Gtk::RESPONSE_ACCEPT); + file_chooser->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL); +#endif + file_chooser->set_select_multiple(_select_multiple); + file_chooser->set_do_overwrite_confirmation(true); + file_chooser->set_create_folders(true); + + // set FileFilter according to 'filetype' attribute + if (!_filetypes.empty() && _mode != FOLDER && _mode != FOLDER_NEW) { + Glib::RefPtr<Gtk::FileFilter> file_filter = Gtk::FileFilter::create(); + + for (auto filetype : _filetypes) { + file_filter->add_pattern(Glib::ustring::compose("*.%1", filetype)); + } + + std::string filter_name = boost::algorithm::join(_filetypes, "+"); + boost::algorithm::to_upper(filter_name); + file_filter->set_name(filter_name); + + file_chooser->add_filter(file_filter); + } + + // set current file/folder suitable for current value + // (use basepath of first filename; relative paths are considered relative to .inx file's location) + if (!_value.empty()) { + std::string first_filename = _value.substr(0, _value.find("|")); + + if (!Glib::path_is_absolute(first_filename)) { + first_filename = Glib::build_filename(_extension->get_base_directory(), first_filename); + } + + std::string dirname = Glib::path_get_dirname(first_filename); + if (Glib::file_test(dirname, Glib::FILE_TEST_IS_DIR)) { + file_chooser->set_current_folder(dirname); + } + + if(_mode == FILE_NEW || _mode == FOLDER_NEW) { + file_chooser->set_current_name(Glib::path_get_basename(first_filename)); + } else { + if (Glib::file_test(first_filename, Glib::FILE_TEST_EXISTS)) { + // TODO: This does not seem to work (at least on Windows) + // file_chooser->set_filename(first_filename); + } + } + } + + // show dialog and parse result + int res = file_chooser->run(); + if (res == Gtk::ResponseType::RESPONSE_ACCEPT) { + std::vector<std::string> filenames = file_chooser->get_filenames(); + std::string filenames_joined = boost::algorithm::join(filenames, "|"); + _entry->set_text(filenames_joined); // let the ParamPathEntry handle the rest (including setting the preference) + } +} + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/parameter-path.h b/src/extension/prefdialog/parameter-path.h new file mode 100644 index 0000000..2de2311 --- /dev/null +++ b/src/extension/prefdialog/parameter-path.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Path parameter for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INK_EXTENSION_PARAM_PATH_H_SEEN +#define INK_EXTENSION_PARAM_PATH_H_SEEN + +#include "parameter.h" + + +namespace Inkscape { +namespace Extension { + +class ParamPathEntry; + +class ParamPath : public InxParameter { +public: + ParamPath(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + /** \brief Returns \c _value, with a \i const to protect it. */ + const std::string& get() const { return _value; } + const std::string& set(const std::string in); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + std::string value_to_string() const override; + +private: + enum Mode { + FILE, FOLDER, FILE_NEW, FOLDER_NEW + }; + + /** \brief Internal value. */ + std::string _value; + + /** selection mode for the file chooser: files or folders? */ + Mode _mode = FILE; + + /** selection mode for the file chooser: multiple items? */ + bool _select_multiple = false; + + /** filetypes that should be selectable in file chooser */ + std::vector<std::string> _filetypes; + + /** pointer to the parameters text entry + * keep this around, so we can update the value accordingly in \a on_button_clicked() */ + ParamPathEntry *_entry; + + void on_button_clicked(); +}; + + +} // namespace Extension +} // namespace Inkscape + +#endif /* INK_EXTENSION_PARAM_PATH_H_SEEN */ + +/* + 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/extension/prefdialog/parameter-string.cpp b/src/extension/prefdialog/parameter-string.cpp new file mode 100644 index 0000000..19e413e --- /dev/null +++ b/src/extension/prefdialog/parameter-string.cpp @@ -0,0 +1,228 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter-string.h" + +#include <gtkmm/box.h> +#include <gtkmm/entry.h> +#include <gtkmm/scrolledwindow.h> +#include <gtkmm/textview.h> +#include <glibmm/regex.h> + +#include "xml/node.h" +#include "extension/extension.h" +#include "preferences.h" + +namespace Inkscape { +namespace Extension { + +ParamString::ParamString(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxParameter(xml, ext) +{ + // get value + const char *value = nullptr; + if (xml->firstChild()) { + value = xml->firstChild()->content(); + } + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + _value = prefs->getString(pref_name()); + + if (_value.empty() && value) { + _value = value; + } + + // translate value + if (!_value.empty()) { + if (_translatable == YES) { // translate only if explicitly marked translatable + _value = get_translation(_value.c_str()); + } + } + + // max-length + const char *max_length = xml->attribute("max-length"); + if (!max_length) { + max_length = xml->attribute("max_length"); // backwards-compatibility with old name (underscore) + } + if (max_length) { + _max_length = strtoul(max_length, nullptr, 0); + } + + // parse appearance + if (_appearance) { + if (!strcmp(_appearance, "multiline")) { + _mode = MULTILINE; + } else { + g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'", + _appearance, _name, _extension->get_id()); + } + } +} + +/** + * A function to set the \c _value. + * + * This function sets the internal value, but it also sets the value + * in the preferences structure. To put it in the right place \c pref_name() is used. + * + * To copy the data into _value the old memory must be free'd first. + * It is important to note that \c g_free handles \c NULL just fine. Then + * the passed in value is duplicated using \c g_strdup(). + * + * @param in The value to set to. + */ +const Glib::ustring& ParamString::set(const Glib::ustring in) +{ + _value = in; + + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setString(pref_name(), _value); + + return _value; +} + +std::string ParamString::value_to_string() const +{ + return _value; +} + + + +/** A special type of Gtk::Entry to handle string parameters. */ +class ParamStringEntry : public Gtk::Entry { +private: + ParamString *_pref; + sigc::signal<void> *_changeSignal; +public: + /** + * Build a string preference for the given parameter. + * @param pref Where to get the string from, and where to put it + * when it changes. + */ + ParamStringEntry(ParamString *pref, sigc::signal<void> *changeSignal) + : Gtk::Entry() + , _pref(pref) + , _changeSignal(changeSignal) + { + this->set_text(_pref->get()); + this->set_max_length(_pref->getMaxLength()); //Set the max length - default zero means no maximum + this->signal_changed().connect(sigc::mem_fun(this, &ParamStringEntry::changed_text)); + }; + void changed_text(); +}; + + +/** + * Respond to the text box changing. + * + * This function responds to the box changing by grabbing the value + * from the text box and putting it in the parameter. + */ +void ParamStringEntry::changed_text() +{ + Glib::ustring data = this->get_text(); + _pref->set(data.c_str()); + if (_changeSignal != nullptr) { + _changeSignal->emit(); + } +} + + + +/** A special type of Gtk::TextView to handle multiline string parameters. */ +class ParamMultilineStringEntry : public Gtk::TextView { +private: + ParamString *_pref; + sigc::signal<void> *_changeSignal; +public: + /** + * Build a string preference for the given parameter. + * @param pref Where to get the string from, and where to put it + * when it changes. + */ + ParamMultilineStringEntry(ParamString *pref, sigc::signal<void> *changeSignal) + : Gtk::TextView() + , _pref(pref) + , _changeSignal(changeSignal) + { + // replace literal '\n' with actual newlines for multiline strings + Glib::ustring value = Glib::Regex::create("\\\\n")->replace_literal(_pref->get(), 0, "\n", (Glib::RegexMatchFlags)0); + + this->get_buffer()->set_text(value); + this->get_buffer()->signal_changed().connect(sigc::mem_fun(this, &ParamMultilineStringEntry::changed_text)); + }; + void changed_text(); +}; + +/** + * Respond to the text box changing. + * + * This function responds to the box changing by grabbing the value + * from the text box and putting it in the parameter. + */ +void ParamMultilineStringEntry::changed_text() +{ + Glib::ustring data = this->get_buffer()->get_text(); + + // always store newlines as literal '\n' + data = Glib::Regex::create("\n")->replace_literal(data, 0, "\\n", (Glib::RegexMatchFlags)0); + + _pref->set(data.c_str()); + if (_changeSignal != nullptr) { + _changeSignal->emit(); + } +} + + + +/** + * Creates a text box for the string parameter. + * + * Builds a hbox with a label and a text box in it. + */ +Gtk::Widget *ParamString::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::Box *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING)); + + Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START)); + label->show(); + box->pack_start(*label, false, false); + + if (_mode == MULTILINE) { + box->set_orientation(Gtk::ORIENTATION_VERTICAL); + + Gtk::ScrolledWindow *textarea = Gtk::manage(new Gtk::ScrolledWindow()); + textarea->set_vexpand(); + textarea->set_shadow_type(Gtk::SHADOW_IN); + + ParamMultilineStringEntry *entry = Gtk::manage(new ParamMultilineStringEntry(this, changeSignal)); + entry->show(); + + textarea->add(*entry); + textarea->show(); + + box->pack_start(*textarea, true, true); + } else { + Gtk::Widget *entry = Gtk::manage(new ParamStringEntry(this, changeSignal)); + entry->show(); + + box->pack_start(*entry, true, true); + } + + box->show(); + + return dynamic_cast<Gtk::Widget *>(box); +} + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/parameter-string.h b/src/extension/prefdialog/parameter-string.h new file mode 100644 index 0000000..3af8311 --- /dev/null +++ b/src/extension/prefdialog/parameter-string.h @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#ifndef INK_EXTENSION_PARAMSTRING_H_SEEN +#define INK_EXTENSION_PARAMSTRING_H_SEEN + +/* + * Copyright (C) 2005-2007 Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Jon A. Cruz <jon@joncruz.org> + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" + +#include <glibmm/ustring.h> + + +namespace Inkscape { +namespace Extension { + +class ParamString : public InxParameter { +public: + enum AppearanceMode { + DEFAULT, MULTILINE + }; + + ParamString(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + /** \brief Returns \c _value, with a \i const to protect it. */ + const Glib::ustring& get() const { return _value; } + const Glib::ustring& set(const Glib::ustring in); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + + std::string value_to_string() const override; + + void setMaxLength(int maxLength) { _max_length = maxLength; } + int getMaxLength() { return _max_length; } + +private: + /** \brief Internal value. */ + Glib::ustring _value; + + /** appearance mode **/ + AppearanceMode _mode = DEFAULT; + + /** \brief Maximum length of the string in characters (zero meaning unlimited). */ + int _max_length = 0; +}; + + +} // namespace Extension +} // namespace Inkscape + +#endif /* INK_EXTENSION_PARAMSTRING_H_SEEN */ + +/* + 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/extension/prefdialog/parameter.cpp b/src/extension/prefdialog/parameter.cpp new file mode 100644 index 0000000..14ccfc3 --- /dev/null +++ b/src/extension/prefdialog/parameter.cpp @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Parameters for extensions. + */ +/* Author: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2005-2007 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <cstring> +#include <list> + +#include <glibmm/i18n.h> +#include <sigc++/sigc++.h> + +#include "parameter.h" +#include "parameter-bool.h" +#include "parameter-color.h" +#include "parameter-float.h" +#include "parameter-int.h" +#include "parameter-notebook.h" +#include "parameter-optiongroup.h" +#include "parameter-path.h" +#include "parameter-string.h" +#include "widget.h" +#include "widget-label.h" + +#include "extension/extension.h" + +#include "object/sp-defs.h" + +#include "ui/widget/color-notebook.h" + +#include "xml/node.h" + + +namespace Inkscape { +namespace Extension { + + +// Re-implement ParamDescription for backwards-compatibility, deriving from both, WidgetLabel and InxParameter. +// TODO: Should go away eventually... +class ParamDescription : public virtual WidgetLabel, public virtual InxParameter { +public: + ParamDescription(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : WidgetLabel(xml, ext) + , InxParameter(xml, ext) + {} + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override + { + return this->WidgetLabel::get_widget(changeSignal); + } + + // Well, no, I don't have a value! That's why I should not be an InxParameter! + std::string value_to_string() const override { return ""; } +}; + + + +InxParameter *InxParameter::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext) +{ + InxParameter *param = nullptr; + + try { + const char *type = in_repr->attribute("type"); + if (!type) { + // we can't create a parameter without type + g_warning("Parameter without type in extension '%s'.", in_ext->get_id()); + } else if(!strcmp(type, "bool") || !strcmp(type, "boolean")) { // support "boolean" for backwards-compatibility + param = new ParamBool(in_repr, in_ext); + } else if (!strcmp(type, "int")) { + param = new ParamInt(in_repr, in_ext); + } else if (!strcmp(type, "float")) { + param = new ParamFloat(in_repr, in_ext); + } else if (!strcmp(type, "string")) { + param = new ParamString(in_repr, in_ext); + } else if (!strcmp(type, "path")) { + param = new ParamPath(in_repr, in_ext); + } else if (!strcmp(type, "description")) { + // support deprecated "description" for backwards-compatibility + in_repr->setAttribute("gui-text", "description"); // TODO: hack to allow descriptions to be parameters + param = new ParamDescription(in_repr, in_ext); + } else if (!strcmp(type, "notebook")) { + in_repr->setAttribute("gui-text", "notebook"); // notebooks have no 'gui-text' (but Parameters need one) + param = new ParamNotebook(in_repr, in_ext); + } else if (!strcmp(type, "optiongroup")) { + param = new ParamOptionGroup(in_repr, in_ext); + } else if (!strcmp(type, "enum")) { // support deprecated "enum" for backwards-compatibility + in_repr->setAttribute("appearance", "combo"); + param = new ParamOptionGroup(in_repr, in_ext); + } else if (!strcmp(type, "color")) { + param = new ParamColor(in_repr, in_ext); + } else { + g_warning("Unknown parameter type ('%s') in extension '%s'", type, in_ext->get_id()); + } + } catch (const param_no_name&) { + } catch (const param_no_text&) { + } + + // Note: param could equal nullptr + return param; +} + +bool InxParameter::get_bool() const +{ + ParamBool const *boolpntr = dynamic_cast<ParamBool const *>(this); + if (!boolpntr) { + throw param_not_bool_param(); + } + return boolpntr->get(); +} + +int InxParameter::get_int() const +{ + ParamInt const *intpntr = dynamic_cast<ParamInt const *>(this); + if (!intpntr) { + throw param_not_int_param(); + } + return intpntr->get(); +} + +float InxParameter::get_float() const +{ + ParamFloat const *floatpntr = dynamic_cast<ParamFloat const *>(this); + if (!floatpntr) { + throw param_not_float_param(); + } + return floatpntr->get(); +} + +const char *InxParameter::get_string() const +{ + ParamString const *stringpntr = dynamic_cast<ParamString const *>(this); + if (!stringpntr) { + throw param_not_string_param(); + } + return stringpntr->get().c_str(); +} + +const char *InxParameter::get_optiongroup() const +{ + ParamOptionGroup const *param = dynamic_cast<ParamOptionGroup const *>(this); + if (!param) { + throw param_not_optiongroup_param(); + } + return param->get().c_str(); +} + +bool InxParameter::get_optiongroup_contains(const char *value) const +{ + ParamOptionGroup const *param = dynamic_cast<ParamOptionGroup const *>(this); + if (!param) { + throw param_not_optiongroup_param(); + } + return param->contains(value); +} + +unsigned int InxParameter::get_color() const +{ + ParamColor const *param = dynamic_cast<ParamColor const *>(this); + if (!param) { + throw param_not_color_param(); + } + return param->get(); +} + +bool InxParameter::set_bool(bool in) +{ + ParamBool * boolpntr = dynamic_cast<ParamBool *>(this); + if (boolpntr == nullptr) + throw param_not_bool_param(); + return boolpntr->set(in); +} + +int InxParameter::set_int(int in) +{ + ParamInt *intpntr = dynamic_cast<ParamInt *>(this); + if (intpntr == nullptr) + throw param_not_int_param(); + return intpntr->set(in); +} + +float InxParameter::set_float(float in) +{ + ParamFloat * floatpntr; + floatpntr = dynamic_cast<ParamFloat *>(this); + if (floatpntr == nullptr) + throw param_not_float_param(); + return floatpntr->set(in); +} + +const char *InxParameter::set_string(const char *in) +{ + ParamString * stringpntr = dynamic_cast<ParamString *>(this); + if (stringpntr == nullptr) + throw param_not_string_param(); + return stringpntr->set(in).c_str(); +} + +const char *InxParameter::set_optiongroup(const char *in) +{ + ParamOptionGroup *param = dynamic_cast<ParamOptionGroup *>(this); + if (!param) { + throw param_not_optiongroup_param(); + } + return param->set(in).c_str(); +} + +unsigned int InxParameter::set_color(unsigned int in) +{ + ParamColor*param = dynamic_cast<ParamColor *>(this); + if (param == nullptr) + throw param_not_color_param(); + return param->set(in); +} + + +InxParameter::InxParameter(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *ext) + : InxWidget(in_repr, ext) +{ + // name (mandatory for all parameters) + const char *name = in_repr->attribute("name"); + if (name) { + _name = g_strstrip(g_strdup(name)); + } + if (!_name || !strcmp(_name, "")) { + g_warning("Parameter without name in extension '%s'.", _extension->get_id()); + throw param_no_name(); + } + + // gui-text + const char *gui_text = in_repr->attribute("gui-text"); + if (!gui_text) { + gui_text = in_repr->attribute("_gui-text"); // backwards-compatibility with underscored variants + } + if (gui_text) { + if (_translatable != NO) { // translate unless explicitly marked untranslatable + gui_text = get_translation(gui_text); + } + _text = g_strdup(gui_text); + } + if (!_text && !_hidden) { + g_warning("Parameter '%s' in extension '%s' is visible but does not have a 'gui-text'.", + _name, _extension->get_id()); + throw param_no_text(); + } + + // gui-description (optional) + const char *gui_description = in_repr->attribute("gui-description"); + if (!gui_description) { + gui_description = in_repr->attribute("_gui-description"); // backwards-compatibility with underscored variants + } + if (gui_description) { + if (_translatable != NO) { // translate unless explicitly marked untranslatable + gui_description = get_translation(gui_description); + } + _description = g_strdup(gui_description); + } +} + +InxParameter::~InxParameter() +{ + g_free(_name); + _name = nullptr; + + g_free(_text); + _text = nullptr; + + g_free(_description); + _description = nullptr; +} + +Glib::ustring InxParameter::pref_name() const +{ + return Glib::ustring::compose("/extensions/%1.%2", _extension->get_id(), _name); +} + +std::string InxParameter::value_to_string() const +{ + // if we end up here we're missing a definition of ::string() in one of the subclasses + g_critical("InxParameter::value_to_string called from parameter '%s' in extension '%s'", _name, _extension->get_id()); + g_assert_not_reached(); + return ""; +} + +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/prefdialog/parameter.h b/src/extension/prefdialog/parameter.h new file mode 100644 index 0000000..7776851 --- /dev/null +++ b/src/extension/prefdialog/parameter.h @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Parameters for extensions. + */ +/* Authors: + * Ted Gould <ted@gould.cx> + * Jon A. Cruz <jon@joncruz.org> + * + * Copyright (C) 2005-2006 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_EXTENSION_PARAM_H__ +#define SEEN_INK_EXTENSION_PARAM_H__ + +#include "widget.h" + + +namespace Glib { +class ustring; +} + + +namespace Inkscape { +namespace Extension { + +/** + * A class to represent the parameter of an extension. + * + * This is really a super class that allows them to abstract all + * the different types of parameters into some that can be passed + * around. There is also a few functions that are used by all the + * different parameters. + */ +class InxParameter : public InxWidget { +public: + InxParameter(Inkscape::XML::Node *in_repr, + Inkscape::Extension::Extension *ext); + + ~InxParameter() override; + + /** Wrapper to cast to the object and use its function. */ + bool get_bool() const; + + /** Wrapper to cast to the object and use it's function. */ + int get_int() const; + + /** Wrapper to cast to the object and use it's function. */ + float get_float() const; + + /** Wrapper to cast to the object and use it's function. */ + const char *get_string() const; + + /** Wrapper to cast to the object and use it's function. */ + const char *get_optiongroup() const; + bool get_optiongroup_contains(const char *value) const; + + /** Wrapper to cast to the object and use it's function. */ + unsigned int get_color() const; + + /** Wrapper to cast to the object and use it's function. */ + bool set_bool(bool in); + + /** Wrapper to cast to the object and use it's function. */ + int set_int(int in); + + /** Wrapper to cast to the object and use it's function. */ + float set_float(float in); + + /** Wrapper to cast to the object and use it's function. */ + const char *set_string(const char *in); + + /** Wrapper to cast to the object and use it's function. */ + const char *set_optiongroup(const char *in); + + /** Wrapper to cast to the object and use it's function. */ + unsigned int set_color(unsigned int in); + + char const *name() const { return _name; } + + /** + * Creates a new extension parameter for usage in a prefdialog. + * + * The type of widget created is parsed from the XML representation passed in, + * and the suitable subclass constructor is called. + * + * Called from base-class method of the same name. + * + * @param in_repr The XML representation describing the widget. + * @param in_ext The extension the widget belongs to. + * @return a pointer to a new Widget if applicable, null otherwise.. + */ + static InxParameter *make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext); + + const char *get_tooltip() const override { return _description; } + + /** + * Gets the current value of the parameter in a string form. + * + * @return String representation of the parameter's value. + * + * \internal Must be implemented by all derived classes. + * Unfortunately it seems we can't make this a pure virtual function, + * as InxParameter is not supposed to be abstract. + */ + virtual std::string value_to_string() const; + + /** Recommended spacing between the widgets making up a single Parameter (e.g. label and input) (in px) */ + const static int GUI_PARAM_WIDGETS_SPACING = 4; + + + /** An error class for when a parameter is called on a type it is not */ + class param_no_name {}; + class param_no_text {}; + class param_not_bool_param {}; + class param_not_color_param {}; + class param_not_float_param {}; + class param_not_int_param {}; + class param_not_optiongroup_param {}; + class param_not_string_param {}; + + +protected: + /** The name of this parameter. */ + char *_name = nullptr; + + /** Parameter text to show as the GUI label. */ + char *_text = nullptr; + + /** Extended description of the parameter (currently shown as tooltip on hover). */ + char *_description = nullptr; + + + /* **** member functions **** */ + + /** + * Build preference name for the current parameter. + * + * Returns a preference name that can be used with setters and getters from Inkscape::Preferences. + * The name is assembled from a fixed root ("/extensions/"), extension ID and parameter name. + * + * @return: Preference name + */ + Glib::ustring pref_name() const; +}; + +} // namespace Extension +} // namespace Inkscape + +#endif // SEEN_INK_EXTENSION_PARAM_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/extension/prefdialog/prefdialog.cpp b/src/extension/prefdialog/prefdialog.cpp new file mode 100644 index 0000000..a94a6d9 --- /dev/null +++ b/src/extension/prefdialog/prefdialog.cpp @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2005-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "prefdialog.h" + +#include <gtkmm/checkbutton.h> +#include <gtkmm/separator.h> +#include <glibmm/i18n.h> + +#include "ui/dialog-events.h" +#include "xml/repr.h" + +// Used to get SP_ACTIVE_DESKTOP +#include "inkscape.h" +#include "document.h" +#include "document-undo.h" + +#include "extension/effect.h" +#include "extension/execution-env.h" +#include "extension/implementation/implementation.h" + +#include "parameter.h" + + +namespace Inkscape { +namespace Extension { + + +/** \brief Creates a new preference dialog for extension preferences + \param name Name of the Extension whose dialog this is (should already be translated) + \param controls The extension specific widgets in the dialog + + This function initializes the dialog with the name of the extension + in the title. It adds a few buttons and sets up handlers for + them. It also places the passed-in widgets into the dialog. +*/ +PrefDialog::PrefDialog (Glib::ustring name, Gtk::Widget * controls, Effect * effect) : + Gtk::Dialog(name, true), + _name(name), + _button_ok(nullptr), + _button_cancel(nullptr), + _button_preview(nullptr), + _param_preview(nullptr), + _effect(effect), + _exEnv(nullptr) +{ + this->set_default_size(0,0); // we want the window to be as small as possible instead of clobbering up space + + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox()); + if (controls == nullptr) { + if (_effect == nullptr) { + std::cout << "AH!!! No controls and no effect!!!" << std::endl; + return; + } + controls = _effect->get_imp()->prefs_effect(_effect, SP_ACTIVE_DESKTOP, &_signal_param_change, nullptr); + _signal_param_change.connect(sigc::mem_fun(this, &PrefDialog::param_change)); + } + + hbox->pack_start(*controls, true, true, 0); + hbox->show(); + + this->get_content_area()->pack_start(*hbox, true, true, 0); + + _button_cancel = add_button(_effect == nullptr ? _("_Cancel") : _("_Close"), Gtk::RESPONSE_CANCEL); + _button_ok = add_button(_effect == nullptr ? _("_OK") : _("_Apply"), Gtk::RESPONSE_OK); + set_default_response(Gtk::RESPONSE_OK); + _button_ok->grab_focus(); + + if (_effect != nullptr && !_effect->no_live_preview) { + if (_param_preview == nullptr) { + XML::Document * doc = sp_repr_read_mem(live_param_xml, strlen(live_param_xml), nullptr); + if (doc == nullptr) { + std::cout << "Error encountered loading live parameter XML !!!" << std::endl; + return; + } + _param_preview = InxParameter::make(doc->root(), _effect); + } + + auto sep = Gtk::manage(new Gtk::Separator()); + sep->show(); + + this->get_content_area()->pack_start(*sep, false, false, InxWidget::GUI_BOX_SPACING); + + hbox = Gtk::manage(new Gtk::HBox()); + hbox->set_border_width(InxWidget::GUI_BOX_MARGIN); + _button_preview = _param_preview->get_widget(&_signal_preview); + _button_preview->show(); + hbox->pack_start(*_button_preview, true, true, 0); + hbox->show(); + + this->get_content_area()->pack_start(*hbox, false, false, 0); + + Gtk::Box *hbox = dynamic_cast<Gtk::Box *>(_button_preview); + if (hbox != nullptr) { + _checkbox_preview = dynamic_cast<Gtk::CheckButton *>(hbox->get_children().front()); + } + + preview_toggle(); + _signal_preview.connect(sigc::mem_fun(this, &PrefDialog::preview_toggle)); + } + + // Set window modality for effects that don't use live preview + if (_effect != nullptr && _effect->no_live_preview) { + set_modal(false); + } + + GtkWidget *dlg = GTK_WIDGET(gobj()); + sp_transientize(dlg); + + return; +} + +PrefDialog::~PrefDialog ( ) +{ + if (_param_preview != nullptr) { + delete _param_preview; + _param_preview = nullptr; + } + + if (_exEnv != nullptr) { + _exEnv->cancel(); + delete _exEnv; + _exEnv = nullptr; + _effect->set_execution_env(_exEnv); + } + + if (_effect != nullptr) { + _effect->set_pref_dialog(nullptr); + } + + return; +} + +void +PrefDialog::preview_toggle () { + SPDocument *document = SP_ACTIVE_DOCUMENT; + bool modified = document->isModifiedSinceSave(); + if(_param_preview->get_bool()) { + if (_exEnv == nullptr) { + set_modal(true); + _exEnv = new ExecutionEnv(_effect, SP_ACTIVE_DESKTOP, nullptr, false, false); + _effect->set_execution_env(_exEnv); + _exEnv->run(); + } + } else { + set_modal(false); + if (_exEnv != nullptr) { + _exEnv->cancel(); + _exEnv->undo(); + _exEnv->reselect(); + delete _exEnv; + _exEnv = nullptr; + _effect->set_execution_env(_exEnv); + } + } + document->setModifiedSinceSave(modified); +} + +void +PrefDialog::param_change () { + if (_exEnv != nullptr) { + if (!_effect->loaded()) { + _effect->set_state(Extension::STATE_LOADED); + } + _timersig.disconnect(); + _timersig = Glib::signal_timeout().connect(sigc::mem_fun(this, &PrefDialog::param_timer_expire), + 250, /* ms */ + Glib::PRIORITY_DEFAULT_IDLE); + } + + return; +} + +bool +PrefDialog::param_timer_expire () { + if (_exEnv != nullptr) { + _exEnv->cancel(); + _exEnv->undo(); + _exEnv->reselect(); + _exEnv->run(); + } + + return false; +} + +void +PrefDialog::on_response (int signal) { + if (signal == Gtk::RESPONSE_OK) { + if (_exEnv == nullptr) { + if (_effect != nullptr) { + _effect->effect(SP_ACTIVE_DESKTOP); + } else { + // Shutdown run() + return; + } + } else { + if (_exEnv->wait()) { + _exEnv->commit(); + } else { + _exEnv->undo(); + _exEnv->reselect(); + } + delete _exEnv; + _exEnv = nullptr; + _effect->set_execution_env(_exEnv); + } + } + + if (_param_preview != nullptr) { + _checkbox_preview->set_active(false); + } + + if ((signal == Gtk::RESPONSE_CANCEL || signal == Gtk::RESPONSE_DELETE_EVENT) && _effect != nullptr) { + delete this; + } + return; +} + +#include "extension/internal/clear-n_.h" + +const char * PrefDialog::live_param_xml = "<param name=\"__live_effect__\" type=\"bool\" gui-text=\"" N_("Live preview") "\" gui-description=\"" N_("Is the effect previewed live on canvas?") "\">false</param>"; + +}; }; /* namespace Inkscape, Extension */ + +/* + 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/extension/prefdialog/prefdialog.h b/src/extension/prefdialog/prefdialog.h new file mode 100644 index 0000000..9fe8acd --- /dev/null +++ b/src/extension/prefdialog/prefdialog.h @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2005,2007-2008 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_DIALOG_H__ +#define INKSCAPE_EXTENSION_DIALOG_H__ + +#include <gtkmm/dialog.h> +#include <glibmm/value.h> +#include <glibmm/ustring.h> + +namespace Gtk { +class CheckButton; +} + +namespace Inkscape { +namespace Extension { +class Effect; +class ExecutionEnv; +class InxParameter; + +/** \brief A class to represent the preferences for an extension */ +class PrefDialog : public Gtk::Dialog { + /** \brief Name of the extension */ + Glib::ustring _name; + + /** \brief A pointer to the OK button */ + Gtk::Button *_button_ok; + /** \brief A pointer to the CANCEL button */ + Gtk::Button *_button_cancel; + + /** \brief Button to control live preview */ + Gtk::Widget *_button_preview; + /** \brief Checkbox for the preview */ + Gtk::CheckButton *_checkbox_preview; + + /** \brief Parameter to control live preview */ + InxParameter *_param_preview; + + /** \brief XML to define the live effects parameter on the dialog */ + static const char * live_param_xml; + + /** \brief Signal that the user is changing the live effect state */ + sigc::signal<void> _signal_preview; + /** \brief Signal that one of the parameters change */ + sigc::signal<void> _signal_param_change; + + /** \brief If this is the preferences for an effect, the effect + that we're working with. */ + Effect *_effect; + /** \brief If we're executing in preview mode here is the execution + environment for the effect. */ + ExecutionEnv *_exEnv; + + /** \brief The timer used to make it so that parameters don't respond + directly and allows for changes. */ + sigc::connection _timersig; + + void preview_toggle(); + void param_change(); + bool param_timer_expire(); + void on_response (int signal) override; + +public: + PrefDialog (Glib::ustring name, + Gtk::Widget * controls = nullptr, + Effect * effect = nullptr); + ~PrefDialog () override; +}; + + +};}; /* namespace Inkscape, Extension */ + +#endif /* INKSCAPE_EXTENSION_DIALOG_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/extension/prefdialog/widget-box.cpp b/src/extension/prefdialog/widget-box.cpp new file mode 100644 index 0000000..9fd75e1 --- /dev/null +++ b/src/extension/prefdialog/widget-box.cpp @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Box widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "widget-box.h" + +#include <gtkmm/box.h> + +#include "xml/node.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { + + +WidgetBox::WidgetBox(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxWidget(xml, ext) +{ + // Decide orientation based on tagname (hbox vs. vbox) + const char *tagname = xml->name(); + if (!strncmp(tagname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + tagname += strlen(INKSCAPE_EXTENSION_NS); + } + if (!strcmp(tagname, "hbox")) { + _orientation = HORIZONTAL; + } else if (!strcmp(tagname, "vbox")) { + _orientation = VERTICAL; + } else { + g_assert_not_reached(); + } + + // Read XML tree of box and parse child widgets + if (xml) { + Inkscape::XML::Node *child_repr = xml->firstChild(); + while (child_repr) { + const char *chname = child_repr->name(); + if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + chname += strlen(INKSCAPE_EXTENSION_NS); + } + if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility + chname++; + } + + if (InxWidget::is_valid_widget_name(chname)) { + InxWidget *widget = InxWidget::make(child_repr, _extension); + if (widget) { + _children.push_back(widget); + } + } else if (child_repr->type() == XML::ELEMENT_NODE) { + g_warning("Invalid child element ('%s') in box widget in extension '%s'.", + chname, _extension->get_id()); + } else if (child_repr->type() != XML::COMMENT_NODE){ + g_warning("Invalid child element found in box widget in extension '%s'.", _extension->get_id()); + } + + child_repr = child_repr->next(); + } + } +} + +Gtk::Widget *WidgetBox::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::Orientation orientation; + if (_orientation == HORIZONTAL) { + orientation = Gtk::ORIENTATION_HORIZONTAL; + } else { + orientation = Gtk::ORIENTATION_VERTICAL; + } + + Gtk::Box *box = Gtk::manage(new Gtk::Box(orientation)); + // box->set_border_width(GUI_BOX_MARGIN); // leave at zero for now, so box is purely for layouting (not grouping) + // revisit this later, possibly implementing GtkFrame or similar + box->set_spacing(GUI_BOX_SPACING); + + if (_orientation == HORIZONTAL) { + box->set_vexpand(false); + } else { + box->set_hexpand(false); + } + + // add child widgets onto page (if any) + for (auto child : _children) { + Gtk::Widget *child_widget = child->get_widget(changeSignal); + if (child_widget) { + int indent = child->get_indent(); + child_widget->set_margin_start(indent * GUI_INDENTATION); + box->pack_start(*child_widget, false, true, 0); // fill=true does not have an effect here, but allows the + // child to choose to expand by setting hexpand/vexpand + + const char *tooltip = child->get_tooltip(); + if (tooltip) { + child_widget->set_tooltip_text(tooltip); + } + } + } + + box->show(); + + return dynamic_cast<Gtk::Widget *>(box); +} + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/widget-box.h b/src/extension/prefdialog/widget-box.h new file mode 100644 index 0000000..6fd7b90 --- /dev/null +++ b/src/extension/prefdialog/widget-box.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Box widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_EXTENSION_WIDGET_BOX_H +#define SEEN_INK_EXTENSION_WIDGET_BOX_H + +#include "widget.h" + +#include <glibmm/ustring.h> + +namespace Gtk { + class Widget; +} + +namespace Inkscape { +namespace Xml { + class Node; +} + +namespace Extension { + +/** \brief A box widget */ +class WidgetBox : public InxWidget { +public: + WidgetBox(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; +private: + enum Orientation { + HORIZONTAL, VERTICAL + }; + + /** Layout orientation of the box (default is vertical) **/ + Orientation _orientation = VERTICAL; +}; + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* SEEN_INK_EXTENSION_WIDGET_BOX_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/extension/prefdialog/widget-image.cpp b/src/extension/prefdialog/widget-image.cpp new file mode 100644 index 0000000..4f5e11d --- /dev/null +++ b/src/extension/prefdialog/widget-image.cpp @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Image widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "widget-image.h" + +#include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> +#include <gtkmm/image.h> + +#include "xml/node.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { + + +WidgetImage::WidgetImage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxWidget(xml, ext) +{ + std::string image_path; + + // get path to image + const char *content = nullptr; + if (xml->firstChild()) { + content = xml->firstChild()->content(); + } + if (content) { + image_path = content; + } else { + g_warning("Missing path for image widget in extension '%s'.", _extension->get_id()); + return; + } + + // make sure path is absolute (relative paths are relative to .inx file's location) + if (!Glib::path_is_absolute(image_path)) { + image_path = Glib::build_filename(_extension->get_base_directory(), image_path); + } + + // check if image exists + if (Glib::file_test(image_path, Glib::FILE_TEST_IS_REGULAR)) { + _image_path = image_path; + } else { + g_warning("Image file ('%s') not found for image widget in extension '%s'.", + image_path.c_str(), _extension->get_id()); + } + + // parse width/height attributes + const char *width = xml->attribute("width"); + const char *height = xml->attribute("height"); + if (width && height) { + _width = strtoul(width, nullptr, 0); + _height = strtoul(height, nullptr, 0); + } +} + +/** \brief Create a label for the description */ +Gtk::Widget *WidgetImage::get_widget(sigc::signal<void> * /*changeSignal*/) +{ + if (_hidden || _image_path.empty()) { + return nullptr; + } + + Gtk::Image *image = Gtk::manage(new Gtk::Image(_image_path)); + + // resize if requested + if (_width && _height) { + Glib::RefPtr<Gdk::Pixbuf> pixbuf = image->get_pixbuf(); + pixbuf = pixbuf->scale_simple(_width, _height, Gdk::INTERP_BILINEAR); + image->set(pixbuf); + } + + image->show(); + + return dynamic_cast<Gtk::Widget *>(image); +} + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/widget-image.h b/src/extension/prefdialog/widget-image.h new file mode 100644 index 0000000..95dd4d5 --- /dev/null +++ b/src/extension/prefdialog/widget-image.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Image widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_EXTENSION_WIDGET_IMAGE_H +#define SEEN_INK_EXTENSION_WIDGET_IMAGE_H + +#include "widget.h" + +#include <string> + +namespace Gtk { + class Widget; +} + +namespace Inkscape { +namespace Xml { + class Node; +} + +namespace Extension { + +/** \brief A label widget */ +class WidgetImage : public InxWidget { +public: + WidgetImage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; +private: + /** \brief Path to image file (relative paths are relative to the .inx file location). */ + std::string _image_path; + + /** desired width of image when rendered on screen (in px) */ + unsigned int _width = 0; + /** desired height of image when rendered on screen (in px) */ + unsigned int _height = 0; +}; + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* SEEN_INK_EXTENSION_WIDGET_IMAGE_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/extension/prefdialog/widget-label.cpp b/src/extension/prefdialog/widget-label.cpp new file mode 100644 index 0000000..ec4f558 --- /dev/null +++ b/src/extension/prefdialog/widget-label.cpp @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Description widget for extensions + *//* + * Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2005-2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "widget-label.h" + +#include <gtkmm/box.h> +#include <gtkmm/label.h> +#include <glibmm/markup.h> +#include <glibmm/regex.h> + +#include "xml/node.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { + + +WidgetLabel::WidgetLabel(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxWidget(xml, ext) +{ + // construct the text content by concatenating all (non-empty) text nodes, + // removing all other nodes (e.g. comment nodes) and replacing <extension:br> elements with "<br/>" + Inkscape::XML::Node * cur_child = xml->firstChild(); + while (cur_child != nullptr) { + if (cur_child->type() == XML::TEXT_NODE && cur_child->content() != nullptr) { + _value += cur_child->content(); + } else if (cur_child->type() == XML::ELEMENT_NODE && !g_strcmp0(cur_child->name(), "extension:br")) { + _value += "<br/>"; + } + cur_child = cur_child->next(); + } + + // do replacements in the source string to account for the attribute xml:space="preserve" + // (those should match replacements potentially performed by xgettext to allow for proper translation) + if (g_strcmp0(xml->attribute("xml:space"), "preserve") == 0) { + // xgettext copies the source string verbatim in this case, so no changes needed + } else { + // remove all whitespace from start/end of string and replace intermediate whitespace with a single space + _value = Glib::Regex::create("^\\s+|\\s+$")->replace_literal(_value, 0, "", (Glib::RegexMatchFlags)0); + _value = Glib::Regex::create("\\s+")->replace_literal(_value, 0, " ", (Glib::RegexMatchFlags)0); + } + + // translate value + if (!_value.empty()) { + if (_translatable != NO) { // translate unless explicitly marked untranslatable + _value = get_translation(_value.c_str()); + } + } + + // finally replace all remaining <br/> with a real newline character + _value = Glib::Regex::create("<br/>")->replace_literal(_value, 0, "\n", (Glib::RegexMatchFlags)0); + + // parse appearance + if (_appearance) { + if (!strcmp(_appearance, "header")) { + _mode = HEADER; + } else if (!strcmp(_appearance, "url")) { + _mode = URL; + } else { + g_warning("Invalid value ('%s') for appearance of label widget in extension '%s'", + _appearance, _extension->get_id()); + } + } +} + +/** \brief Create a label for the description */ +Gtk::Widget *WidgetLabel::get_widget(sigc::signal<void> * /*changeSignal*/) +{ + if (_hidden) { + return nullptr; + } + + Glib::ustring newtext = _value; + + Gtk::Label *label = Gtk::manage(new Gtk::Label()); + if (_mode == HEADER) { + label->set_markup(Glib::ustring("<b>") + Glib::Markup::escape_text(newtext) + Glib::ustring("</b>")); + label->set_margin_top(5); + label->set_margin_bottom(5); + } else if (_mode == URL) { + Glib::ustring escaped_url = Glib::Markup::escape_text(newtext); + label->set_markup(Glib::ustring::compose("<a href='%1'>%1</a>", escaped_url)); + } else { + label->set_text(newtext); + } + label->set_line_wrap(); + label->set_xalign(0); + + // TODO: Ugly "fix" for gtk3 width/height calculation of labels. + // - If not applying any limits long labels will make the window grow horizontally until it uses up + // most of the available space (i.e. most of the screen area) which is ridiculously wide. + // - By using "set_default_size(0,0)" in prefidalog.cpp we tell the window to shrink as much as possible, + // however this can result in a much too narrow dialog instead and a lot of unnecessary wrapping. + // - Here we set a lower limit of GUI_MAX_LINE_LENGTH characters per line that long texts will always use. + // This means texts can not shrink anymore (they can still grow, though) and it's also necessary + // to prevent https://bugzilla.gnome.org/show_bug.cgi?id=773572 + int len = newtext.length(); + label->set_width_chars(len > GUI_MAX_LINE_LENGTH ? GUI_MAX_LINE_LENGTH : len); + + label->show(); + + Gtk::HBox *hbox = Gtk::manage(new Gtk::HBox()); + hbox->pack_start(*label, true, true); + hbox->show(); + + return hbox; +} + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/widget-label.h b/src/extension/prefdialog/widget-label.h new file mode 100644 index 0000000..1c16550 --- /dev/null +++ b/src/extension/prefdialog/widget-label.h @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Description widget for extensions + *//* + * Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> * + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2005-2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_EXTENSION_WIDGET_LABEL_H +#define SEEN_INK_EXTENSION_WIDGET_LABEL_H + +#include "widget.h" + +#include <glibmm/ustring.h> + +namespace Gtk { + class Widget; +} + +namespace Inkscape { +namespace Xml { + class Node; +} + +namespace Extension { + +/** \brief A label widget */ +class WidgetLabel : public InxWidget { +public: + enum AppearanceMode { + DEFAULT, HEADER, URL + }; + + WidgetLabel(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; +private: + /** \brief Internal value. */ + Glib::ustring _value; + + /** appearance mode **/ + AppearanceMode _mode = DEFAULT; +}; + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* SEEN_INK_EXTENSION_WIDGET_LABEL_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/extension/prefdialog/widget-separator.cpp b/src/extension/prefdialog/widget-separator.cpp new file mode 100644 index 0000000..d78894b --- /dev/null +++ b/src/extension/prefdialog/widget-separator.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Separator widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "widget-separator.h" + +#include <gtkmm/separator.h> + +#include "xml/node.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { + + +WidgetSeparator::WidgetSeparator(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxWidget(xml, ext) +{ +} + +/** \brief Create a label for the description */ +Gtk::Widget *WidgetSeparator::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::Separator *separator = Gtk::manage(new Gtk::Separator()); + separator->show(); + + return dynamic_cast<Gtk::Widget *>(separator); +} + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/widget-separator.h b/src/extension/prefdialog/widget-separator.h new file mode 100644 index 0000000..9533aba --- /dev/null +++ b/src/extension/prefdialog/widget-separator.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Separator widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_EXTENSION_WIDGET_SEPARATOR_H +#define SEEN_INK_EXTENSION_WIDGET_SEPARATOR_H + +#include "widget.h" + +#include <glibmm/ustring.h> + +namespace Gtk { + class Widget; +} + +namespace Inkscape { +namespace Xml { + class Node; +} + +namespace Extension { + +/** \brief A separator widget */ +class WidgetSeparator : public InxWidget { +public: + WidgetSeparator(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; +}; + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* SEEN_INK_EXTENSION_WIDGET_SEPARATOR_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/extension/prefdialog/widget-spacer.cpp b/src/extension/prefdialog/widget-spacer.cpp new file mode 100644 index 0000000..8ffa6ac --- /dev/null +++ b/src/extension/prefdialog/widget-spacer.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Spacer widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "widget-spacer.h" + +#include <gtkmm/box.h> + +#include "xml/node.h" +#include "extension/extension.h" + +namespace Inkscape { +namespace Extension { + + +WidgetSpacer::WidgetSpacer(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext) + : InxWidget(xml, ext) +{ + // get size + const char *size = xml->attribute("size"); + if (size) { + _size = strtol(size, nullptr, 0); + if (_size == 0) { + if (!strcmp(size, "expand")) { + _expand = true; + } else { + g_warning("Invalid value ('%s') for size spacer in extension '%s'", size, _extension->get_id()); + } + } + } +} + +/** \brief Create a label for the description */ +Gtk::Widget *WidgetSpacer::get_widget(sigc::signal<void> *changeSignal) +{ + if (_hidden) { + return nullptr; + } + + Gtk::Box *spacer = Gtk::manage(new Gtk::Box()); + spacer->set_border_width(_size/2); + + if (_expand) { + spacer->set_hexpand(); + spacer->set_vexpand(); + } + + spacer->show(); + + return dynamic_cast<Gtk::Widget *>(spacer); +} + +} /* namespace Extension */ +} /* namespace Inkscape */ diff --git a/src/extension/prefdialog/widget-spacer.h b/src/extension/prefdialog/widget-spacer.h new file mode 100644 index 0000000..467b5f9 --- /dev/null +++ b/src/extension/prefdialog/widget-spacer.h @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Spacer widget for extensions + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_EXTENSION_WIDGET_SPACER_H +#define SEEN_INK_EXTENSION_WIDGET_SPACER_H + +#include "widget.h" + +#include <glibmm/ustring.h> + +namespace Gtk { + class Widget; +} + +namespace Inkscape { +namespace Xml { + class Node; +} + +namespace Extension { + +/** \brief A separator widget */ +class WidgetSpacer : public InxWidget { +public: + WidgetSpacer(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext); + + Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override; + +private: + /** size of the spacer in px */ + int _size = GUI_BOX_MARGIN; + + /** should the spacer be flexible and expand? */ + bool _expand = false; +}; + +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* SEEN_INK_EXTENSION_WIDGET_SPACER_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/extension/prefdialog/widget.cpp b/src/extension/prefdialog/widget.cpp new file mode 100644 index 0000000..18639c5 --- /dev/null +++ b/src/extension/prefdialog/widget.cpp @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Parameters for extensions. + *//* + * Author: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "parameter.h" +#include "widget.h" +#include "widget-box.h" +#include "widget-image.h" +#include "widget-label.h" +#include "widget-separator.h" +#include "widget-spacer.h" + +#include <algorithm> +#include <cstring> + +#include <sigc++/sigc++.h> + +#include "extension/extension.h" + +#include "xml/node.h" + + +namespace Inkscape { +namespace Extension { + +InxWidget *InxWidget::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext) +{ + InxWidget *widget = nullptr; + + const char *name = in_repr->name(); + if (!strncmp(name, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) { + name += strlen(INKSCAPE_EXTENSION_NS); + } + if (name[0] == '_') { // allow leading underscore in tag names for backwards-compatibility + name++; + } + + // decide on widget type based on tag name + // keep in sync with list of names supported in InxWidget::is_valid_widget_name() below + if (!name) { + // we can't create a widget without name + g_warning("InxWidget without name in extension '%s'.", in_ext->get_id()); + } else if (!strcmp(name, "hbox") || !strcmp(name, "vbox")) { + widget = new WidgetBox(in_repr, in_ext); + } else if (!strcmp(name, "image")) { + widget = new WidgetImage(in_repr, in_ext); + } else if (!strcmp(name, "label")) { + widget = new WidgetLabel(in_repr, in_ext); + } else if (!strcmp(name, "separator")) { + widget = new WidgetSeparator(in_repr, in_ext); + } else if (!strcmp(name, "spacer")) { + widget = new WidgetSpacer(in_repr, in_ext); + } else if (!strcmp(name, "param")) { + widget = InxParameter::make(in_repr, in_ext); + } else { + g_warning("Unknown widget name ('%s') in extension '%s'", name, in_ext->get_id()); + } + + // Note: widget could equal nullptr + return widget; +} + +bool InxWidget::is_valid_widget_name(const char *name) +{ + // keep in sync with names supported in InxWidget::make() above + static const std::vector<std::string> valid_names = + {"hbox", "vbox", "image", "label", "separator", "spacer", "param"}; + + if (std::find(valid_names.begin(), valid_names.end(), name) != valid_names.end()) { + return true; + } else { + return false; + } +} + + +InxWidget::InxWidget(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *ext) + : _extension(ext) +{ + // translatable (optional) + const char *translatable = in_repr->attribute("translatable"); + if (translatable) { + if (!strcmp(translatable, "yes")) { + _translatable = YES; + } else if (!strcmp(translatable, "no")) { + _translatable = NO; + } else { + g_warning("Invalid value ('%s') for translatable attribute of widget '%s' in extension '%s'", + translatable, in_repr->name(), _extension->get_id()); + } + } + + // context (optional) + const char *context = in_repr->attribute("context"); + if (!context) { + context = in_repr->attribute("msgctxt"); // backwards-compatibility with previous name + } + if (context) { + _context = g_strdup(context); + } + + // gui-hidden (optional) + const char *gui_hidden = in_repr->attribute("gui-hidden"); + if (gui_hidden != nullptr) { + if (strcmp(gui_hidden, "true") == 0) { + _hidden = true; + } + } + + // indent (optional) + const char *indent = in_repr->attribute("indent"); + if (indent != nullptr) { + _indent = strtol(indent, nullptr, 0); + } + + // appearance (optional, does not apply to all parameters) + const char *appearance = in_repr->attribute("appearance"); + if (appearance) { + _appearance = g_strdup(appearance); + } +} + +InxWidget::~InxWidget() +{ + for (auto child : _children) { + delete child; + } + + g_free(_context); + _context = nullptr; + + g_free(_appearance); + _appearance = nullptr; +} + +Gtk::Widget * +InxWidget::get_widget(sigc::signal<void> * /*changeSignal*/) +{ + // if we end up here we're missing a definition of ::get_widget() in one of the subclasses + g_critical("InxWidget::get_widget called from widget of type '%s' in extension '%s'", + typeid(this).name(), _extension->get_id()); + g_assert_not_reached(); + return nullptr; +} + +const char *InxWidget::get_translation(const char* msgid) { + return _extension->get_translation(msgid, _context); +} + +void InxWidget::get_widgets(std::vector<InxWidget *> &list) +{ + list.push_back(this); + for (auto child : _children) { + child->get_widgets(list); + } +} + +} // namespace Extension +} // 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:fileencoding=utf-8:textwidth=99 : diff --git a/src/extension/prefdialog/widget.h b/src/extension/prefdialog/widget.h new file mode 100644 index 0000000..843655c --- /dev/null +++ b/src/extension/prefdialog/widget.h @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** @file + * Base class for extension widgets. + *//* + * Authors: + * Patrick Storz <eduard.braun2@gmx.de> + * + * Copyright (C) 2019 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef SEEN_INK_EXTENSION_WIDGET_H +#define SEEN_INK_EXTENSION_WIDGET_H + +#include <string> +#include <vector> + +#include <sigc++/sigc++.h> + +namespace Gtk { +class Widget; +} + +namespace Inkscape { +namespace XML { +class Node; +} + +namespace Extension { + +class Extension; + + +/** + * Base class to represent all widgets of an extension (including parameters) + */ +class InxWidget { +public: + InxWidget(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext); + + virtual ~InxWidget(); + + /** + * Creates a new extension widget for usage in a prefdialog. + * + * The type of widget created is parsed from the XML representation passed in, + * and the suitable subclass constructor is called. + * + * For specialized widget types (like parameters) we defer to the subclass function of the same name. + * + * @param in_repr The XML representation describing the widget. + * @param in_ext The extension the widget belongs to. + * @return a pointer to a new Widget if applicable, null otherwise.. + */ + static InxWidget *make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext); + + /** Checks if name is a valid widget name, i.e. a widget can be constructed from it using make() */ + static bool is_valid_widget_name(const char *name); + + /** Return the instance's GTK::Widget representation for usage in a GUI + * + * @param changeSignal Can be used to subscribe to parameter changes. + * Will be emitted whenever a parameter value changes. + * + * @teturn A Gtk::Widget for the \a InxWidget. \c nullptr if the widget is hidden. + */ + virtual Gtk::Widget *get_widget(sigc::signal<void> *changeSignal); + + virtual const char *get_tooltip() const { return nullptr; } // tool-tips are exclusive to InxParameters for now + + /** Indicates if the widget is hidden or not */ + bool get_hidden() const { return _hidden; } + + /** Indentation level of the widget */ + int get_indent() const { return _indent; } + + + /** + * Recursively construct a list containing the current widget and all of it's child widgets (if it has any) + * + * @param list Reference to a vector of pointers to \a InxWidget that will be appended with the new \a InxWidgets + */ + virtual void get_widgets(std::vector<InxWidget *> &list); + + + /** Recommended margin of boxes containing multiple widgets (in px) */ + const static int GUI_BOX_MARGIN = 10; + /** Recommended spacing between multiple widgets packed into a box (in px) */ + const static int GUI_BOX_SPACING = 4; + /** Recommended indentation width of widgets(in px) */ + const static int GUI_INDENTATION = 12; + /** Recommended maximum line length for wrapping textual wdgets (in chars) */ + const static int GUI_MAX_LINE_LENGTH = 60; + +protected: + enum Translatable { + UNSET, YES, NO + }; + + /** Which extension is this Widget attached to. */ + Inkscape::Extension::Extension *_extension = nullptr; + + /** Child widgets of this widget (might be empty if there are none) */ + std::vector<InxWidget *> _children; + + /** Whether the widget is visible. */ + bool _hidden = false; + + /** Indentation level of the widget. */ + int _indent = 0; + + /** Appearance of the widget (not used by all widgets). */ + char *_appearance = nullptr; + + /** Is widget translatable? */ + Translatable _translatable = UNSET; + + /** context for translation of translatable strings. */ + char *_context = nullptr; + + + /* **** member functions **** */ + + /** gets the gettext translation for msgid + * + * Handles translation domain of the extension and message context of the widget internally + * + * @param msgid String to translate + * @return Translated string + */ + const char *get_translation(const char* msgid); +}; + +} // namespace Extension +} // namespace Inkscape + +#endif // SEEN_INK_EXTENSION_WIDGET_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/extension/print.cpp b/src/extension/print.cpp new file mode 100644 index 0000000..d9f7406 --- /dev/null +++ b/src/extension/print.cpp @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "implementation/implementation.h" +#include "print.h" + +/* Inkscape::Extension::Print */ + +namespace Inkscape { +namespace Extension { + +Print::Print (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory) + : Extension(in_repr, in_imp, base_directory) + , base(nullptr) + , drawing(nullptr) + , root(nullptr) + , dkey(0) +{ +} + +Print::~Print () += default; + +bool +Print::check () +{ + return Extension::check(); +} + +unsigned int +Print::setup () +{ + return imp->setup(this); +} + +unsigned int +Print::set_preview () +{ + return imp->set_preview(this); +} + +unsigned int +Print::begin (SPDocument *doc) +{ + return imp->begin(this, doc); +} + +unsigned int +Print::finish () +{ + return imp->finish(this); +} + +unsigned int +Print::bind (const Geom::Affine &transform, float opacity) +{ + return imp->bind (this, transform, opacity); +} + +unsigned int +Print::release () +{ + return imp->release(this); +} + +unsigned int +Print::comment (char const *comment) +{ + return imp->comment(this, comment); +} + +unsigned int +Print::fill (Geom::PathVector const &pathv, Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox) +{ + return imp->fill (this, pathv, ctm, style, pbox, dbox, bbox); +} + +unsigned int +Print::stroke (Geom::PathVector const &pathv, Geom::Affine const &ctm, SPStyle const *style, + Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox) +{ + return imp->stroke (this, pathv, ctm, style, pbox, dbox, bbox); +} + +unsigned int +Print::image (unsigned char *px, unsigned int w, unsigned int h, unsigned int rs, + const Geom::Affine &transform, const SPStyle *style) +{ + return imp->image (this, px, w, h, rs, transform, style); +} + +unsigned int +Print::text (char const *text, Geom::Point const &p, SPStyle const *style) +{ + return imp->text (this, text, p, style); +} + +bool +Print::textToPath () +{ + return imp->textToPath(this); +} + +//whether embed font in print output (EPS especially) +bool +Print::fontEmbedded () +{ + return imp->fontEmbedded(this); +} + +} } /* namespace Inkscape, Extension */ + +/* + 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/extension/print.h b/src/extension/print.h new file mode 100644 index 0000000..56ec367 --- /dev/null +++ b/src/extension/print.h @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Authors: + * Ted Gould <ted@gould.cx> + * Abhishek Sharma + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_PRINT_H__ +#define INKSCAPE_EXTENSION_PRINT_H__ + +#include "extension.h" + +class SPItem; +class SPStyle; + +namespace Inkscape { + +class Drawing; +class DrawingItem; + +namespace Extension { + +class Print : public Extension { + +public: /* TODO: These are public for the short term, but this should be fixed */ + SPItem *base; + Inkscape::Drawing *drawing; + Inkscape::DrawingItem *root; + unsigned int dkey; + +public: + Print(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory); + ~Print() override; + + bool check() override; + + /* FALSE means user hit cancel */ + unsigned int setup (); + unsigned int set_preview (); + + unsigned int begin (SPDocument *doc); + unsigned int finish (); + + /* Rendering methods */ + unsigned int bind (Geom::Affine const &transform, + float opacity); + unsigned int release (); + unsigned int comment (const char * comment); + unsigned int fill (Geom::PathVector const &pathv, + Geom::Affine const &ctm, + SPStyle const *style, + Geom::OptRect const &pbox, + Geom::OptRect const &dbox, + Geom::OptRect const &bbox); + unsigned int stroke (Geom::PathVector const &pathv, + Geom::Affine const &transform, + SPStyle const *style, + Geom::OptRect const &pbox, + Geom::OptRect const &dbox, + Geom::OptRect const &bbox); + unsigned int image (unsigned char *px, + unsigned int w, + unsigned int h, + unsigned int rs, + Geom::Affine const &transform, + SPStyle const *style); + unsigned int text (char const *text, + Geom::Point const &p, + SPStyle const *style); + bool textToPath (); + bool fontEmbedded (); +}; + +} } /* namespace Inkscape, Extension */ +#endif /* INKSCAPE_EXTENSION_PRINT_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/extension/system.cpp b/src/extension/system.cpp new file mode 100644 index 0000000..08de0b4 --- /dev/null +++ b/src/extension/system.cpp @@ -0,0 +1,734 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is file is kind of the junk file. Basically everything that + * didn't fit in one of the other well defined areas, well, it's now + * here. Which is good in someways, but this file really needs some + * definition. Hopefully that will come ASAP. + * + * Authors: + * Ted Gould <ted@gould.cx> + * Johan Engelen <johan@shouraizou.nl> + * Jon A. Cruz <jon@joncruz.org> + * Abhishek Sharma + * + * Copyright (C) 2006-2007 Johan Engelen + * Copyright (C) 2002-2004 Ted Gould + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "ui/interface.h" + +#include "system.h" +#include "preferences.h" +#include "extension.h" +#include "db.h" +#include "input.h" +#include "output.h" +#include "effect.h" +#include "patheffect.h" +#include "print.h" +#include "implementation/script.h" +#include "implementation/xslt.h" +#include "xml/rebase-hrefs.h" +#include "io/sys.h" +#include "inkscape.h" +#include "document-undo.h" +#include "loader.h" + +#include <glibmm/miscutils.h> + +namespace Inkscape { +namespace Extension { + +static void open_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data); +static void save_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data); + +/** + * \return A new document created from the filename passed in + * \brief This is a generic function to use the open function of + * a module (including Autodetect) + * \param key Identifier of which module to use + * \param filename The file that should be opened + * + * First things first, are we looking at an autodetection? Well if that's the case then the module + * needs to be found, and that is done with a database lookup through the module DB. The foreach + * function is called, with the parameter being a gpointer array. It contains both the filename + * (to find its extension) and where to write the module when it is found. + * + * If there is no autodetection, then the module database is queried with the key given. + * + * If everything is cool at this point, the module is loaded, and there is possibility for + * preferences. If there is a function, then it is executed to get the dialog to be displayed. + * After it is finished the function continues. + * + * Lastly, the open function is called in the module itself. + */ +SPDocument *open(Extension *key, gchar const *filename) +{ + Input *imod = nullptr; + + if (key == nullptr) { + gpointer parray[2]; + parray[0] = (gpointer)filename; + parray[1] = (gpointer)&imod; + db.foreach(open_internal, (gpointer)&parray); + } else { + imod = dynamic_cast<Input *>(key); + } + + bool last_chance_svg = false; + if (key == nullptr && imod == nullptr) { + last_chance_svg = true; + imod = dynamic_cast<Input *>(db.get(SP_MODULE_KEY_INPUT_SVG)); + } + + if (imod == nullptr) { + throw Input::no_extension_found(); + } + + // Hide pixbuf extensions depending on user preferences. + //g_warning("Extension: %s", imod->get_id()); + + bool show = true; + if (strlen(imod->get_id()) > 21) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + bool ask = prefs->getBool("/dialogs/import/ask"); + bool ask_svg = prefs->getBool("/dialogs/import/ask_svg"); + Glib::ustring id = Glib::ustring(imod->get_id(), 22); + if (id.compare("org.inkscape.input.svg") == 0) { + if (ask_svg && prefs->getBool("/options/onimport", false)) { + show = true; + imod->set_gui(true); + } else { + show = false; + imod->set_gui(false); + } + } else if(strlen(imod->get_id()) > 27) { + id = Glib::ustring(imod->get_id(), 28); + if (!ask && id.compare( "org.inkscape.input.gdkpixbuf") == 0) { + show = false; + imod->set_gui(false); + } + } + } + imod->set_state(Extension::STATE_LOADED); + + if (!imod->loaded()) { + throw Input::open_failed(); + } + + if (!imod->prefs(filename)) { + throw Input::open_cancelled(); + } + + SPDocument *doc = imod->open(filename); + + if (!doc) { + throw Input::open_failed(); + } + + if (last_chance_svg) { + if ( INKSCAPE.use_gui() ) { + sp_ui_error_dialog(_("Format autodetect failed. The file is being opened as SVG.")); + } else { + g_warning("%s", _("Format autodetect failed. The file is being opened as SVG.")); + } + } + + doc->setDocumentUri(filename); + if (!show) { + imod->set_gui(true); + } + + return doc; +} + +/** + * \return none + * \brief This is the function that searches each module to see + * if it matches the filename for autodetection. + * \param in_plug The module to be tested + * \param in_data An array of pointers containing the filename, and + * the place to put a successfully found module. + * + * Basically this function only looks at input modules as it is part of the open function. If the + * module is an input module, it then starts to take it apart, and the data that is passed in. + * Because the data being passed in is in such a weird format, there are a few casts to make it + * easier to use. While it looks like a lot of local variables, they'll all get removed by the + * compiler. + * + * First thing that is checked is if the filename is shorter than the extension itself. There is + * no way for a match in that case. If it's long enough then there is a string compare of the end + * of the filename (for the length of the extension), and the extension itself. If this passes + * then the pointer passed in is set to the current module. + */ +static void +open_internal(Extension *in_plug, gpointer in_data) +{ + if (!in_plug->deactivated() && dynamic_cast<Input *>(in_plug)) { + gpointer *parray = (gpointer *)in_data; + gchar const *filename = (gchar const *)parray[0]; + Input **pimod = (Input **)parray[1]; + + // skip all the rest if we already found a function to open it + // since they're ordered by preference now. + if (!*pimod) { + gchar const *ext = dynamic_cast<Input *>(in_plug)->get_extension(); + + gchar *filenamelower = g_utf8_strdown(filename, -1); + gchar *extensionlower = g_utf8_strdown(ext, -1); + + if (g_str_has_suffix(filenamelower, extensionlower)) { + *pimod = dynamic_cast<Input *>(in_plug); + } + + g_free(filenamelower); + g_free(extensionlower); + } + } + + return; +} + +/** + * \return None + * \brief This is a generic function to use the save function of + * a module (including Autodetect) + * \param key Identifier of which module to use + * \param doc The document to be saved + * \param filename The file that the document should be saved to + * \param official (optional) whether to set :output_module and :modified in the + * document; is true for normal save, false for temporary saves + * + * First things first, are we looking at an autodetection? Well if that's the case then the module + * needs to be found, and that is done with a database lookup through the module DB. The foreach + * function is called, with the parameter being a gpointer array. It contains both the filename + * (to find its extension) and where to write the module when it is found. + * + * If there is no autodetection the module database is queried with the key given. + * + * If everything is cool at this point, the module is loaded, and there is possibility for + * preferences. If there is a function, then it is executed to get the dialog to be displayed. + * After it is finished the function continues. + * + * Lastly, the save function is called in the module itself. + */ +void +save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension, bool check_overwrite, bool official, + Inkscape::Extension::FileSaveMethod save_method) +{ + Output *omod; + if (key == nullptr) { + gpointer parray[2]; + parray[0] = (gpointer)filename; + parray[1] = (gpointer)&omod; + omod = nullptr; + db.foreach(save_internal, (gpointer)&parray); + + /* This is a nasty hack, but it is required to ensure that + autodetect will always save with the Inkscape extensions + if they are available. */ + if (omod != nullptr && !strcmp(omod->get_id(), SP_MODULE_KEY_OUTPUT_SVG)) { + omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); + } + /* If autodetect fails, save as Inkscape SVG */ + if (omod == nullptr) { + // omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); use exception and let user choose + } + } else { + omod = dynamic_cast<Output *>(key); + } + + if (!dynamic_cast<Output *>(omod)) { + g_warning("Unable to find output module to handle file: %s\n", filename); + throw Output::no_extension_found(); + } + + omod->set_state(Extension::STATE_LOADED); + if (!omod->loaded()) { + throw Output::save_failed(); + } + + if (!omod->prefs()) { + throw Output::save_cancelled(); + } + + gchar *fileName = nullptr; + if (setextension) { + gchar *lowerfile = g_utf8_strdown(filename, -1); + gchar *lowerext = g_utf8_strdown(omod->get_extension(), -1); + + if (!g_str_has_suffix(lowerfile, lowerext)) { + fileName = g_strdup_printf("%s%s", filename, omod->get_extension()); + } + + g_free(lowerfile); + g_free(lowerext); + } + + if (fileName == nullptr) { + fileName = g_strdup(filename); + } + + if (check_overwrite && !sp_ui_overwrite_file(fileName)) { + g_free(fileName); + throw Output::no_overwrite(); + } + + // test if the file exists and is writable + // the test only checks the file attributes and might pass where ACL does not allow writes + if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_is_writable(filename)) { + g_free(fileName); + throw Output::file_read_only(); + } + + Inkscape::XML::Node *repr = doc->getReprRoot(); + + + // remember attributes in case this is an unofficial save and/or overwrite fails + gchar *saved_uri = g_strdup(doc->getDocumentURI()); + gchar *saved_output_extension = nullptr; + gchar *saved_dataloss = nullptr; + bool saved_modified = doc->isModifiedSinceSave(); + saved_output_extension = g_strdup(get_file_save_extension(save_method).c_str()); + saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss")); + if (official) { + // The document is changing name/uri. + doc->changeUriAndHrefs(fileName); + } + + // Update attributes: + { + { + DocumentUndo::ScopedInsensitive _no_undo(doc); + // also save the extension for next use + store_file_extension_in_prefs (omod->get_id(), save_method); + // set the "dataloss" attribute if the chosen extension is lossy + repr->removeAttribute("inkscape:dataloss"); + if (omod->causes_dataloss()) { + repr->setAttribute("inkscape:dataloss", "true"); + } + } + doc->setModifiedSinceSave(false); + } + + try { + omod->save(doc, fileName); + } + catch(...) { + // revert attributes in case of official and overwrite + if(check_overwrite && official) { + { + DocumentUndo::ScopedInsensitive _no_undo(doc); + store_file_extension_in_prefs (saved_output_extension, save_method); + repr->setAttribute("inkscape:dataloss", saved_dataloss); + } + doc->changeUriAndHrefs(saved_uri); + } + doc->setModifiedSinceSave(saved_modified); + // free used resources + g_free(saved_output_extension); + g_free(saved_dataloss); + g_free(saved_uri); + + g_free(fileName); + + throw; + } + + // If it is an unofficial save, set the modified attributes back to what they were. + if ( !official) { + { + DocumentUndo::ScopedInsensitive _no_undo(doc); + store_file_extension_in_prefs (saved_output_extension, save_method); + repr->setAttribute("inkscape:dataloss", saved_dataloss); + } + doc->setModifiedSinceSave(saved_modified); + + g_free(saved_output_extension); + g_free(saved_dataloss); + } + + g_free(fileName); + return; +} + +/** + * \return none + * \brief This is the function that searches each module to see + * if it matches the filename for autodetection. + * \param in_plug The module to be tested + * \param in_data An array of pointers containing the filename, and + * the place to put a successfully found module. + * + * Basically this function only looks at output modules as it is part of the open function. If the + * module is an output module, it then starts to take it apart, and the data that is passed in. + * Because the data being passed in is in such a weird format, there are a few casts to make it + * easier to use. While it looks like a lot of local variables, they'll all get removed by the + * compiler. + * + * First thing that is checked is if the filename is shorter than the extension itself. There is + * no way for a match in that case. If it's long enough then there is a string compare of the end + * of the filename (for the length of the extension), and the extension itself. If this passes + * then the pointer passed in is set to the current module. + */ +static void +save_internal(Extension *in_plug, gpointer in_data) +{ + if (!in_plug->deactivated() && dynamic_cast<Output *>(in_plug)) { + gpointer *parray = (gpointer *)in_data; + gchar const *filename = (gchar const *)parray[0]; + Output **pomod = (Output **)parray[1]; + + // skip all the rest if we already found someone to save it + // since they're ordered by preference now. + if (!*pomod) { + gchar const *ext = dynamic_cast<Output *>(in_plug)->get_extension(); + + gchar *filenamelower = g_utf8_strdown(filename, -1); + gchar *extensionlower = g_utf8_strdown(ext, -1); + + if (g_str_has_suffix(filenamelower, extensionlower)) { + *pomod = dynamic_cast<Output *>(in_plug); + } + + g_free(filenamelower); + g_free(extensionlower); + } + } + + return; +} + +Print * +get_print(gchar const *key) +{ + return dynamic_cast<Print *>(db.get(key)); +} + +/** + * \return true if extension successfully parsed, false otherwise + * A true return value does not guarantee an extension was actually registered, + * but indicates no errors occurred while parsing the extension. + * \brief Creates a module from a Inkscape::XML::Document describing the module + * \param doc The XML description of the module + * + * This function basically has two segments. The first is that it goes through the Repr tree + * provided, and determines what kind of module this is, and what kind of implementation to use. + * All of these are then stored in two enums that are defined in this function. This makes it + * easier to add additional types (which will happen in the future, I'm sure). + * + * Second, there is case statements for these enums. The first one is the type of module. This is + * the one where the module is actually created. After that, then the implementation is applied to + * get the load and unload functions. If there is no implementation then these are not set. This + * case could apply to modules that are built in (like the SVG load/save functions). + */ +bool +build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp, std::string* baseDir) +{ + enum { + MODULE_EXTENSION, + MODULE_XSLT, + MODULE_PLUGIN, + MODULE_UNKNOWN_IMP + } module_implementation_type = MODULE_UNKNOWN_IMP; + enum { + MODULE_INPUT, + MODULE_OUTPUT, + MODULE_FILTER, + MODULE_PRINT, + MODULE_PATH_EFFECT, + MODULE_UNKNOWN_FUNC + } module_functional_type = MODULE_UNKNOWN_FUNC; + + g_return_val_if_fail(doc != nullptr, false); + + Inkscape::XML::Node *repr = doc->root(); + + if (strcmp(repr->name(), INKSCAPE_EXTENSION_NS "inkscape-extension")) { + g_warning("Extension definition started with <%s> instead of <" INKSCAPE_EXTENSION_NS "inkscape-extension>. Extension will not be created. See http://wiki.inkscape.org/wiki/index.php/Extensions for reference.\n", repr->name()); + return false; + } + + Inkscape::XML::Node *child_repr = repr->firstChild(); + while (child_repr != nullptr) { + char const *element_name = child_repr->name(); + /* printf("Child: %s\n", child_repr->name()); */ + if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "input")) { + module_functional_type = MODULE_INPUT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "output")) { + module_functional_type = MODULE_OUTPUT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "effect")) { + module_functional_type = MODULE_FILTER; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "print")) { + module_functional_type = MODULE_PRINT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "path-effect")) { + module_functional_type = MODULE_PATH_EFFECT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "script")) { + module_implementation_type = MODULE_EXTENSION; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "xslt")) { + module_implementation_type = MODULE_XSLT; + } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "plugin")) { + module_implementation_type = MODULE_PLUGIN; + } + + //Inkscape::XML::Node *old_repr = child_repr; + child_repr = child_repr->next(); + //Inkscape::GC::release(old_repr); + } + + Implementation::Implementation *imp; + if (in_imp == nullptr) { + switch (module_implementation_type) { + case MODULE_EXTENSION: { + Implementation::Script *script = new Implementation::Script(); + imp = static_cast<Implementation::Implementation *>(script); + break; + } + case MODULE_XSLT: { + Implementation::XSLT *xslt = new Implementation::XSLT(); + imp = static_cast<Implementation::Implementation *>(xslt); + break; + } + case MODULE_PLUGIN: { + Inkscape::Extension::Loader loader = Inkscape::Extension::Loader(); + if( baseDir != nullptr){ + loader.set_base_directory ( *baseDir ); + } + imp = loader.load_implementation(doc); + break; + } + default: { + imp = nullptr; + break; + } + } + } else { + imp = in_imp; + } + + Extension *module = nullptr; + try { + switch (module_functional_type) { + case MODULE_INPUT: { + module = new Input(repr, imp, baseDir); + break; + } + case MODULE_OUTPUT: { + module = new Output(repr, imp, baseDir); + break; + } + case MODULE_FILTER: { + module = new Effect(repr, imp, baseDir); + break; + } + case MODULE_PRINT: { + module = new Print(repr, imp, baseDir); + break; + } + case MODULE_PATH_EFFECT: { + module = new PathEffect(repr, imp, baseDir); + break; + } + default: { + g_warning("Extension of unknown type!"); // TODO: Should not happen! Is this even useful? + module = new Extension(repr, imp, baseDir); + break; + } + } + } catch (const Extension::extension_no_id& e) { + g_warning("Building extension failed. Extension does not have a valid ID"); + } catch (const Extension::extension_no_name& e) { + g_warning("Building extension failed. Extension does not have a valid name"); + } catch (const Extension::extension_not_compatible& e) { + return true; // This is not an actual error; just silently ignore the extension + } + + if (module) { + return true; + } + + return false; +} + +/** + * \brief This function creates a module from a filename of an + * XML description. + * \param filename The file holding the XML description of the module. + * + * This function calls build_from_reprdoc with using sp_repr_read_file to create the reprdoc. + */ +void +build_from_file(gchar const *filename) +{ + std::string dir = Glib::path_get_dirname(filename); + + Inkscape::XML::Document *doc = sp_repr_read_file(filename, INKSCAPE_EXTENSION_URI); + if (!doc) { + g_critical("Inkscape::Extension::build_from_file() - XML description loaded from '%s' not valid.", filename); + return; + } + + if (!build_from_reprdoc(doc, nullptr, &dir)) { + g_warning("Inkscape::Extension::build_from_file() - Could not parse extension from '%s'.", filename); + } + + Inkscape::GC::release(doc); +} + +/** + * \brief This function creates a module from a buffer holding an + * XML description. + * \param buffer The buffer holding the XML description of the module. + * + * This function calls build_from_reprdoc with using sp_repr_read_mem to create the reprdoc. It + * finds the length of the buffer using strlen. + */ +void +build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp) +{ + Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), INKSCAPE_EXTENSION_URI); + if (!doc) { + g_critical("Inkscape::Extension::build_from_mem() - XML description loaded from memory buffer not valid."); + return; + } + + if (!build_from_reprdoc(doc, in_imp, nullptr)) { + g_critical("Inkscape::Extension::build_from_mem() - Could not parse extension from memory buffer."); + } + + Inkscape::GC::release(doc); +} + +/* + * TODO: Is it guaranteed that the returned extension is valid? If so, we can remove the check for + * filename_extension in sp_file_save_dialog(). + */ +Glib::ustring +get_file_save_extension (Inkscape::Extension::FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring extension; + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + extension = prefs->getString("/dialogs/save_as/default"); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + extension = prefs->getString("/dialogs/save_copy/default"); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE; + break; + case FILE_SAVE_METHOD_EXPORT: + /// \todo no default extension set for Export? defaults to SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE is ok? + break; + } + + if(extension.empty()) { + extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE; + } + + return extension; +} + +Glib::ustring +get_file_save_path (SPDocument *doc, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + Glib::ustring path; + bool use_current_dir = true; + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + { + use_current_dir = prefs->getBool("/dialogs/save_as/use_current_dir", true); + if (doc->getDocumentURI() && use_current_dir) { + path = Glib::path_get_dirname(doc->getDocumentURI()); + } else { + path = prefs->getString("/dialogs/save_as/path"); + } + break; + } + case FILE_SAVE_METHOD_TEMPORARY: + path = prefs->getString("/dialogs/save_as/path"); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + use_current_dir = prefs->getBool("/dialogs/save_copy/use_current_dir", prefs->getBool("/dialogs/save_as/use_current_dir", true)); + if (doc->getDocumentURI() && use_current_dir) { + path = Glib::path_get_dirname(doc->getDocumentURI()); + } else { + path = prefs->getString("/dialogs/save_copy/path"); + } + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + if (doc->getDocumentURI()) { + path = Glib::path_get_dirname(doc->getDocumentURI()); + } else { + // FIXME: should we use the save_as path here or something else? Maybe we should + // leave this as a choice to the user. + path = prefs->getString("/dialogs/save_as/path"); + } + break; + case FILE_SAVE_METHOD_EXPORT: + /// \todo no default path set for Export? + // defaults to g_get_home_dir() + break; + } + + if(path.empty()) { + path = g_get_home_dir(); // Is this the most sensible solution? Note that we should avoid + // g_get_current_dir because this leads to problems on OS X where + // Inkscape opens the dialog inside application bundle when it is + // invoked for the first teim. + } + + return path; +} + +void +store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + prefs->setString("/dialogs/save_as/default", extension); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + prefs->setString("/dialogs/save_copy/default", extension); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + case FILE_SAVE_METHOD_EXPORT: + // do nothing + break; + } +} + +void +store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + switch (method) { + case FILE_SAVE_METHOD_SAVE_AS: + case FILE_SAVE_METHOD_TEMPORARY: + prefs->setString("/dialogs/save_as/path", path); + break; + case FILE_SAVE_METHOD_SAVE_COPY: + prefs->setString("/dialogs/save_copy/path", path); + break; + case FILE_SAVE_METHOD_INKSCAPE_SVG: + case FILE_SAVE_METHOD_EXPORT: + // do nothing + break; + } +} + +} } /* namespace Inkscape::Extension */ + +/* + 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/extension/system.h b/src/extension/system.h new file mode 100644 index 0000000..ea70a0f --- /dev/null +++ b/src/extension/system.h @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * This is file is kind of the junk file. Basically everything that + * didn't fit in one of the other well defined areas, well, it's now + * here. Which is good in someways, but this file really needs some + * definition. Hopefully that will come ASAP. + * + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2002-2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_SYSTEM_H__ +#define INKSCAPE_EXTENSION_SYSTEM_H__ + +#include <glibmm/ustring.h> + +class SPDocument; + +namespace Inkscape { + +namespace Extension { +class Extension; +class Print; + +namespace Implementation { +class Implementation; +} + +/** + * Used to distinguish between the various invocations of the save dialogs (and thus to determine + * the file type and save path offered in the dialog) + */ +enum FileSaveMethod { + FILE_SAVE_METHOD_SAVE_AS, + FILE_SAVE_METHOD_SAVE_COPY, + FILE_SAVE_METHOD_EXPORT, + // Fallback for special cases (e.g., when saving a document for the first time or after saving + // it in a lossy format) + FILE_SAVE_METHOD_INKSCAPE_SVG, + // For saving temporary files; we return the same data as for FILE_SAVE_METHOD_SAVE_AS + FILE_SAVE_METHOD_TEMPORARY, +}; + +SPDocument *open(Extension *key, gchar const *filename); +void save(Extension *key, SPDocument *doc, gchar const *filename, + bool setextension, bool check_overwrite, bool official, + Inkscape::Extension::FileSaveMethod save_method); +Print *get_print(gchar const *key); +void build_from_file(gchar const *filename); +void build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp); + +/** + * Determine the desired default file extension depending on the given file save method. + * The returned string is guaranteed to be non-empty. + * + * @param method the file save method of the dialog + * @return the corresponding default file extension + */ +Glib::ustring get_file_save_extension (FileSaveMethod method); + +/** + * Determine the desired default save path depending on the given FileSaveMethod. + * The returned string is guaranteed to be non-empty. + * + * @param method the file save method of the dialog + * @param doc the file's document + * @return the corresponding default save path + */ +Glib::ustring get_file_save_path (SPDocument *doc, FileSaveMethod method); + +/** + * Write the given file extension back to prefs so that it can be used later on. + * + * @param extension the file extension which should be written to prefs + * @param method the file save method of the dialog + */ +void store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method); + +/** + * Write the given path back to prefs so that it can be used later on. + * + * @param path the path which should be written to prefs + * @param method the file save method of the dialog + */ +void store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method); + +} } /* namespace Inkscape::Extension */ + +#endif /* INKSCAPE_EXTENSION_SYSTEM_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/extension/timer.cpp b/src/extension/timer.cpp new file mode 100644 index 0000000..7ed856e --- /dev/null +++ b/src/extension/timer.cpp @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Here is where the extensions can get timed on when they load and + * unload. All of the timing is done in here. + * + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <glibmm/main.h> + +#include "extension.h" +#include "timer.h" + +namespace Inkscape { +namespace Extension { + +#define TIMER_SCALE_VALUE 20 + +ExpirationTimer * ExpirationTimer::timer_list = nullptr; +ExpirationTimer * ExpirationTimer::idle_start = nullptr; +long ExpirationTimer::timeout = 240; +bool ExpirationTimer::timer_started = false; + +/** \brief Create a new expiration timer + \param in_extension Which extension this timer is related to + + This function creates the timer, and sets the time to the current + time, plus what ever the current timeout is. Also, if this is + the first timer extension, the timer is kicked off. This function + also sets up the circularly linked list of all the timers. +*/ +ExpirationTimer::ExpirationTimer (Extension * in_extension): + locked(0), + extension(in_extension) +{ + /* Fix Me! */ + if (timer_list == nullptr) { + next = this; + timer_list = this; + } else { + next = timer_list->next; + timer_list->next = this; + } + + expiration.assign_current_time(); + expiration += timeout; + + if (!timer_started) { + Glib::signal_timeout().connect(sigc::ptr_fun(&timer_func), timeout * 1000 / TIMER_SCALE_VALUE); + timer_started = true; + } + + return; +} + +/** \brief Deletes a \c ExpirationTimer + + The most complex thing that this function does is remove the timer + from the circularly linked list. If this is the only entry in the + list that is easy, otherwise all the entries must be found, and this + one removed from the list. +*/ +ExpirationTimer::~ExpirationTimer() +{ + if (this != next) { + /* This will remove this entry from the circularly linked + list. */ + ExpirationTimer * prev; + for (prev = timer_list; + prev->next != this; + prev = prev->next){}; + prev->next = next; + + if (idle_start == this) + idle_start = next; + + /* This may occur more than you think, just because the guy + doing most of the deletions is the idle function, who tracks + where it is looking using the \c timer_list variable. */ + if (timer_list == this) + timer_list = next; + } else { + /* If we're the only entry in the list, the list needs to go + to NULL */ + timer_list = nullptr; + idle_start = nullptr; + } + + return; +} + +/** \brief Touches the timer to extend the length before it expires + + Basically it adds more time to the timer. One thing that is kinda + tricky is that it adds half the time remaining back into the timer. + This allows for some extensions that are used regularly to having + extended expiration times. So, in the end, they stay loaded longer. + Extensions that are only used once will expire at a standard rate + set by \c timeout. +*/ +void +ExpirationTimer::touch () +{ + Glib::TimeVal current; + current.assign_current_time(); + + long time_left = (long)(expiration.as_double() - current.as_double()); + if (time_left < 0) time_left = 0; + time_left /= 2; + + expiration = current + timeout + time_left; + return; +} + +/** \brief Check to see if the timer has expired + + Checks the time against the current time. +*/ +bool +ExpirationTimer::expired () const +{ + if (locked > 0) return false; + + Glib::TimeVal current; + current.assign_current_time(); + return expiration < current; +} + +// int idle_cnt = 0; + +/** \brief This function goes in the idle loop to find expired extensions + \return Whether the function should be requeued or not + + This function first insures that there is a timer list, and then checks + to see if the one on the top of the list has expired. If it has + expired it unloads the module. By unloading the module, the timer + gets deleted (happens in the unload function). If the list is + no empty, the function returns that it should be dequeued and sets + the \c timer_started variable so that the timer will be reissued when + a timer is added. If there is entries left, but the next one is + where this function started, then the timer is set up. The timer + will then re-add the idle loop function when it runs. +*/ +bool +ExpirationTimer::idle_func () +{ + // std::cout << "Idle func pass: " << idle_cnt++ << " timer list: " << timer_list << std::endl; + + /* see if this is the last */ + if (timer_list == nullptr) { + timer_started = false; + return false; + } + + /* evaluate current */ + if (timer_list->expired()) { + timer_list->extension->set_state(Extension::STATE_UNLOADED); + } + + /* see if this is the last */ + if (timer_list == nullptr) { + timer_started = false; + return false; + } + + if (timer_list->next == idle_start) { + /* if so, set up the timer and return FALSE */ + /* Note: This may cause one to be missed on the evaluation if + the one before it expires and it is last in the list. + While this could be taken care of, it isn't worth the + complexity for this lazy removal that we're doing. It + should get picked up next time */ + Glib::signal_timeout().connect(sigc::ptr_fun(&timer_func), timeout * 1000 / TIMER_SCALE_VALUE); + return false; + } + + /* If nothing else, continue on */ + timer_list = timer_list->next; + return true; +} + +/** \brief A timer function to set up the idle function + \return Always false -- to disable the timer + + This function sets up the idle loop when it runs. The idle loop is + the one that unloads all the extensions. +*/ +bool +ExpirationTimer::timer_func () +{ + // std::cout << "Timer func" << std::endl; + idle_start = timer_list; + // idle_cnt = 0; + Glib::signal_idle().connect(sigc::ptr_fun(&idle_func)); + return false; +} + +}; }; /* namespace Inkscape, Extension */ + +/* + 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/extension/timer.h b/src/extension/timer.h new file mode 100644 index 0000000..a4f9bfe --- /dev/null +++ b/src/extension/timer.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Here is where the extensions can get timed on when they load and + * unload. All of the timing is done in here. + * + * Authors: + * Ted Gould <ted@gould.cx> + * + * Copyright (C) 2004 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#ifndef INKSCAPE_EXTENSION_TIMER_H__ +#define INKSCAPE_EXTENSION_TIMER_H__ + +#include <cstddef> +#include <sigc++/sigc++.h> +#include <glibmm/timeval.h> + +namespace Inkscape { +namespace Extension { + +class Extension; + +class ExpirationTimer { + /** \brief Circularly linked list of all timers */ + static ExpirationTimer * timer_list; + /** \brief Which timer was on top when we started the idle loop */ + static ExpirationTimer * idle_start; + /** \brief What the current timeout is */ + static long timeout; + /** \brief Has the timer been started? */ + static bool timer_started; + + /** \brief Is this extension locked from being unloaded? */ + int locked; + /** \brief Next entry in the list */ + ExpirationTimer * next; + /** \brief When this timer expires */ + Glib::TimeVal expiration; + /** \brief What extension this function relates to */ + Extension * extension; + + bool expired() const; + + static bool idle_func (); + static bool timer_func (); + +public: + ExpirationTimer(Extension * in_extension); + virtual ~ExpirationTimer(); + + void touch (); + void lock () { locked++; }; + void unlock () { locked--; }; + + /** \brief Set the timeout variable */ + static void set_timeout (long in_seconds) { timeout = in_seconds; }; +}; + +}; }; /* namespace Inkscape, Extension */ + +#endif /* INKSCAPE_EXTENSION_TIMER_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 : |