diff options
Diffstat (limited to 'src/extension/implementation')
| -rw-r--r-- | src/extension/implementation/.cvsignore | 5 | ||||
| -rw-r--r-- | src/extension/implementation/Makefile_insert | 13 | ||||
| -rw-r--r-- | src/extension/implementation/implementation.cpp | 181 | ||||
| -rw-r--r-- | src/extension/implementation/implementation.h | 127 | ||||
| -rw-r--r-- | src/extension/implementation/makefile.in | 17 | ||||
| -rw-r--r-- | src/extension/implementation/plugin-link.h | 68 | ||||
| -rw-r--r-- | src/extension/implementation/plugin.cpp | 328 | ||||
| -rw-r--r-- | src/extension/implementation/plugin.h | 121 | ||||
| -rw-r--r-- | src/extension/implementation/script.cpp | 1053 | ||||
| -rw-r--r-- | src/extension/implementation/script.h | 86 |
10 files changed, 1999 insertions, 0 deletions
diff --git a/src/extension/implementation/.cvsignore b/src/extension/implementation/.cvsignore new file mode 100644 index 000000000..e8014d011 --- /dev/null +++ b/src/extension/implementation/.cvsignore @@ -0,0 +1,5 @@ +Makefile +Makefile.in +.deps +makefile +.dirstamp diff --git a/src/extension/implementation/Makefile_insert b/src/extension/implementation/Makefile_insert new file mode 100644 index 000000000..5d70216d2 --- /dev/null +++ b/src/extension/implementation/Makefile_insert @@ -0,0 +1,13 @@ +## Makefile.am fragment sourced by src/Makefile.am. + +extension/implementation/all: extension/implementation/libimplementation.a + +extension/implementation/clean: + rm -f extension/implementation/libimplementation.a \ + $(extension_implementation_libimplementation_a_OBJECTS) + +extension_implementation_libimplementation_a_SOURCES = \ + extension/implementation/implementation.cpp \ + extension/implementation/implementation.h \ + extension/implementation/script.cpp \ + extension/implementation/script.h diff --git a/src/extension/implementation/implementation.cpp b/src/extension/implementation/implementation.cpp new file mode 100644 index 000000000..c959d50b3 --- /dev/null +++ b/src/extension/implementation/implementation.cpp @@ -0,0 +1,181 @@ +/* + Author: Ted Gould <ted@gould.cx> + Copyright (c) 2003-2005 + + This code is licensed under the GNU GPL. See COPYING for details. + + 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. +*/ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include "implementation.h" + +#include <extension/output.h> +#include <extension/input.h> +#include <extension/effect.h> + +namespace Inkscape { +namespace Extension { +namespace Implementation { + +/** + * \return Was the load sucessful? + * \brief This function is the stub load. It just returns success. + * \param module The Extension that should be loaded. + */ +bool +Implementation::load(Inkscape::Extension::Extension *module) { + return TRUE; +} /* Implementation::load */ + +void +Implementation::unload(Inkscape::Extension::Extension *module) { + return; +} /* Implementation::unload */ + +bool +Implementation::check(Inkscape::Extension::Extension *module) { + /* If there are no checks, they all pass */ + return TRUE; +} /* Implemenation::check */ + +Gtk::Widget * +Implementation::prefs_input(Inkscape::Extension::Input *module, gchar const *filename) { + return module->autogui(); +} /* Implementation::prefs_input */ + +SPDocument * +Implementation::open(Inkscape::Extension::Input *module, gchar const *filename) { + /* throw open_failed(); */ + return NULL; +} /* Implementation::open */ + +Gtk::Widget * +Implementation::prefs_output(Inkscape::Extension::Output *module) { + return module->autogui(); +} /* Implementation::prefs_output */ + +void +Implementation::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) { + /* throw save_fail */ + return; +} /* Implementation::save */ + +Gtk::Widget * +Implementation::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *view) { + return module->autogui(); +} /* Implementation::prefs_effect */ + +void +Implementation::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document) { + /* throw filter_fail */ + return; +} /* Implementation::filter */ + +unsigned int +Implementation::setup(Inkscape::Extension::Print *module) +{ + return 0; +} + +unsigned int +Implementation::set_preview(Inkscape::Extension::Print *module) +{ + return 0; +} + + +unsigned int +Implementation::begin(Inkscape::Extension::Print *module, SPDocument *doc) +{ + return 0; +} + +unsigned int +Implementation::finish(Inkscape::Extension::Print *module) +{ + return 0; +} + + +/* Rendering methods */ +unsigned int +Implementation::bind(Inkscape::Extension::Print *module, NRMatrix const *transform, float opacity) +{ + return 0; +} + +unsigned int +Implementation::release(Inkscape::Extension::Print *module) +{ + return 0; +} + +unsigned int +Implementation::comment(Inkscape::Extension::Print *module, char const *comment) +{ + return 0; +} + +unsigned int +Implementation::fill(Inkscape::Extension::Print *module, NRBPath const *bpath, NRMatrix const *ctm, SPStyle const *style, + NRRect const *pbox, NRRect const *dbox, NRRect const *bbox) +{ + return 0; +} + +unsigned int +Implementation::stroke(Inkscape::Extension::Print *module, NRBPath const *bpath, NRMatrix const *transform, SPStyle const *style, + NRRect const *pbox, NRRect const *dbox, NRRect const *bbox) +{ + return 0; +} + +unsigned int +Implementation::image(Inkscape::Extension::Print *module, unsigned char *px, unsigned int w, unsigned int h, unsigned int rs, + NRMatrix const *transform, SPStyle const *style) +{ + return 0; +} + +unsigned int +Implementation::text(Inkscape::Extension::Print *module, char const *text, + NR::Point p, SPStyle const *style) +{ + return 0; +} + +/** + \brief Tell the printing engine whether text should be text or path + \retval true Render the text as a path + \retval false Render text using the text function (above) + + Default value is false because most printing engines will support + paths more than they'll support text. (at least they do today) +*/ +bool +Implementation::textToPath(Inkscape::Extension::Print *ext) +{ + return false; +} + + +} /* 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:encoding=utf-8:textwidth=99 : diff --git a/src/extension/implementation/implementation.h b/src/extension/implementation/implementation.h new file mode 100644 index 000000000..68c2eb04c --- /dev/null +++ b/src/extension/implementation/implementation.h @@ -0,0 +1,127 @@ +/* + Author: Ted Gould <ted@gould.cx> + Copyright (c) 2003-2005 + + This code is licensed under the GNU GPL. See COPYING for details. + + 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 __INKSCAPE_EXTENSION_IMPLEMENTATION_H__ +#define __INKSCAPE_EXTENSION_IMPLEMENTATION_H__ + +#include <gtk/gtkdialog.h> +#include <gdkmm/types.h> +#include <gtkmm/widget.h> + +#include "forward.h" +#include "extension/extension-forward.h" +#include "libnr/nr-forward.h" +#include "libnr/nr-point.h" + +namespace Inkscape { +namespace Extension { +namespace Implementation { + +/** + * 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() {} + + virtual ~Implementation() {} + + /* ----- Basic functions for all Extension ----- */ + virtual bool load(Inkscape::Extension::Extension *module); + + virtual void unload(Inkscape::Extension::Extension *module); + + /** Verify any dependencies. */ + virtual bool check(Inkscape::Extension::Extension *module); + + + /* ----- 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); + + /* ----- 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); + /* TODO: need to figure out what we need here */ + + virtual void effect(Inkscape::Extension::Effect *module, + Inkscape::UI::View::View *document); + + /* ----- Print functions ----- */ + virtual unsigned setup(Inkscape::Extension::Print *module); + virtual unsigned set_preview(Inkscape::Extension::Print *module); + + virtual unsigned begin(Inkscape::Extension::Print *module, + SPDocument *doc); + virtual unsigned finish(Inkscape::Extension::Print *module); + virtual bool textToPath(Inkscape::Extension::Print *ext); + + /* ----- Rendering methods ----- */ + virtual unsigned bind(Inkscape::Extension::Print *module, + NRMatrix const *transform, + float opacity); + virtual unsigned release(Inkscape::Extension::Print *module); + virtual unsigned comment(Inkscape::Extension::Print *module, const char * comment); + virtual unsigned fill(Inkscape::Extension::Print *module, + NRBPath const *bpath, + NRMatrix const *ctm, + SPStyle const *style, + NRRect const *pbox, + NRRect const *dbox, + NRRect const *bbox); + virtual unsigned stroke(Inkscape::Extension::Print *module, + NRBPath const *bpath, + NRMatrix const *transform, + SPStyle const *style, + NRRect const *pbox, + NRRect const *dbox, + NRRect const *bbox); + virtual unsigned image(Inkscape::Extension::Print *module, + unsigned char *px, + unsigned int w, + unsigned int h, + unsigned int rs, + NRMatrix const *transform, + SPStyle const *style); + virtual unsigned text(Inkscape::Extension::Print *module, + char const *text, + NR::Point p, + SPStyle const *style); +}; + +} /* 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/makefile.in b/src/extension/implementation/makefile.in new file mode 100644 index 000000000..c0bf21190 --- /dev/null +++ b/src/extension/implementation/makefile.in @@ -0,0 +1,17 @@ +# Convenience stub makefile to call the real Makefile. + +@SET_MAKE@ + +# Explicit so that it's the default rule. +all: + cd ../.. && $(MAKE) extension/implementation/all + +clean %.a %.o: + cd ../.. && $(MAKE) extension/implementation/$@ + +.PHONY: all clean + +OBJEXT = @OBJEXT@ + +.SUFFIXES: +.SUFFIXES: .a .$(OBJEXT) diff --git a/src/extension/implementation/plugin-link.h b/src/extension/implementation/plugin-link.h new file mode 100644 index 000000000..740b8952a --- /dev/null +++ b/src/extension/implementation/plugin-link.h @@ -0,0 +1,68 @@ +/** \file + * Plugin prototypes. + * + * This header describes which prototypes plugins should use when + * creating their functions. This header is also used by the internal + * plugins code to guarantee consistency. + * + * Author: Ted Gould <ted@gould.cx> + * Copyright (c) 2004-2005 + * + * This code is licensed under the GNU GPL. See COPYING for details. + */ + +#ifndef __INKSCAPE_EXTENSION_IMPLEMENTATION_PLUGIN_LINK_H__ +#define __INKSCAPE_EXTENSION_IMPLEMENTATION_PLUGIN_LINK_H__ + +#include <gtk/gtkdialog.h> +#include <gtkmm/widget.h> + +/** \todo This needs to go away eventually. */ +#include "document.h" + +/** \brief A simple typedef to make it so that inkscape_extension can + be used before I figure out what makes sense here */ +typedef void inkscape_extension; +/** \brief The C prototype of a load function. */ +typedef int (*inkscape_plugin_load)(inkscape_extension * in_ext); +/** \brief The C prototype of an unload function. */ +typedef void (*inkscape_plugin_unload)(inkscape_extension * in_ext); +/** \brief The C prototype of an open function. */ +typedef SPDocument *(*inkscape_plugin_open)(inkscape_extension * in_ext, const gchar * filename); +/** \brief The C prototype of an input prefs function. */ +typedef Gtk::Widget * (*inkscape_plugin_prefs_input)(inkscape_extension * in_ext, gchar const * filename); +/** \brief The C prototype of an effect function. */ +typedef void (*inkscape_plugin_effect)(inkscape_extension * in_ext, Inkscape::UI::View::View * view); +/** \brief The C prototype of an effect prefs function. */ +typedef Gtk::Widget * (*inkscape_plugin_prefs_effect)(inkscape_extension * in_ext, Inkscape::UI::View::View * view); + +/** \brief The name of the symbol for the plugin. Should match + \c INKSCAPE_PLUGIN_NAME_STR (minus the quotes). */ +#define INKSCAPE_PLUGIN_NAME inkscape_plugin_table +/** \brief The name of the table to define the plugin as a string. This + should be the same as \c INKSCAPE_PLUGIN_NAME but with quotes. */ +#define INKSCAPE_PLUGIN_NAME_STR "inkscape_plugin_table" +/** \brief The version of the plugin interface that is being used. This + should always be used in the version entry in the \c inkscape_plugin_function_table + version entry. This way compiled plugins can be detected. */ +#define INKSCAPE_PLUGIN_VERSION 0 + +/** \brief A structure containing all the functions that should be called + to make the plugin work. */ +typedef struct { + int version; /**< The interface version used. Should + always be \c INKSCAPE_PLUGIN_VERSION. */ + inkscape_plugin_load load; /**< Load function, called on first use */ + inkscape_plugin_unload unload; /**< Unload function, called when Inkscape is + finished with the plugin */ + inkscape_plugin_open open; /**< Open function, called to open a file + for Inkscape */ + inkscape_plugin_prefs_input prefs_input; /**< Input preferences function, called to get + further parameters for an input plugin. */ + inkscape_plugin_effect effect; /**< Effect function, called to cause an effect + on a document. */ + inkscape_plugin_prefs_effect prefs_effect;/**< Effect preferences, on call could cause settings + on a document. */ +} inkscape_plugin_function_table; + +#endif /* __INKSCAPE_EXTENSION_IMPLEMENTATION_PLUGIN_LINK_H__ */ diff --git a/src/extension/implementation/plugin.cpp b/src/extension/implementation/plugin.cpp new file mode 100644 index 000000000..0bc3267f4 --- /dev/null +++ b/src/extension/implementation/plugin.cpp @@ -0,0 +1,328 @@ +/** \file + * The implementation of pluggable objects into Inkscape. + * + * Author: Ted Gould <ted@gould.cx> + * Copyright (c) 2004-2005 + * + * This code is licensed under the GNU GPL. See COPYING for details. + * + * This file implements loadable modules for Inkscape. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <stdio.h> + +#include <glibmm/module.h> +#include <glibmm/fileutils.h> +#include <path-prefix.h> +#include "extension/extension.h" +#include "xml/repr.h" +#include "plugin.h" +#include "plugin-link.h" + +namespace Inkscape { +namespace Extension { +namespace Implementation { + +/** \brief Create an object by nulling everything out. */ +Plugin::Plugin(void) +{ + _module = NULL; + _symTable = NULL; + + return; +} + +/** + \brief Oh, so someone actually wants to use this plugin! We better + go and grab it then! + \param module Unused except to pass to the plugin's load function. + \return Whether the load was successful. Hopefully always TRUE. + + Okay, first things first, are modules supported on this platform? That + is a good first check. Also, is this plugin already loaded? If so + don't reload it. + + If all those are true we need to figure out the filename that needs + to be loaded. This involves going through the definition of the plugin + for the \c name field. It is then run though the \c build_path function + to add the .so or .dll on the end. The path that is used is the + \c INKSCAPE_PLUGINDIR. + + The module is then loaded into RAM by Glib. I'm sure there is lots + of magic involved here -- but we don't have to worry about it, we + just check to make sure it worked. + + After it is loaded into memory the function lookup table is grabbed + and then the load function for the plugin is called. +*/ +bool +Plugin::load(Inkscape::Extension::Extension *module) +{ + if (!Glib::Module::get_supported()) { + return FALSE; + } + + if (module->loaded()) { + return TRUE; + } + + Inkscape::XML::Node * child_repr = sp_repr_children(module->get_repr()); + const gchar * name = NULL; + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "plugin")) { + child_repr = sp_repr_children(child_repr); + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "name")) { + name = sp_repr_children(child_repr)->content(); + } + child_repr = sp_repr_next(child_repr); + } + } + child_repr = sp_repr_next(child_repr); + } + + if (name == NULL) { + return FALSE; + } + + std::string path = Glib::Module::build_path(INKSCAPE_PLUGINDIR, name); + // std::cout << "Load path: " << path << std::endl; + _module = new Glib::Module(path); + + if (!(bool)_module || _module == NULL) { + printf("Loading failed\n"); + return FALSE; + } + + /* Grab symbols */ + void * voidpntr; + if (!_module->get_symbol(INKSCAPE_PLUGIN_NAME_STR, voidpntr)) { + // printf("Error loading library\n"); + // std::cout << "Last error: " << _module->get_last_error() << std::endl; + return FALSE; + } + _symTable = (inkscape_plugin_function_table *)voidpntr; + + if (_symTable->version != INKSCAPE_PLUGIN_VERSION) { + /* Someday this could adapt older versions so that we could + be compatible -- but not today */ + return FALSE; + } + + if (_symTable->load != NULL) { + return (bool)_symTable->load((inkscape_extension *)module); + } + + return TRUE; +} + +/** + \brief No one is interested in this plugin for now. It is removed + to save memory. + \param module The module of this implementation, passed to unload. + \return None. + + Call the unload function of the plugin, then delete the symbol table + and the module itself. Put everything back to NULL. +*/ +void +Plugin::unload(Inkscape::Extension::Extension *module) +{ + _symTable->unload((inkscape_extension *)module); + _symTable = NULL; + delete _module; + _module = NULL; + return; +} + +/** + \brief Just check to make sure everything exists. This makes sure + that the loadable file exits. + \param module Unused. + \return The status of the check. TRUE means that it passed. + + This function builds the path name out of the XML definition of the + plugin. It does this by looking for the \c name parameter. It then + uses \c build_path to add the .so or .dll and adds in the \c INKSCAPE_PLUGINDIR + to the front of it. Then the \c file_test function is used to make + sure it exists. +*/ +bool +Plugin::check(Inkscape::Extension::Extension *module) +{ + Inkscape::XML::Node * child_repr = sp_repr_children(module->get_repr()); + const gchar * name = NULL; + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "plugin")) { + child_repr = sp_repr_children(child_repr); + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "name")) { + name = sp_repr_children(child_repr)->content(); + } + child_repr = sp_repr_next(child_repr); + } + } + child_repr = sp_repr_next(child_repr); + } + + if (name == NULL) { + return FALSE; + } + + std::string path = Glib::Module::build_path(INKSCAPE_PLUGINDIR, name); + // std::cout << "Path: " << path << std::endl; + if (!Glib::file_test(path, Glib::FILE_TEST_EXISTS | Glib::FILE_TEST_IS_EXECUTABLE)) { + // std::cout << "Failed!" << std::endl; + return FALSE; + } + + // std::cout << "No Problem." << std::endl; + return TRUE; +} + +Gtk::Widget * +Plugin::prefs_input(Inkscape::Extension::Input *module, gchar const *filename) +{ + return Inkscape::Extension::Implementation::Implementation::prefs_input(module, filename); +} + +/* + \brief Function to call the open in the plugin. + \param module Passed on + \param filename Passed on + \return The document that is opened or NULL for error. + + This function looks in the symbol table to see if there is an open + function. If there is, it is called, otherwise the standard implementation + function is used instead. +*/ +SPDocument * +Plugin::open(Inkscape::Extension::Input *module, gchar const *filename) +{ + if (_symTable->open != NULL) { + return _symTable->open((inkscape_extension *)module, filename); + } else { + return Inkscape::Extension::Implementation::Implementation::open(module, filename); + } +} + +Gtk::Widget * +Plugin::prefs_output(Inkscape::Extension::Output *module) +{ + return Inkscape::Extension::Implementation::Implementation::prefs_output(module); +} + +void +Plugin::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) +{ + return Inkscape::Extension::Implementation::Implementation::save(module, doc, filename); +} + +Gtk::Widget * +Plugin::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view) +{ + if (_symTable->prefs_effect != NULL) { + return _symTable->prefs_effect((inkscape_extension *)module, view); + } else { + return Inkscape::Extension::Implementation::Implementation::prefs_effect(module, view); + } +} + +void +Plugin::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document) +{ + if (_symTable->effect != NULL) { + return _symTable->effect((inkscape_extension *)module, document); + } else { + return Inkscape::Extension::Implementation::Implementation::effect(module, document); + } +} + +unsigned +Plugin::setup(Inkscape::Extension::Print *module) +{ + return Inkscape::Extension::Implementation::Implementation::setup(module); +} + +unsigned +Plugin::set_preview(Inkscape::Extension::Print *module) +{ + return Inkscape::Extension::Implementation::Implementation::set_preview(module); +} + +unsigned +Plugin::begin(Inkscape::Extension::Print *module, SPDocument *doc) +{ + return Inkscape::Extension::Implementation::Implementation::begin(module, doc); +} + +unsigned +Plugin::finish(Inkscape::Extension::Print *module) +{ + return Inkscape::Extension::Implementation::Implementation::finish(module); +} + +bool +Plugin::textToPath(Inkscape::Extension::Print *ext) +{ + return Inkscape::Extension::Implementation::Implementation::finish(ext); +} + +unsigned +Plugin::bind(Inkscape::Extension::Print *module, NRMatrix const *transform, float opacity) +{ + return Inkscape::Extension::Implementation::Implementation::bind(module, transform, opacity); +} + +unsigned +Plugin::release(Inkscape::Extension::Print *module) +{ + return Inkscape::Extension::Implementation::Implementation::release(module); +} + +unsigned +Plugin::comment(Inkscape::Extension::Print *module, const char * comment) +{ + return Inkscape::Extension::Implementation::Implementation::comment(module,comment); +} + +unsigned +Plugin::fill(Inkscape::Extension::Print *module, NRBPath const *bpath, NRMatrix const *ctm, SPStyle const *style, NRRect const *pbox, NRRect const *dbox, NRRect const *bbox) +{ + return Inkscape::Extension::Implementation::Implementation::fill(module, bpath, ctm, style, pbox, dbox, bbox); +} + +unsigned +Plugin::stroke(Inkscape::Extension::Print *module, NRBPath const *bpath, NRMatrix const *transform, SPStyle const *style, NRRect const *pbox, NRRect const *dbox, NRRect const *bbox) +{ + return Inkscape::Extension::Implementation::Implementation::stroke(module, bpath, transform, style, pbox, dbox, bbox); +} + +unsigned +Plugin::image(Inkscape::Extension::Print *module, unsigned char *px, unsigned int w, unsigned int h, unsigned int rs, NRMatrix const *transform, SPStyle const *style) +{ + return Inkscape::Extension::Implementation::Implementation::image(module, px, w, h, rs, transform, style); +} + +unsigned +Plugin::text(Inkscape::Extension::Print *module, char const *text, NR::Point p, SPStyle const *style) +{ + return Inkscape::Extension::Implementation::Implementation::text(module, text, p, style); +} + +} /* 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 : diff --git a/src/extension/implementation/plugin.h b/src/extension/implementation/plugin.h new file mode 100644 index 000000000..88658e954 --- /dev/null +++ b/src/extension/implementation/plugin.h @@ -0,0 +1,121 @@ +/** \file + * Inkscape::Extension::Implementation::Plugin + * + * Author: Ted Gould <ted@gould.cx> + * Copyright (c) 2004-2005 + * + * This code is licensed under the GNU GPL. See COPYING for details. + */ +#ifndef __INKSCAPE_EXTENSION_IMPLEMENTATION_PLUGIN_H__ +#define __INKSCAPE_EXTENSION_IMPLEMENTATION_PLUGIN_H__ + +#include <extension/implementation/implementation.h> +#include <glibmm/module.h> +#include "plugin-link.h" + +namespace Inkscape { +namespace Extension { +namespace Implementation { + +/** \brief For the most part this is a direct steal from \c implementation.h + in that all the functions have the same prototypes. The two + added things are a pointer to the loaded module and a pointer + to the symbol table in that. */ +class Plugin : public Implementation { + /** \brief A pointer to the module created when loading the plugin. */ + Glib::Module * _module; + /** \brief The symbol table that is in the plugin. It is pulled out + here so that it doesn't have to be grabbed as often. */ + inkscape_plugin_function_table * _symTable; + +public: + Plugin(void); + + /* ----- Basic functions for all Extension ----- */ + virtual bool load(Inkscape::Extension::Extension *module); + + virtual void unload(Inkscape::Extension::Extension *module); + + /** Verify any dependencies. */ + virtual bool check(Inkscape::Extension::Extension *module); + + + /* ----- 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); + + /* ----- 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); + /* TODO: need to figure out what we need here */ + + virtual void effect(Inkscape::Extension::Effect *module, + Inkscape::UI::View::View *document); + + /* ----- Print functions ----- */ + virtual unsigned setup(Inkscape::Extension::Print *module); + virtual unsigned set_preview(Inkscape::Extension::Print *module); + + virtual unsigned begin(Inkscape::Extension::Print *module, + SPDocument *doc); + virtual unsigned finish(Inkscape::Extension::Print *module); + virtual bool textToPath(Inkscape::Extension::Print *ext); + + /* ----- Rendering methods ----- */ + virtual unsigned bind(Inkscape::Extension::Print *module, + NRMatrix const *transform, + float opacity); + virtual unsigned release(Inkscape::Extension::Print *module); + virtual unsigned comment(Inkscape::Extension::Print *module, const char * comment); + virtual unsigned fill(Inkscape::Extension::Print *module, + NRBPath const *bpath, + NRMatrix const *ctm, + SPStyle const *style, + NRRect const *pbox, + NRRect const *dbox, + NRRect const *bbox); + virtual unsigned stroke(Inkscape::Extension::Print *module, + NRBPath const *bpath, + NRMatrix const *transform, + SPStyle const *style, + NRRect const *pbox, + NRRect const *dbox, + NRRect const *bbox); + virtual unsigned image(Inkscape::Extension::Print *module, + unsigned char *px, + unsigned int w, + unsigned int h, + unsigned int rs, + NRMatrix const *transform, + SPStyle const *style); + virtual unsigned text(Inkscape::Extension::Print *module, + char const *text, + NR::Point p, + SPStyle const *style); +}; + +} /* namespace Implementation */ +} /* namespace Extension */ +} /* namespace Inkscape */ + +#endif /* __INKSCAPE_EXTENSION_IMPLEMENTATION_PLUGIN_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 000000000..f9cc2ee0b --- /dev/null +++ b/src/extension/implementation/script.cpp @@ -0,0 +1,1053 @@ +/** \file + * 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, read the file 'COPYING' for more information + */ + +#define __INKSCAPE_EXTENSION_IMPLEMENTATION_SCRIPT_C__ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <unistd.h> + +#include <errno.h> +#include <gtkmm/textview.h> +#include <gtkmm/scrolledwindow.h> + +#include "ui/view/view.h" +#include "desktop-handles.h" +#include "selection.h" +#include "sp-namedview.h" +#include "io/sys.h" +#include "prefs-utils.h" +#include "../system.h" +#include "extension/effect.h" +#include "extension/db.h" +#include "script.h" + +#include "util/glib-list-iterators.h" + +#ifdef WIN32 +#include <windows.h> +#endif + +/** This is the command buffer that gets allocated from the stack */ +#define BUFSIZE (255) + +/* Namespaces */ +namespace Inkscape { +namespace Extension { +namespace Implementation { + +/* Real functions */ +/** + \return A script object + \brief This function creates a script object and sets up the + variables. + + 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(), + command(NULL), + helper_extension(NULL) +{ +} + +/** + \return A string with the complete string with the relative directory expanded + \brief This function takes in a Repr that contains a reldir entry + and returns that data with the relative directory expanded. + Mostly it is here so that relative directories all get used + the same way. + \param reprin The Inkscape::XML::Node with the reldir in it. + + Basically this function looks at an attribute of the Repr, and makes + a decision based on that. Currently, it is only working with the + 'extensions' relative directory, but there will be more of them. + One thing to notice is that this function always returns an allocated + string. This means that the caller of this function can always + free what they are given (and should do it too!). +*/ +gchar * +Script::solve_reldir(Inkscape::XML::Node *reprin) { + gchar const *reldir = reprin->attribute("reldir"); + + if (reldir == NULL) { + return g_strdup(sp_repr_children(reprin)->content()); + } + + if (!strcmp(reldir, "extensions")) { + for(unsigned int i=0; i<Inkscape::Extension::Extension::search_path.size(); i++) { + gchar * filename = g_build_filename(Inkscape::Extension::Extension::search_path[i], sp_repr_children(reprin)->content(), NULL); + if ( Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) ) { + return filename; + } + g_free(filename); + } + } else { + return g_strdup(sp_repr_children(reprin)->content()); + } + + return NULL; +} + +/** + \return Whether the command given exists, including in the path + \brief This function is used to find out if something exists for + the check command. It can look in the path if required. + \param command The command or file that should be looked for + + The first thing that this function does is check to see if the + incoming file name has a directory delimiter in it. This would + mean that it wants to control the directories, and should be + used directly. + + If not, the path is used. 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 +Script::check_existance(gchar const *command) +{ + if (*command == '\0') { + /* We check the simple case first. */ + return FALSE; + } + + if (g_utf8_strchr(command, -1, G_DIR_SEPARATOR) != NULL) { + /* Don't search when it contains a slash. */ + if (Inkscape::IO::file_test(command, G_FILE_TEST_EXISTS)) + return TRUE; + else + return FALSE; + } + + + gchar *path = g_strdup(g_getenv("PATH")); + if (path == NULL) { + /* 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 != NULL;) { + gchar *const local_path = path; + path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR); + if (path == NULL) { + break; + } + /* Not sure whether this is UTF8 happy, but it would seem + like it considering that I'm searching (and finding) + the ':' character */ + if (path != local_path && path != NULL) { + path[0] = '\0'; + path++; + } else { + path = NULL; + } + + gchar *final_name; + if (local_path == '\0') { + final_name = g_strdup(command); + } else { + final_name = g_build_filename(local_path, command, NULL); + } + + if (Inkscape::IO::file_test(final_name, G_FILE_TEST_EXISTS)) { + g_free(final_name); + g_free(orig_path); + return TRUE; + } + + g_free(final_name); + } + + return FALSE; +} + +/** + \return none + \brief This function 'loads' an extention, basically it determines + the full command for the extention and stores that. + \param module The extention 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 extention 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 = NULL; + + /* This should probably check to find the executable... */ + Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr()); + gchar *command_text = NULL; + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "script")) { + child_repr = sp_repr_children(child_repr); + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "command")) { + command_text = solve_reldir(child_repr); + + const gchar * interpretstr = child_repr->attribute("interpreter"); + if (interpretstr != NULL) { + struct interpreter_t { + gchar * identity; + gchar * prefstring; + gchar * defaultval; + }; + const interpreter_t interpreterlst[] = { + {"perl", "perl-interpreter", "perl"}, + {"python", "python-interpreter", "python"}, + {"ruby", "ruby-interpreter", "ruby"}, + {"shell", "shell-interpreter", "sh"} + }; /* Change count below if you change structure */ + for (unsigned int i = 0; i < 4; i++) { + if (!strcmp(interpretstr, interpreterlst[i].identity)) { + const gchar * insertText = interpreterlst[i].defaultval; + if (prefs_get_string_attribute("extensions", interpreterlst[i].prefstring) != NULL) + insertText = prefs_get_string_attribute("extensions", interpreterlst[i].prefstring); +#ifdef _WIN32 + else { + char szExePath[MAX_PATH]; + char szCurrentDir[MAX_PATH]; + GetCurrentDirectory(sizeof(szCurrentDir), szCurrentDir); + if (reinterpret_cast<unsigned>(FindExecutable(command_text, szCurrentDir, szExePath)) > 32) + insertText = szExePath; + } +#endif + + gchar * temp = command_text; + command_text = g_strconcat(insertText, " ", temp, NULL); + g_free(temp); + + break; + } + } + } + } + if (!strcmp(child_repr->name(), "helper_extension")) { + helper_extension = g_strdup(sp_repr_children(child_repr)->content()); + } + child_repr = sp_repr_next(child_repr); + } + + break; + } + child_repr = sp_repr_next(child_repr); + } + + g_return_val_if_fail(command_text != NULL, FALSE); + + if (command != NULL) + g_free(command); + command = command_text; + + 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) +{ + if (command != NULL) { + g_free(command); + command = NULL; + } + if (helper_extension != NULL) { + g_free(helper_extension); + helper_extension = NULL; + } + + return; +} + +/** + \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) +{ + Inkscape::XML::Node *child_repr = sp_repr_children(module->get_repr()); + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "script")) { + child_repr = sp_repr_children(child_repr); + while (child_repr != NULL) { + if (!strcmp(child_repr->name(), "check")) { + gchar *command_text = solve_reldir(child_repr); + if (command_text != NULL) { + /* I've got the command */ + bool existance; + + existance = check_existance(command_text); + g_free(command_text); + if (!existance) + return FALSE; + } + } + + if (!strcmp(child_repr->name(), "helper_extension")) { + gchar const *helper = sp_repr_children(child_repr)->content(); + if (Inkscape::Extension::db.get(helper) == NULL) { + return FALSE; + } + } + + child_repr = sp_repr_next(child_repr); + } + + break; + } + child_repr = sp_repr_next(child_repr); + } + + return TRUE; +} + +/** + \return A dialog for preferences + \brief A stub funtion 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, gchar const *filename) +{ + /*return module->autogui(); */ + return NULL; +} + +/** + \return A dialog for preferences + \brief A stub funtion 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();*/ + return NULL; +} + +/** + \return A dialog for preferences + \brief A stub funtion right now + \param module Module who's preferences need getting + + This function should really do something, right now it doesn't. +*/ +Gtk::Widget * +Script::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *view) +{ + return module->autogui(); +} + +/** + \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 on of those the function g_file_open_tmp is used with + the header of ink_ext_. + + The extension is then executed using the 'execute' function + with the filname coming in, and the temporary filename. After + That executing, 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, gchar const *filename) +{ + int data_read = 0; + gint tempfd; + gchar *tempfilename_out; + + // FIXME: process the GError instead of passing NULL + if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) { + /* Error, couldn't create temporary filename */ + if (errno == EINVAL) { + /* The last six characters of template were not XXXXXX. Now template is unchanged. */ + perror("Extension::Script: template for filenames is misconfigured.\n"); + exit(-1); + } else if (errno == EEXIST) { + /* Now the contents of template are undefined. */ + perror("Extension::Script: Could not create a unique temporary filename\n"); + return NULL; + } else { + perror("Extension::Script: Unknown error creating temporary filename\n"); + exit(-1); + } + } + + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = NULL; + gchar *local_filename = g_filename_from_utf8( filename, + -1, &bytesRead, &bytesWritten, &error); + + data_read = execute(command, local_filename, tempfilename_out); + g_free(local_filename); + + SPDocument *mydoc = NULL; + if (data_read > 10) { + if (helper_extension == NULL) { + mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out); + } else { + mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(helper_extension), tempfilename_out); + } + } + + if (mydoc != NULL) + sp_document_set_uri(mydoc, (const gchar *)filename); + + // make sure we don't leak file descriptors from g_file_open_tmp + close(tempfd); + // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name + unlink(tempfilename_out); + g_free(tempfilename_out); + + return mydoc; +} + +/** + \return none + \brief This function uses an extention to save a document. It first + creates an SVG file of the document, and then runs it through + the script. + \param module Extention to be used + \param doc Document to be saved + \param filename The name to save the final file as + + 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 extetion 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 g_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 extention_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, gchar const *filename) +{ + gint tempfd; + gchar *tempfilename_in; + // FIXME: process the GError instead of passing NULL + if ((tempfd = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) { + /* Error, couldn't create temporary filename */ + if (errno == EINVAL) { + /* The last six characters of template were not XXXXXX. Now template is unchanged. */ + perror("Extension::Script: template for filenames is misconfigured.\n"); + exit(-1); + } else if (errno == EEXIST) { + /* Now the contents of template are undefined. */ + perror("Extension::Script: Could not create a unique temporary filename\n"); + return; + } else { + perror("Extension::Script: Unknown error creating temporary filename\n"); + exit(-1); + } + } + + if (helper_extension == NULL) { + Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), doc, tempfilename_in, FALSE, FALSE, FALSE); + } else { + Inkscape::Extension::save(Inkscape::Extension::db.get(helper_extension), doc, tempfilename_in, FALSE, FALSE, FALSE); + } + + gsize bytesRead = 0; + gsize bytesWritten = 0; + GError *error = NULL; + gchar *local_filename = g_filename_from_utf8( filename, + -1, &bytesRead, &bytesWritten, &error); + + execute(command, tempfilename_in, local_filename); + + g_free(local_filename); + + // make sure we don't leak file descriptors from g_file_open_tmp + close(tempfd); + // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name + unlink(tempfilename_in); + g_free(tempfilename_in); +} + +/** + \return none + \brief This function uses an extention as a effect on a document. + \param module Extention 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 it's work done. Both of these + files have random names created for them using the g_file_open_temp function + with the sp_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) +{ + int data_read = 0; + SPDocument * mydoc = NULL; + gint tempfd_in; + gchar *tempfilename_in; + + // FIXME: process the GError instead of passing NULL + if ((tempfd_in = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_in, NULL)) == -1) { + /* Error, couldn't create temporary filename */ + if (errno == EINVAL) { + /* The last six characters of template were not XXXXXX. Now template is unchanged. */ + perror("Extension::Script: template for filenames is misconfigured.\n"); + exit(-1); + } else if (errno == EEXIST) { + /* Now the contents of template are undefined. */ + perror("Extension::Script: Could not create a unique temporary filename\n"); + return; + } else { + perror("Extension::Script: Unknown error creating temporary filename\n"); + exit(-1); + } + } + + gint tempfd_out; + gchar *tempfilename_out; + // FIXME: process the GError instead of passing NULL + if ((tempfd_out = g_file_open_tmp("ink_ext_XXXXXX", &tempfilename_out, NULL)) == -1) { + /* Error, couldn't create temporary filename */ + if (errno == EINVAL) { + /* The last six characters of template were not XXXXXX. Now template is unchanged. */ + perror("Extension::Script: template for filenames is misconfigured.\n"); + exit(-1); + } else if (errno == EEXIST) { + /* Now the contents of template are undefined. */ + perror("Extension::Script: Could not create a unique temporary filename\n"); + return; + } else { + perror("Extension::Script: Unknown error creating temporary filename\n"); + exit(-1); + } + } + + Inkscape::Extension::save(Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE), + doc->doc(), tempfilename_in, FALSE, FALSE, FALSE); + + Glib::ustring local_command(command); + + /* fixme: Should be some sort of checking here. Don't know how to do this with structs instead + * of classes. */ + SPDesktop *desktop = (SPDesktop *) doc; + if (desktop != NULL) { + using Inkscape::Util::GSListConstIterator; + GSListConstIterator<SPItem *> selected = SP_DT_SELECTION(desktop)->itemList(); + while ( selected != NULL ) { + local_command += " --id="; + local_command += SP_OBJECT_ID(*selected); + ++selected; + } + } + + Glib::ustring * paramString = module->paramString(); + local_command += *paramString; + delete paramString; + + // std::cout << local_command << std::endl; + + data_read = execute(local_command.c_str(), tempfilename_in, tempfilename_out); + + if (data_read > 10) + mydoc = Inkscape::Extension::open(Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG), tempfilename_out); + + // make sure we don't leak file descriptors from g_file_open_tmp + close(tempfd_in); + close(tempfd_out); + // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name + unlink(tempfilename_in); + g_free(tempfilename_in); + unlink(tempfilename_out); + g_free(tempfilename_out); + + /* Do something with mydoc.... */ + if (mydoc != NULL) { + doc->doc()->emitReconstructionStart(); + copy_doc(doc->doc()->rroot, mydoc->rroot); + doc->doc()->emitReconstructionFinish(); + mydoc->release(); + } +} + + +/** + \brief A function to take all the svg elements from one document + and put them in another. + \param oldroot The root node of the document to be replaced + \param newroot The root node of the document to replace it with + + This function first deletes all of the data in the old document. It + does this by creating a list of what needs to be deleted, and then + goes through the list. This two pass approach removes issues with + the list being change while parsing through it. Lots of nasty bugs. + + Then, it goes through the new document, duplicating all of the + elements and putting them into the old document. The copy + is then complete. +*/ +void +Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot) +{ + std::vector<Inkscape::XML::Node *> delete_list; + for (Inkscape::XML::Node * child = oldroot->firstChild(); + child != NULL; + child = child->next()) { + if (!strcmp("sodipodi:namedview", child->name())) + continue; + if (!strcmp("svg:defs", child->name())) + continue; + delete_list.push_back(child); + } + for (unsigned int i = 0; i < delete_list.size(); i++) + sp_repr_unparent(delete_list[i]); + + for (Inkscape::XML::Node * child = newroot->firstChild(); + child != NULL; + child = child->next()) { + if (!strcmp("sodipodi:namedview", child->name())) + continue; + if (!strcmp("svg:defs", child->name())) + continue; + oldroot->appendChild(child->duplicate()); + } + + /** \todo Restore correct layer */ + /** \todo Restore correct selection */ +} + +/* Helper class used by Script::execute */ +class pipe_t { +public: + /* These functions set errno if they return false. + I'm not sure whether that's a good idea or not, but it should be reasonably + straightforward to change it if needed. */ + bool open(char *command, char const *errorFile, int mode); + bool close(); + + /* These return the number of bytes read/written. */ + size_t read(void *buffer, size_t size); + size_t write(void const *buffer, size_t size); + + enum { + mode_read = 1 << 0, + mode_write = 1 << 1, + }; + +private: +#ifdef WIN32 + /* This is used to translate win32 errors into errno errors. + It only recognizes a few win32 errors for the moment though. */ + static int translate_error(DWORD err); + + HANDLE hpipe; +#else + FILE *ppipe; +#endif +}; + +/** + \return none + \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 is 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 fread and 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 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 gchar * in_command, const gchar * filein, const gchar * fileout) +{ + g_return_val_if_fail(in_command != NULL, 0); + // printf("Executing: %s\n", in_command); + + gchar * errorFile; + gint errorFileNum; + errorFileNum = g_file_open_tmp("ink_ext_stderr_XXXXXX", &errorFile, NULL); + if (errorFileNum != 0) { + close(errorFileNum); + } else { + g_free(errorFile); + errorFile = NULL; + } + + char *command = g_strdup_printf("%s \"%s\"", in_command, filein); + // std::cout << "Command to run: " << command << std::endl; + + pipe_t pipe; + bool open_success = pipe.open(command, errorFile, pipe_t::mode_read); + g_free(command); + + /* Run script */ + if (!open_success) { + /* Error - could not open pipe - check errno */ + if (errno == EINVAL) { + perror("Extension::Script: Invalid mode argument in popen\n"); + } else if (errno == ECHILD) { + perror("Extension::Script: Cannot obtain child extension status in popen\n"); + } else { + perror("Extension::Script: Unknown error for popen\n"); + } + return 0; + } + + Inkscape::IO::dump_fopen_call(fileout, "J"); + FILE *pfile = Inkscape::IO::fopen_utf8name(fileout, "w"); + + if (pfile == NULL) { + /* Error - could not open file */ + if (errno == EINVAL) { + perror("Extension::Script: The mode provided to fopen was invalid\n"); + } else { + perror("Extension::Script: Unknown error attempting to open temporary file\n"); + } + return 0; + } + + /* Copy pipe output to a temporary file */ + int amount_read = 0; + char buf[BUFSIZE]; + int num_read; + while ((num_read = pipe.read(buf, BUFSIZE)) != 0) { + amount_read += num_read; + fwrite(buf, 1, num_read, pfile); + } + + /* Close file */ + if (fclose(pfile) == EOF) { + if (errno == EBADF) { + perror("Extension::Script: The filedescriptor for the temporary file is invalid\n"); + return 0; + } else { + perror("Extension::Script: Unknown error closing temporary file\n"); + } + } + + /* Close pipe */ + if (!pipe.close()) { + if (errno == EINVAL) { + perror("Extension::Script: Invalid mode set for pclose\n"); + } else if (errno == ECHILD) { + perror("Extension::Script: Could not obtain child status for pclose\n"); + } else { + if (errorFile != NULL) { + checkStderr(errorFile, Gtk::MESSAGE_ERROR, + _("Inkscape has received an error from the script that it called. " + "The text returned with the error is included below. " + "Inkscape will continue working, but the action you requested has been cancelled.")); + } else { + perror("Extension::Script: Unknown error for pclose\n"); + } + } + /* Could be a lie, but if there is an error, we don't want + * to count on what was read being good */ + amount_read = 0; + } else { + if (errorFile != NULL) { + checkStderr(errorFile, 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.")); + } + } + + if (errorFile != NULL) { + unlink(errorFile); + g_free(errorFile); + } + + return amount_read; +} + +/** \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 (gchar * filename, Gtk::MessageType type, gchar * message) +{ + // magic win32 crlf->lf conversion means the file length is not the same as + // the text length, but luckily gtk will accept crlf in textviews so we can + // just use binary mode + std::ifstream stderrf (filename, std::ios_base::in | std::ios_base::binary); + if (!stderrf.is_open()) return; + + stderrf.seekg(0, std::ios::end); + int length = stderrf.tellg(); + if (0 == length) return; + stderrf.seekg(0, std::ios::beg); + + Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true); + warning.set_resizable(true); + + Gtk::VBox * vbox = warning.get_vbox(); + + /* 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(); + + char * buffer = new char [length]; + stderrf.read(buffer, length); + textview->get_buffer()->set_text(buffer, buffer + length); + delete buffer; + stderrf.close(); + + 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(); + + vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */); + + warning.run(); + + return; +} + +#ifdef WIN32 + +bool pipe_t::open(char *command, char const *errorFile, int mode_p) { + HANDLE pipe_write; + + // Create pipe + { + SECURITY_ATTRIBUTES secattrs; + ZeroMemory(&secattrs, sizeof(secattrs)); + secattrs.nLength = sizeof(secattrs); + secattrs.lpSecurityDescriptor = 0; + secattrs.bInheritHandle = TRUE; + HANDLE t_pipe_read = 0; + if ( !CreatePipe(&t_pipe_read, &pipe_write, &secattrs, 0) ) { + errno = translate_error(GetLastError()); + return false; + } + // This duplicate handle makes the read pipe uninheritable + if ( !DuplicateHandle(GetCurrentProcess(), t_pipe_read, GetCurrentProcess(), &hpipe, 0, FALSE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS) ) { + int en = translate_error(GetLastError()); + CloseHandle(t_pipe_read); + CloseHandle(pipe_write); + errno = en; + return false; + } + } + // Open stderr file + HANDLE hStdErrFile = CreateFile(errorFile, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, 0, NULL); + HANDLE hInheritableStdErr; + DuplicateHandle(GetCurrentProcess(), hStdErrFile, GetCurrentProcess(), &hInheritableStdErr, 0, TRUE, DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS); + + // Create process + { + PROCESS_INFORMATION procinfo; + STARTUPINFO startupinfo; + ZeroMemory(&procinfo, sizeof(procinfo)); + ZeroMemory(&startupinfo, sizeof(startupinfo)); + startupinfo.cb = sizeof(startupinfo); + //startupinfo.lpReserved = 0; + //startupinfo.lpDesktop = 0; + //startupinfo.lpTitle = 0; + startupinfo.dwFlags = STARTF_USESTDHANDLES; + startupinfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + startupinfo.hStdOutput = pipe_write; + startupinfo.hStdError = hInheritableStdErr; + + if ( !CreateProcess(NULL, command, NULL, NULL, TRUE, 0, NULL, NULL, &startupinfo, &procinfo) ) { + errno = translate_error(GetLastError()); + return false; + } + CloseHandle(procinfo.hThread); + CloseHandle(procinfo.hProcess); + } + + // Close our copy of the write handle + CloseHandle(hInheritableStdErr); + CloseHandle(pipe_write); + + return true; +} + +bool pipe_t::close() { + BOOL retval = CloseHandle(hpipe); + if ( !retval ) { + errno = translate_error(GetLastError()); + } + return retval != FALSE; +} + +size_t pipe_t::read(void *buffer, size_t size) { + DWORD bytes_read = 0; + ReadFile(hpipe, buffer, size, &bytes_read, 0); + return bytes_read; +} + +size_t pipe_t::write(void const *buffer, size_t size) { + DWORD bytes_written = 0; + WriteFile(hpipe, buffer, size, &bytes_written, 0); + return bytes_written; +} + +int pipe_t::translate_error(DWORD err) { + switch (err) { + case ERROR_FILE_NOT_FOUND: + return ENOENT; + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_PARAMETER: + return EINVAL; + default: + return 0; + } +} + +#else // Win32 + +bool pipe_t::open(char *command, char const *errorFile, int mode_p) { + char popen_mode[4] = {0,0,0,0}; + char *popen_mode_cur = popen_mode; + + if ( (mode_p & mode_read) != 0 ) { + *popen_mode_cur++ = 'r'; + } + + if ( (mode_p & mode_write) != 0 ) { + *popen_mode_cur++ = 'w'; + } + + /* Get the commandline to be run */ + if (errorFile != NULL) { + char * temp; + temp = g_strdup_printf("%s 2> %s", command, errorFile); + ppipe = popen(temp, popen_mode); + g_free(temp); + } else + ppipe = popen(command, popen_mode); + + return ppipe != NULL; +} + +bool pipe_t::close() { + return fclose(ppipe) == 0; +} + +size_t pipe_t::read(void *buffer, size_t size) { + return fread(buffer, 1, size, ppipe); +} + +size_t pipe_t::write(void const *buffer, size_t size) { + return fwrite(buffer, 1, size, ppipe); +} + +#endif // (Non-)Win32 + + +} /* 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:encoding=utf-8:textwidth=99 : diff --git a/src/extension/implementation/script.h b/src/extension/implementation/script.h new file mode 100644 index 000000000..e13ffbb13 --- /dev/null +++ b/src/extension/implementation/script.h @@ -0,0 +1,86 @@ +/* + * 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, read the file 'COPYING' for more information + */ + +#ifndef __INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H__ +#define __INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H__ + +#include "implementation.h" +#include <gtkmm/messagedialog.h> + +namespace Inkscape { +namespace XML { +class Node; +} +} + + +namespace Inkscape { +namespace Extension { +namespace Implementation { + +class Script : public Implementation { +private: + gchar * command; /**< The command that has been dirived from + the configuration file with appropriate + directories */ + gchar * helper_extension; + /**< This is the extension that will be used + as the helper to read in or write out the + data */ + /** This function actually does the work, everything else is preparing + for this function. It is the core here */ + int execute (gchar const *command, + gchar const *filein, + gchar const *fileout); + /** Just a quick function to find and resolve relative paths for + the incoming scripts */ + gchar * solve_reldir (Inkscape::XML::Node *reprin); + bool check_existance (gchar const *command); + void copy_doc (Inkscape::XML::Node * olddoc, Inkscape::XML::Node * newdoc); + void checkStderr (gchar * filename, Gtk::MessageType type, gchar * message); + +public: + Script (void); + virtual bool load (Inkscape::Extension::Extension *module); + virtual void unload (Inkscape::Extension::Extension *module); + virtual bool check (Inkscape::Extension::Extension *module); + virtual Gtk::Widget * prefs_input (Inkscape::Extension::Input *module, + gchar const *filename); + virtual SPDocument * open (Inkscape::Extension::Input *module, + gchar const *filename); + virtual Gtk::Widget * prefs_output (Inkscape::Extension::Output *module); + virtual void save (Inkscape::Extension::Output *module, + SPDocument *doc, + gchar const *filename); + virtual Gtk::Widget * + prefs_effect (Inkscape::Extension::Effect *module, + Inkscape::UI::View::View * view); + virtual void effect (Inkscape::Extension::Effect *module, + Inkscape::UI::View::View *doc); + +}; + +} /* Inkscape */ +} /* Extension */ +} /* Implementation */ +#endif /* __INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_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 : |
