diff options
| author | Martin Owens <doctormo@gmail.com> | 2014-03-27 01:33:44 +0000 |
|---|---|---|
| committer | Martin Owens <doctormo@gmail.com> | 2014-03-27 01:33:44 +0000 |
| commit | 5a4fb2325f60d292b47330f540b26a3279341c90 (patch) | |
| tree | d2aa7967be25450b83e625025366c618101ae49f /src/extension/internal | |
| parent | The Polar Arrange Tab of the Arrange Dialog now hides the parametric (diff) | |
| parent | Remove Snap menu item and improve grid menu item text (diff) | |
| download | inkscape-5a4fb2325f60d292b47330f540b26a3279341c90.tar.gz inkscape-5a4fb2325f60d292b47330f540b26a3279341c90.zip | |
Commit a merge to trunk, with probabal errors
(bzr r11073.1.36)
Diffstat (limited to 'src/extension/internal')
70 files changed, 18444 insertions, 5463 deletions
diff --git a/src/extension/internal/Makefile_insert b/src/extension/internal/Makefile_insert index 06fa275cb..a31843114 100644 --- a/src/extension/internal/Makefile_insert +++ b/src/extension/internal/Makefile_insert @@ -4,7 +4,18 @@ if WITH_LIBWPG ink_common_sources += \ extension/internal/wpg-input.cpp \ extension/internal/wpg-input.h +endif + +if WITH_LIBVISIO +ink_common_sources += \ + extension/internal/vsd-input.cpp \ + extension/internal/vsd-input.h +endif +if WITH_LIBCDR +ink_common_sources += \ + extension/internal/cdr-input.cpp \ + extension/internal/cdr-input.h endif if USE_IMAGE_MAGICK @@ -127,13 +138,38 @@ ink_common_sources += \ extension/internal/latex-pstricks.h \ extension/internal/latex-pstricks-out.cpp \ extension/internal/latex-pstricks-out.h \ + extension/internal/filter/bevels.h \ + extension/internal/filter/blurs.h \ + extension/internal/filter/bumps.h \ + extension/internal/filter/color.h \ + extension/internal/filter/distort.h \ + extension/internal/filter/filter.h \ + extension/internal/filter/image.h \ + extension/internal/filter/morphology.h \ + extension/internal/filter/overlays.h \ + extension/internal/filter/paint.h \ + extension/internal/filter/protrusions.h \ + extension/internal/filter/shadows.h \ + extension/internal/filter/textures.h \ + extension/internal/filter/transparency.h \ extension/internal/filter/filter-all.cpp \ extension/internal/filter/filter-file.cpp \ extension/internal/filter/filter.cpp \ extension/internal/filter/filter.h \ - extension/internal/filter/drop-shadow.h \ - extension/internal/filter/snow.h \ - extension/internal/emf-win32-print.h \ - extension/internal/emf-win32-print.cpp \ - extension/internal/emf-win32-inout.h \ - extension/internal/emf-win32-inout.cpp + extension/internal/text_reassemble.c \ + extension/internal/text_reassemble.h \ + extension/internal/emf-print.h \ + extension/internal/emf-print.cpp \ + extension/internal/emf-inout.h \ + extension/internal/emf-inout.cpp \ + extension/internal/metafile-inout.h \ + extension/internal/metafile-inout.cpp \ + extension/internal/metafile-print.h \ + extension/internal/metafile-print.cpp \ + extension/internal/wmf-print.h \ + extension/internal/wmf-print.cpp \ + extension/internal/wmf-inout.h \ + extension/internal/wmf-inout.cpp \ + extension/internal/image-resolution.h \ + extension/internal/image-resolution.cpp + diff --git a/src/extension/internal/bitmap/despeckle.cpp b/src/extension/internal/bitmap/despeckle.cpp index f818ef81a..c40e6e227 100644 --- a/src/extension/internal/bitmap/despeckle.cpp +++ b/src/extension/internal/bitmap/despeckle.cpp @@ -23,7 +23,7 @@ Despeckle::applyEffect(Magick::Image *image) { } void -Despeckle::refreshParameters(Inkscape::Extension::Effect *module) { +Despeckle::refreshParameters(Inkscape::Extension::Effect */*module*/) { } #include "../clear-n_.h" diff --git a/src/extension/internal/bitmap/enhance.cpp b/src/extension/internal/bitmap/enhance.cpp index 0bd362e8c..5b06a2d27 100644 --- a/src/extension/internal/bitmap/enhance.cpp +++ b/src/extension/internal/bitmap/enhance.cpp @@ -23,7 +23,7 @@ Enhance::applyEffect(Magick::Image *image) { } void -Enhance::refreshParameters(Inkscape::Extension::Effect *module) { } +Enhance::refreshParameters(Inkscape::Extension::Effect */*module*/) { } #include "../clear-n_.h" diff --git a/src/extension/internal/bitmap/equalize.cpp b/src/extension/internal/bitmap/equalize.cpp index b0726b61a..6098f74b1 100644 --- a/src/extension/internal/bitmap/equalize.cpp +++ b/src/extension/internal/bitmap/equalize.cpp @@ -23,7 +23,7 @@ Equalize::applyEffect(Magick::Image *image) { } void -Equalize::refreshParameters(Inkscape::Extension::Effect *module) { } +Equalize::refreshParameters(Inkscape::Extension::Effect */*module*/) { } #include "../clear-n_.h" diff --git a/src/extension/internal/bitmap/imagemagick.cpp b/src/extension/internal/bitmap/imagemagick.cpp index 728c38dea..0d47240d4 100644 --- a/src/extension/internal/bitmap/imagemagick.cpp +++ b/src/extension/internal/bitmap/imagemagick.cpp @@ -8,6 +8,14 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include <glibmm/threads.h> +#endif + #include <libintl.h> #include <gtkmm/box.h> diff --git a/src/extension/internal/bitmap/negate.cpp b/src/extension/internal/bitmap/negate.cpp index 2e564821e..4737e6443 100644 --- a/src/extension/internal/bitmap/negate.cpp +++ b/src/extension/internal/bitmap/negate.cpp @@ -23,7 +23,7 @@ Negate::applyEffect(Magick::Image* image) { } void -Negate::refreshParameters(Inkscape::Extension::Effect* module) { +Negate::refreshParameters(Inkscape::Extension::Effect* /*module*/) { } #include "../clear-n_.h" diff --git a/src/extension/internal/bitmap/normalize.cpp b/src/extension/internal/bitmap/normalize.cpp index 591fe1fdd..1444aa654 100644 --- a/src/extension/internal/bitmap/normalize.cpp +++ b/src/extension/internal/bitmap/normalize.cpp @@ -23,7 +23,7 @@ Normalize::applyEffect(Magick::Image* image) { } void -Normalize::refreshParameters(Inkscape::Extension::Effect* module) { +Normalize::refreshParameters(Inkscape::Extension::Effect* /*module*/) { } #include "../clear-n_.h" diff --git a/src/extension/internal/bluredge.cpp b/src/extension/internal/bluredge.cpp index 8f1e07211..3ce537d9f 100644 --- a/src/extension/internal/bluredge.cpp +++ b/src/extension/internal/bluredge.cpp @@ -17,6 +17,7 @@ #include "document.h" #include "selection.h" #include "helper/action.h" +#include "helper/action-context.h" #include "preferences.h" #include "path-chemistry.h" #include "sp-item.h" @@ -93,16 +94,16 @@ BlurEdge::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View new_group->appendChild(new_items[i]); selection->add(new_items[i]); - sp_selected_path_to_curves(static_cast<SPDesktop *>(desktop)); + sp_selected_path_to_curves(selection, static_cast<SPDesktop *>(desktop)); 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(desktop), NULL); + sp_action_perform(Inkscape::Verb::get(SP_VERB_SELECTION_INSET)->get_action(Inkscape::ActionContext(desktop)), NULL); } else if (offset > 0.0) { prefs->setDoubleUnit("/options/defaultoffsetwidth/value", offset, "px"); - sp_action_perform(Inkscape::Verb::get(SP_VERB_SELECTION_OFFSET)->get_action(desktop), NULL); + sp_action_perform(Inkscape::Verb::get(SP_VERB_SELECTION_OFFSET)->get_action(Inkscape::ActionContext(desktop)), NULL); } selection->clear(); diff --git a/src/extension/internal/cairo-png-out.cpp b/src/extension/internal/cairo-png-out.cpp index 4751a229a..956fcce9a 100644 --- a/src/extension/internal/cairo-png-out.cpp +++ b/src/extension/internal/cairo-png-out.cpp @@ -68,7 +68,7 @@ png_render_document_to_file(SPDocument *doc, gchar const *filename) ctx = renderer->createContext(); /* Render document */ - bool ret = renderer->setupDocument(ctx, doc, TRUE, NULL); + bool ret = renderer->setupDocument(ctx, doc, TRUE, 0., NULL); if (ret) { renderer->renderItem(ctx, base); ctx->saveAsPng(filename); diff --git a/src/extension/internal/cairo-ps-out.cpp b/src/extension/internal/cairo-ps-out.cpp index 8b1b3fbfd..055a30add 100644 --- a/src/extension/internal/cairo-ps-out.cpp +++ b/src/extension/internal/cairo-ps-out.cpp @@ -66,7 +66,7 @@ bool CairoEpsOutput::check (Inkscape::Extension::Extension * /*module*/) 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, bool eps = false) + bool filtertobitmap, int resolution, const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px, bool eps = false) { doc->ensureUpToDate(); @@ -97,14 +97,14 @@ ps_print_document_to_file(SPDocument *doc, gchar const *filename, unsigned int l ctx->setPSLevel(level); ctx->setEPS(eps); ctx->setTextToPath(texttopath); - renderer->_omitText = omittext; + ctx->setOmitText(omittext); ctx->setFilterToBitmap(filtertobitmap); ctx->setBitmapResolution(resolution); bool ret = ctx->setPsTarget(filename); if(ret) { /* Render document */ - ret = renderer->setupDocument(ctx, doc, pageBoundingBox, base); + ret = renderer->setupDocument(ctx, doc, pageBoundingBox, bleedmargin_px, base); if (ret) { renderer->renderItem(ctx, base); ret = ctx->finish(); @@ -136,12 +136,12 @@ CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar con if (ext == NULL) return; - const gchar *new_level = NULL; int level = CAIRO_PS_LEVEL_2; try { - new_level = mod->get_param_enum("PSlevel"); - if((new_level != NULL) && (g_ascii_strcasecmp("PS3", new_level) == 0)) + const gchar *new_level = mod->get_param_enum("PSlevel"); + if((new_level != NULL) && (g_ascii_strcasecmp("PS3", new_level) == 0)) { level = CAIRO_PS_LEVEL_3; + } } catch(...) {} bool new_textToPath = FALSE; @@ -169,12 +169,14 @@ CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar con bool new_areaPage = true; try { - new_areaPage = mod->get_param_bool("areaPage"); + new_areaPage = (strcmp(ext->get_param_optiongroup("area"), "page") == 0); } catch(...) {} - bool new_areaDrawing = true; + bool new_areaDrawing = !new_areaPage; + + float bleedmargin_px = 0.; try { - new_areaDrawing = mod->get_param_bool("areaDrawing"); + bleedmargin_px = ext->get_param_float("bleed"); } catch(...) {} const gchar *new_exportId = NULL; @@ -186,7 +188,11 @@ CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar con { 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); + 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) @@ -195,7 +201,7 @@ CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar con // Create LaTeX file (if requested) if (new_textToLaTeX) { - ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_areaDrawing, new_areaPage, false); + 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(); @@ -219,12 +225,12 @@ CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar co if (ext == NULL) return; - const gchar *new_level = NULL; int level = CAIRO_PS_LEVEL_2; try { - new_level = mod->get_param_enum("PSlevel"); - if((new_level != NULL) && (g_ascii_strcasecmp("PS3", new_level) == 0)) + const gchar *new_level = mod->get_param_enum("PSlevel"); + if((new_level != NULL) && (g_ascii_strcasecmp("PS3", new_level) == 0)) { level = CAIRO_PS_LEVEL_3; + } } catch(...) {} bool new_textToPath = FALSE; @@ -252,12 +258,14 @@ CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar co bool new_areaPage = true; try { - new_areaPage = mod->get_param_bool("areaPage"); + new_areaPage = (strcmp(ext->get_param_optiongroup("area"), "page") == 0); } catch(...) {} - bool new_areaDrawing = true; + bool new_areaDrawing = !new_areaPage; + + float bleedmargin_px = 0.; try { - new_areaDrawing = mod->get_param_bool("areaDrawing"); + bleedmargin_px = ext->get_param_float("bleed"); } catch(...) {} const gchar *new_exportId = NULL; @@ -269,7 +277,11 @@ CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar co { 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, true); + 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) @@ -278,7 +290,7 @@ CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar co // Create LaTeX file (if requested) if (new_textToLaTeX) { - ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_areaDrawing, new_areaPage, false); + 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(); @@ -310,32 +322,33 @@ CairoEpsOutput::textToPath(Inkscape::Extension::Print * ext) void CairoPsOutput::init (void) { - 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=\"enum\" >\n" - "<_item value='PS3'>" N_("PostScript level 3") "</_item>\n" -#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) + 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=\"enum\" >\n" + "<_item value='PS3'>" N_("PostScript level 3") "</_item>\n" "<_item value='PS2'>" N_("PostScript level 2") "</_item>\n" -#endif "</param>\n" - "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" - "<param name=\"textToLaTeX\" gui-text=\"" N_("PS+LaTeX: Omit text in PS, and create LaTeX file") "\" type=\"boolean\">false</param>\n" - "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" - "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" - "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is drawing") "\" type=\"boolean\">true</param>\n" - "<param name=\"areaPage\" gui-text=\"" N_("Export area is page") "\" type=\"boolean\">true</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; + "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" + "<param name=\"textToLaTeX\" gui-text=\"" N_("PS+LaTeX: Omit text in PS, and create LaTeX file") "\" type=\"boolean\">false</param>\n" + "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" + "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" + "<param name=\"area\" gui-text=\"" N_("Output page size") "\" type=\"optiongroup\" >\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; } /** @@ -348,32 +361,33 @@ CairoPsOutput::init (void) void CairoEpsOutput::init (void) { - 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=\"enum\" >\n" - "<_item value='PS3'>" N_("PostScript level 3") "</_item>\n" -#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) + 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=\"enum\" >\n" + "<_item value='PS3'>" N_("PostScript level 3") "</_item>\n" "<_item value='PS2'>" N_("PostScript level 2") "</_item>\n" -#endif "</param>\n" - "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" - "<param name=\"textToLaTeX\" gui-text=\"" N_("EPS+LaTeX: Omit text in EPS, and create LaTeX file") "\" type=\"boolean\">false</param>\n" - "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" - "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" - "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is drawing") "\" type=\"boolean\">true</param>\n" - "<param name=\"areaPage\" gui-text=\"" N_("Export area is page") "\" type=\"boolean\">true</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; + "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" + "<param name=\"textToLaTeX\" gui-text=\"" N_("EPS+LaTeX: Omit text in EPS, and create LaTeX file") "\" type=\"boolean\">false</param>\n" + "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" + "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" + "<param name=\"area\" gui-text=\"" N_("Output page size") "\" type=\"optiongroup\" >\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 */ diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp index b6a58c526..c09b8e9c8 100644 --- a/src/extension/internal/cairo-render-context.cpp +++ b/src/extension/internal/cairo-render-context.cpp @@ -44,12 +44,11 @@ #include "sp-pattern.h" #include "sp-mask.h" #include "sp-clippath.h" +#include "util/units.h" #ifdef WIN32 #include "libnrtype/FontFactory.h" // USE_PANGO_WIN32 #endif -#include <unit-constants.h> - #include "cairo-render-context.h" #include "cairo-renderer.h" #include "extension/system.h" @@ -73,8 +72,8 @@ #include <cairo-ft.h> #endif #ifdef CAIRO_HAS_WIN32_FONT -#include <cairo-win32.h> #include <pango/pangowin32.h> +#include <cairo-win32.h> #endif #include <pango/pangofc-fontmap.h> @@ -112,6 +111,7 @@ CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : _ps_level(1), _eps(false), _is_texttopath(FALSE), + _is_omittext(FALSE), _is_filtertobitmap(FALSE), _bitmapresolution(72), _stream(NULL), @@ -125,7 +125,8 @@ CairoRenderContext::CairoRenderContext(CairoRenderer *parent) : _state(NULL), _renderer(parent), _render_mode(RENDER_MODE_NORMAL), - _clip_mode(CLIP_MODE_MASK) + _clip_mode(CLIP_MODE_MASK), + _omittext_state(EMPTY) { font_table = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, font_data_free); } @@ -427,6 +428,16 @@ void CairoRenderContext::setTextToPath(bool texttopath) _is_texttopath = texttopath; } +void CairoRenderContext::setOmitText(bool omittext) +{ + _is_omittext = omittext; +} + +bool CairoRenderContext::getOmitText(void) +{ + return _is_omittext; +} + void CairoRenderContext::setFilterToBitmap(bool filtertobitmap) { _is_filtertobitmap = filtertobitmap; @@ -708,7 +719,7 @@ CairoRenderContext::popLayer(void) for (int row = 0 ; row < height; row++) { unsigned char *row_data = pixels + (row * stride); for (int i = 0 ; i < width; i++) { - guint32 *pixel = (guint32 *)row_data + 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 ); @@ -792,9 +803,7 @@ CairoRenderContext::setupSurface(double width, double height) #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); -#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0)) cairo_pdf_surface_restrict_to_version(surface, (cairo_pdf_version_t)_pdf_level); -#endif break; #endif #ifdef CAIRO_HAS_PS_SURFACE @@ -803,10 +812,8 @@ CairoRenderContext::setupSurface(double width, double height) if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) { return FALSE; } -#if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 5, 2)) cairo_ps_surface_restrict_to_level(surface, (cairo_ps_level_t)_ps_level); cairo_ps_surface_set_eps(surface, (cairo_bool_t) _eps); -#endif // Cairo calculates the bounding box itself, however we want to override this. See Launchpad bug #380501 #if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 11, 2)) // cairo_ps_dsc_comment(surface, os_bbox.str().c_str()); @@ -855,7 +862,7 @@ CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t _surface = surface; if (_vector_based_target) { - cairo_scale(_cr, PT_PER_PX, PT_PER_PX); + 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 @@ -1080,11 +1087,11 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver // show items and render them for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { - if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children + if (pat_i && SP_IS_OBJECT(pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children for ( SPObject *child = pat_i->firstChild() ; child; child = child->getNext() ) { - if (SP_IS_ITEM (child)) { - SP_ITEM (child)->invoke_show (drawing, dkey, SP_ITEM_REFERENCE_FLAGS); - _renderer->renderItem(pattern_ctx, SP_ITEM (child)); + 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 @@ -1109,10 +1116,10 @@ CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver // hide all items for (SPPattern *pat_i = pat; pat_i != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { - if (pat_i && SP_IS_OBJECT (pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children + if (pat_i && SP_IS_OBJECT(pat_i) && pattern_hasItemChildren(pat_i)) { // find the first one with item children for ( SPObject *child = pat_i->firstChild() ; child; child = child->getNext() ) { - if (SP_IS_ITEM (child)) { - SP_ITEM (child)->invoke_hide (dkey); + if (SP_IS_ITEM(child)) { + SP_ITEM(child)->invoke_hide(dkey); } } break; // do not go further up the chain if children are found @@ -1181,7 +1188,7 @@ CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const pain return NULL; } - if (pattern && SP_IS_GRADIENT (paintserver)) { + if (pattern && SP_IS_GRADIENT(paintserver)) { SPGradient *g = SP_GRADIENT(paintserver); // set extend type @@ -1290,12 +1297,16 @@ CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect const &p } } - if (style->stroke_dash.n_dash && - style->stroke_dash.dash ) + if (!style->stroke_dasharray.values.empty()) { - cairo_set_dash(_cr, style->stroke_dash.dash, style->stroke_dash.n_dash, style->stroke_dash.offset); + 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]; + } + cairo_set_dash(_cr, dashes, ndashes, style->stroke_dashoffset.value); } else { - cairo_set_dash(_cr, NULL, 0, 0.0); // disable dashing + cairo_set_dash(_cr, NULL, 0, 0.0); // disable dashing } cairo_set_line_width(_cr, style->stroke_width.computed); @@ -1303,40 +1314,65 @@ CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect const &p // 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; + 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; + 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) { + if (_omittext_state == NEW_PAGE_ON_GRAPHIC) + cairo_show_page(_cr); + _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; + } +} + bool CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle const *style, Geom::OptRect const &pbox) { g_assert( _is_valid ); + _prepareRenderGraphic(); + if (_render_mode == RENDER_MODE_CLIP) { if (_clip_mode == CLIP_MODE_PATH) { addClipPath(pathv, &style->fill_rule); @@ -1400,8 +1436,8 @@ CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle con return true; } -bool CairoRenderContext::renderImage(GdkPixbuf *pb, - Geom::Affine const &image_transform, SPStyle const * /*style*/) +bool CairoRenderContext::renderImage(Inkscape::Pixbuf *pb, + Geom::Affine const &image_transform, SPStyle const *style) { g_assert( _is_valid ); @@ -1409,16 +1445,18 @@ bool CairoRenderContext::renderImage(GdkPixbuf *pb, return true; } - int w = gdk_pixbuf_get_width (pb); - int h = gdk_pixbuf_get_height (pb); + _prepareRenderGraphic(); + + int w = pb->width(); + int h = pb->height(); // TODO: reenable merge_opacity if useful float opacity = _state->opacity; - cairo_surface_t *image_surface = ink_cairo_surface_create_for_argb32_pixbuf(pb); + 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; + return false; } cairo_save(_cr); @@ -1434,10 +1472,30 @@ bool CairoRenderContext::renderImage(GdkPixbuf *pb, 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 + // http://www.w3.org/TR/css4-images/#the-image-rendering + // style.h/style.cpp + switch (style->image_rendering.computed) { + case SP_CSS_COLOR_RENDERING_AUTO: + // Do nothing + break; + case SP_CSS_COLOR_RENDERING_OPTIMIZEQUALITY: + cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_BEST ); + break; + case SP_CSS_COLOR_RENDERING_OPTIMIZESPEED: + default: + cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_NEAREST ); + break; + } + } + cairo_paint_with_alpha(_cr, opacity); cairo_restore(_cr); - cairo_surface_destroy(image_surface); return true; } @@ -1490,9 +1548,14 @@ unsigned int CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont * /*font*/, 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; + double size = style->font_size.computed; /// \fixme why is this variable never used? gpointer fonthash = (gpointer)font; cairo_font_face_t *font_face = (cairo_font_face_t *)g_hash_table_lookup(font_table, fonthash); @@ -1633,9 +1696,9 @@ _write_callback(void *closure, const unsigned char *data, unsigned int length) written = fwrite (data, 1, length, file); if (written == length) - return CAIRO_STATUS_SUCCESS; + return CAIRO_STATUS_SUCCESS; else - return CAIRO_STATUS_WRITE_ERROR; + return CAIRO_STATUS_WRITE_ERROR; } #include "clear-n_.h" diff --git a/src/extension/internal/cairo-render-context.h b/src/extension/internal/cairo-render-context.h index 94c7bb294..8d3e63775 100644 --- a/src/extension/internal/cairo-render-context.h +++ b/src/extension/internal/cairo-render-context.h @@ -6,7 +6,7 @@ */ /* * Authors: - * Miklos Erdelyi <erdelyim@gmail.com> + * Miklos Erdelyi <erdelyim@gmail.com> * * Copyright (C) 2006 Miklos Erdelyi * @@ -32,13 +32,15 @@ class SPClipPath; class SPMask; namespace Inkscape { +class Pixbuf; + namespace Extension { namespace Internal { class CairoRenderer; class CairoRenderContext; -class CairoRenderState; -class CairoGlyphInfo; +struct CairoRenderState; +struct CairoGlyphInfo; // Holds info for rendering a glyph struct CairoGlyphInfo { @@ -94,6 +96,8 @@ public: void setPDFLevel(unsigned int level); void setTextToPath(bool texttopath); bool getTextToPath(void); + void setOmitText(bool omittext); + bool getOmitText(void); void setFilterToBitmap(bool filtertobitmap); bool getFilterToBitmap(void); void setBitmapResolution(int resolution); @@ -109,6 +113,9 @@ public: /** 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(void); + /* Render/clip mode setting/query */ void setRenderMode(CairoRenderMode mode); CairoRenderMode getRenderMode(void) const; @@ -139,7 +146,7 @@ public: /* Rendering methods */ bool renderPathVector(Geom::PathVector const &pathv, SPStyle const *style, Geom::OptRect const &pbox); - bool renderImage(GdkPixbuf *pb, + 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); @@ -150,6 +157,12 @@ protected: CairoRenderContext(CairoRenderer *renderer); virtual ~CairoRenderContext(void); + enum CairoOmitTextPageState { + EMPTY, + GRAPHIC_ON_TOP, + NEW_PAGE_ON_GRAPHIC + }; + float _width; float _height; unsigned short _dpi; @@ -157,6 +170,7 @@ protected: unsigned int _ps_level; bool _eps; bool _is_texttopath; + bool _is_omittext; bool _is_filtertobitmap; int _bitmapresolution; @@ -182,6 +196,8 @@ protected: CairoRenderMode _render_mode; CairoClipMode _clip_mode; + CairoOmitTextPageState _omittext_state; + 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); @@ -196,6 +212,9 @@ protected: 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); + void _prepareRenderText(void); + GHashTable *font_table; static void font_data_free(gpointer data); diff --git a/src/extension/internal/cairo-renderer-pdf-out.cpp b/src/extension/internal/cairo-renderer-pdf-out.cpp index 5d45fcf87..0c314a576 100644 --- a/src/extension/internal/cairo-renderer-pdf-out.cpp +++ b/src/extension/internal/cairo-renderer-pdf-out.cpp @@ -39,6 +39,8 @@ #include <2geom/affine.h> #include "document.h" +#include "util/units.h" + namespace Inkscape { namespace Extension { namespace Internal { @@ -57,7 +59,7 @@ bool CairoRendererPdfOutput::check(Inkscape::Extension::Extension * /*module*/) 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) + const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px) { doc->ensureUpToDate(); @@ -92,14 +94,14 @@ pdf_render_document_to_file(SPDocument *doc, gchar const *filename, unsigned int CairoRenderContext *ctx = renderer->createContext(); ctx->setPDFLevel(level); ctx->setTextToPath(texttopath); - renderer->_omitText = omittext; + ctx->setOmitText(omittext); ctx->setFilterToBitmap(filtertobitmap); ctx->setBitmapResolution(resolution); bool ret = ctx->setPdfTarget (filename); if(ret) { /* Render document */ - ret = renderer->setupDocument(ctx, doc, pageBoundingBox, base); + ret = renderer->setupDocument(ctx, doc, pageBoundingBox, bleedmargin_px, base); if (ret) { renderer->renderItem(ctx, base); ret = ctx->finish(); @@ -134,12 +136,12 @@ CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, if (ext == NULL) return; - const gchar *new_level = NULL; int level = 0; try { - new_level = mod->get_param_enum("PDFversion"); - if((new_level != NULL) && (g_ascii_strcasecmp("PDF-1.5", new_level) == 0)) + const gchar *new_level = mod->get_param_enum("PDFversion"); + if((new_level != NULL) && (g_ascii_strcasecmp("PDF-1.5", new_level) == 0)) { level = 1; + } } catch(...) { g_warning("Parameter <PDFversion> might not exist"); @@ -185,20 +187,20 @@ CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, g_warning("Parameter <exportId> might not exist"); } - bool new_exportDrawing = FALSE; + bool new_exportCanvas = true; try { - new_exportDrawing = mod->get_param_bool("areaDrawing"); - } - catch(...) { - g_warning("Parameter <areaDrawing> might not exist"); + new_exportCanvas = (strcmp(ext->get_param_optiongroup("area"), "page") == 0); + } catch(...) { + g_warning("Parameter <area> might not exist"); } + bool new_exportDrawing = !new_exportCanvas; - bool new_exportCanvas = FALSE; + float new_bleedmargin_px = 0.; try { - new_exportCanvas = mod->get_param_bool("areaPage"); + new_bleedmargin_px = Inkscape::Util::Quantity::convert(mod->get_param_float("bleed"), "mm", "px"); } catch(...) { - g_warning("Parameter <exportCanvas> might not exist"); + g_warning("Parameter <bleed> might not exist"); } // Create PDF file @@ -207,7 +209,7 @@ CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, 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_exportId, new_exportDrawing, new_exportCanvas, new_bleedmargin_px); g_free(final_name); if (!ret) @@ -216,7 +218,7 @@ CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, // Create LaTeX file (if requested) if (new_textToLaTeX) { - ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_exportDrawing, new_exportCanvas, true); + 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(); @@ -235,32 +237,35 @@ CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, void CairoRendererPdfOutput::init (void) { - 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=\"enum\" >\n" + 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=\"enum\" >\n" #if (CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 10, 0)) - "<_item value='PDF-1.5'>" N_("PDF 1.5") "</_item>\n" + "<_item value='PDF-1.5'>" N_("PDF 1.5") "</_item>\n" #endif "<_item value='PDF-1.4'>" N_("PDF 1.4") "</_item>\n" - "</param>\n" - "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" - "<param name=\"textToLaTeX\" gui-text=\"" N_("PDF+LaTeX: Omit text in PDF, and create LaTeX file") "\" type=\"boolean\">false</param>\n" - "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" - "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" - "<param name=\"areaDrawing\" gui-text=\"" N_("Export area is drawing") "\" type=\"boolean\">false</param>\n" - "<param name=\"areaPage\" gui-text=\"" N_("Export area is page") "\" type=\"boolean\">false</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; + "</param>\n" + "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">false</param>\n" + "<param name=\"textToLaTeX\" gui-text=\"" N_("PDF+LaTeX: Omit text in PDF, and create LaTeX file") "\" type=\"boolean\">false</param>\n" + "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"boolean\">true</param>\n" + "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">90</param>\n" + "<param name=\"area\" gui-text=\"" N_("Output page size:") "\" type=\"optiongroup\" >\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 */ diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp index 09d69becb..6fbc85c05 100644 --- a/src/extension/internal/cairo-renderer.cpp +++ b/src/extension/internal/cairo-renderer.cpp @@ -27,6 +27,7 @@ #include <signal.h> #include <errno.h> +#include <boost/scoped_ptr.hpp> #include "libnrtype/Layout-TNG.h" #include <2geom/transforms.h> @@ -55,7 +56,7 @@ #include "sp-mask.h" #include "sp-clippath.h" -#include <unit-constants.h> +#include "util/units.h" #include "helper/png-write.h" #include "helper/pixbuf-ops.h" @@ -101,7 +102,6 @@ namespace Extension { namespace Internal { CairoRenderer::CairoRenderer(void) - : _omitText(false) {} CairoRenderer::~CairoRenderer(void) @@ -348,8 +348,8 @@ static void sp_image_render(SPItem *item, CairoRenderContext *ctx) if (!image->pixbuf) return; if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return; - w = gdk_pixbuf_get_width (image->pixbuf); - h = gdk_pixbuf_get_height (image->pixbuf); + w = image->pixbuf->width(); + h = image->pixbuf->height(); double x = image->x.computed; double y = image->y.computed; @@ -442,7 +442,7 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) */ res = ctx->getBitmapResolution(); if(res == 0) { - res = PX_PER_IN; + res = Inkscape::Util::Quantity::convert(1, "in", "px"); } TRACE(("sp_asbitmap_render: resolution: %f\n", res )); @@ -450,7 +450,7 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) Geom::OptRect bbox = item->desktopVisualBounds(); // no bbox, e.g. empty group - if (!bbox) { + if (!bbox) { return; } @@ -463,8 +463,8 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) } // The width and height of the bitmap in pixels - unsigned width = ceil(bbox->width() * (res / PX_PER_IN)); - unsigned height = ceil(bbox->height() * (res / PX_PER_IN)); + 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; @@ -477,7 +477,7 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) double shift_y = bbox->max()[Geom::Y]; // For default 90 dpi, snap bitmap to pixel grid - if (res == PX_PER_IN) { + if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) { shift_x = round (shift_x); shift_y = -round (-shift_y); // Correct rounding despite coordinate inversion. // Remove the negations when the inversion is gone. @@ -498,17 +498,15 @@ static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx) GSList *items = NULL; items = g_slist_append(items, item); - GdkPixbuf *pb = sp_generate_internal_bitmap(document, NULL, - bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y], - width, height, res, res, (guint32) 0xffffff00, items ); + boost::scoped_ptr<Inkscape::Pixbuf> pb( + sp_generate_internal_bitmap(document, NULL, + bbox->min()[Geom::X], bbox->min()[Geom::Y], bbox->max()[Geom::X], bbox->max()[Geom::Y], + width, height, res, res, (guint32) 0xffffff00, items )); if (pb) { - TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL )); - // TODO this is stupid - we just converted to pixbuf format when generating the bitmap! - convert_pixbuf_normal_to_argb32(pb); - ctx->renderImage(pb, t, item->style); - g_object_unref(pb); - pb = 0; + //TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL )); + + ctx->renderImage(pb.get(), t, item->style); } g_slist_free (items); } @@ -578,18 +576,13 @@ CairoRenderer::setStateForItem(CairoRenderContext *ctx, SPItem const *item) // TODO change this to accept a const SPItem: void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item) { - if ( _omitText && (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) ) { - // skip text if _omitText is true - return; - } - ctx->pushState(); setStateForItem(ctx, item); CairoRenderState *state = ctx->getCurrentState(); state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 ); - // Draw item on a temporary surface so a mask, clip path, or opacity can be applied to it. + // 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(); @@ -598,13 +591,13 @@ void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item) sp_item_invoke_render(item, ctx); if (state->need_layer) - ctx->popLayer(); + ctx->popLayer(); // This applies clipping/masking ctx->popState(); } bool -CairoRenderer::setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, SPItem *base) +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 PDFLaTeXRenderer::setupDocument !!! @@ -625,10 +618,11 @@ CairoRenderer::setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool page } d = *bbox; } + d.expandBy(bleedmargin_px); if (ctx->_vector_based_target) { // convert from px to pt - d *= Geom::Scale(PT_PER_PX); + d *= Geom::Scale(Inkscape::Util::Quantity::convert(1, "px", "pt")); } ctx->_width = d.width(); @@ -638,16 +632,21 @@ CairoRenderer::setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool page bool ret = ctx->setupSurface(ctx->_width, ctx->_height); - if (ret && !pageBoundingBox) - { - double high = doc->getHeight(); - if (ctx->_vector_based_target) - high *= PT_PER_PX; - - /// @fixme hardcoded dt2doc transform? - Geom::Affine tp(Geom::Translate(-d.left() * (ctx->_vector_based_target ? PX_PER_PT : 1.0), - (d.bottom() - high) * (ctx->_vector_based_target ? PX_PER_PT : 1.0))); - ctx->transform(tp); + if (ret) { + if (pageBoundingBox) { + // translate to set bleed/margin + Geom::Affine tp( Geom::Translate( bleedmargin_px, bleedmargin_px ) ); + ctx->transform(tp); + } else { + double high = doc->getHeight().value("px"); + if (ctx->_vector_based_target) + high = Inkscape::Util::Quantity::convert(high, "px", "pt"); + + // this transform translates the export drawing to a virtual page (0,0)-(width,height) + Geom::Affine tp(Geom::Translate(-d.left() * (ctx->_vector_based_target ? Inkscape::Util::Quantity::convert(1, "pt", "px") : 1.0), + (d.bottom() - high) * (ctx->_vector_based_target ? Inkscape::Util::Quantity::convert(1, "pt", "px") : 1.0))); + ctx->transform(tp); + } } return ret; diff --git a/src/extension/internal/cairo-renderer.h b/src/extension/internal/cairo-renderer.h index 7fa7c7ff5..cfef6bdea 100644 --- a/src/extension/internal/cairo-renderer.h +++ b/src/extension/internal/cairo-renderer.h @@ -53,14 +53,10 @@ public: /** 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, SPItem *base); + 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); - - /** If _omitText is true, no text will be output to the PDF document. - The PDF will be exactly the same as if the text was written to it and then erased. */ - bool _omitText; }; // FIXME: this should be a static method of CairoRenderer diff --git a/src/extension/internal/cdr-input.cpp b/src/extension/internal/cdr-input.cpp new file mode 100644 index 000000000..0111ed626 --- /dev/null +++ b/src/extension/internal/cdr-input.cpp @@ -0,0 +1,339 @@ +/* + * 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 bellow. + * + * Authors: + * Fridrich Strba (fridrich.strba@bluewin.ch) + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#include <stdio.h> +#include "config.h" + +#include "cdr-input.h" + +#ifdef WITH_LIBCDR + +#include <string> +#include <cstring> + +#include <libcdr/libcdr.h> +#include <libwpd-stream/libwpd-stream.h> + +#include <gtkmm/alignment.h> +#include <gtkmm/comboboxtext.h> +#include <gtkmm/drawingarea.h> +#include <gtkmm/frame.h> +#include <gtkmm/scale.h> + +#include "extension/system.h" +#include "extension/input.h" +#include "document.h" + +#include "document-private.h" +#include "document-undo.h" +#include "inkscape.h" + +#include "dialogs/dialog-events.h" +#include <gtk/gtk.h> +#include "ui/widget/spinbutton.h" +#include "ui/widget/frame.h" +#include <glibmm/i18n.h> + +#include <gdkmm/general.h> + +#include "svg-view.h" +#include "svg-view-widget.h" + +#include "util/units.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + + +class CdrImportDialog : public Gtk::Dialog { +public: + CdrImportDialog(const std::vector<WPXString> &vec); + virtual ~CdrImportDialog(); + + bool showDialog(); + unsigned getSelectedPage(); + void getImportSettings(Inkscape::XML::Node *prefs); + +private: + void _setPreviewPage(unsigned page); + + // Signal handlers +#if !WITH_GTKMM_3_0 + bool _onExposePreview(GdkEventExpose *event); +#endif + + void _onPageNumberChanged(); + + class Gtk::Button * cancelbutton; + class Gtk::Button * okbutton; + class Gtk::Label * _labelSelect; + class Inkscape::UI::Widget::SpinButton * _pageNumberSpin; + class Gtk::Label * _labelTotalPages; + class Gtk::VBox * vbox1; + class Gtk::VBox * vbox2; + class Gtk::Widget * _previewArea; + + const std::vector<WPXString> &_vec; // Document to be imported + unsigned _current_page; // Current selected page + int _preview_width, _preview_height; // Size of the preview area +}; + +CdrImportDialog::CdrImportDialog(const std::vector<WPXString> &vec) + : _vec(vec), _current_page(1) +{ + int num_pages = _vec.size(); + if ( num_pages <= 1 ) + return; + cancelbutton = Gtk::manage(new class Gtk::Button(Gtk::StockID("gtk-cancel"))); + okbutton = Gtk::manage(new class Gtk::Button(Gtk::StockID("gtk-ok"))); + _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:"))); + + // Page number +#if WITH_GTKMM_3_0 + Glib::RefPtr<Gtk::Adjustment> _pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _vec.size(), 1, 10, 0); + _pageNumberSpin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(_pageNumberSpin_adj, 1, 1)); +#else + Gtk::Adjustment *_pageNumberSpin_adj = Gtk::manage( + new class Gtk::Adjustment(1, 1, _vec.size(), 1, 10, 0)); + _pageNumberSpin = Gtk::manage(new class Inkscape::UI::Widget::SpinButton(*_pageNumberSpin_adj, 1, 1)); +#endif + _labelTotalPages = Gtk::manage(new class Gtk::Label()); + gchar *label_text = g_strdup_printf(_("out of %i"), num_pages); + _labelTotalPages->set_label(label_text); + g_free(label_text); + + vbox1 = Gtk::manage(new class Gtk::VBox(false, 4)); + SPDocument *doc = SPDocument::createNewDocFromMem(_vec[0].cstr(), strlen(_vec[0].cstr()), 0); + _previewArea = Glib::wrap(sp_svg_view_widget_new(doc)); + + vbox2 = Gtk::manage(new class Gtk::VBox(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); + this->get_action_area()->property_layout_style().set_value(Gtk::BUTTONBOX_END); + _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_line_wrap(false); + _labelTotalPages->set_use_markup(false); + _labelTotalPages->set_selectable(false); + vbox2->pack_start(*_previewArea, Gtk::PACK_SHRINK, 0); + this->get_vbox()->set_homogeneous(false); + this->get_vbox()->set_spacing(0); + this->get_vbox()->pack_start(*vbox2); + 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); + this->get_action_area()->add(*_labelSelect); + this->add_action_widget(*_pageNumberSpin, -7); + this->get_action_area()->add(*_labelTotalPages); + this->add_action_widget(*cancelbutton, -6); + this->add_action_widget(*okbutton, -5); + cancelbutton->show(); + okbutton->show(); + _labelSelect->show(); + _pageNumberSpin->show(); + _labelTotalPages->show(); + vbox1->show(); + _previewArea->show(); + vbox2->show(); + + // Connect signals + _pageNumberSpin_adj->signal_value_changed().connect(sigc::mem_fun(*this, &CdrImportDialog::_onPageNumberChanged)); +} + +CdrImportDialog::~CdrImportDialog() {} + +bool CdrImportDialog::showDialog() +{ + show(); + gint b = run(); + hide(); + if ( b == Gtk::RESPONSE_OK ) { + 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(_current_page); +} + +/** + * \brief Renders the given page's thumbnail + */ +void CdrImportDialog::_setPreviewPage(unsigned page) +{ + SPDocument *doc = SPDocument::createNewDocFromMem(_vec[page-1].cstr(), strlen(_vec[page-1].cstr()), 0); + Gtk::Widget * tmpPreviewArea = Glib::wrap(sp_svg_view_widget_new(doc)); + std::swap(_previewArea, tmpPreviewArea); + if (tmpPreviewArea) { + _previewArea->set_size_request( tmpPreviewArea->get_width(), tmpPreviewArea->get_height() ); + delete tmpPreviewArea; + } + vbox2->pack_start(*_previewArea, Gtk::PACK_SHRINK, 0); + _previewArea->show_now(); +} + +SPDocument *CdrInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri) +{ + WPXFileStream input(uri); + + if (!libcdr::CDRDocument::isSupported(&input)) { + return NULL; + } + + libcdr::CDRStringVector output; + if (!libcdr::CDRDocument::generateSVG(&input, output)) { + return NULL; + } + + if (output.empty()) { + return NULL; + } + + std::vector<WPXString> tmpSVGOutput; + for (unsigned i=0; i<output.size(); ++i) { + WPXString 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 = 0; + if (inkscape_use_gui()) { + dlg = new CdrImportDialog(tmpSVGOutput); + if (!dlg->showDialog()) { + delete dlg; + return NULL; + } + } + + // 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->getDefaultUnit()), doc->getHeight().value(doc->getDefaultUnit()))); + } + return doc; +} + +#include "clear-n_.h" + +void CdrInput::init(void) +{ + /* 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 000000000..3de6c1ed0 --- /dev/null +++ b/src/extension/internal/cdr-input.h @@ -0,0 +1,58 @@ +/* + * 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, read the file 'COPYING' for more information + */ + +#ifndef __EXTENSION_INTERNAL_CDROUTPUT_H__ +#define __EXTENSION_INTERNAL_CDROUTPUT_H__ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef WITH_LIBCDR + +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include <glibmm/threads.h> +#endif + +#include <gtkmm/dialog.h> + +#include "../implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class CdrInput : public Inkscape::Extension::Implementation::Implementation { + CdrInput () { }; +public: + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ); + static void init( void ); + +}; + +} } } /* 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/emf-inout.cpp b/src/extension/internal/emf-inout.cpp new file mode 100644 index 000000000..eae3bfb5a --- /dev/null +++ b/src/extension/internal/emf-inout.cpp @@ -0,0 +1,3547 @@ +/** @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, 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 + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +//#include <png.h> //This must precede text_reassemble.h or it blows up in pngconf.h when compiling +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <libuemf/symbol_convert.h> + +#include "sp-root.h" +#include "sp-path.h" +#include "style.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 "document.h" +#include "util/units.h" +#include "shape-editor.h" +#include "sp-namedview.h" +#include "document-undo.h" +#include "inkscape.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 U_RECTL rc_old = rectl_set(pointl_set(-1,-1),pointl_set(-1,-1)); +static bool clipset = false; +static uint32_t ICMmode = 0; // not used yet, but code to read it from EMF implemented +static uint32_t BLTmode = 0; + +Emf::Emf (void) // The null constructor +{ + return; +} + + +Emf::~Emf (void) //The destructor +{ + return; +} + + +bool +Emf::check (Inkscape::Extension::Extension * /*module*/) +{ + if (NULL == 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 = NULL; + mod->root = NULL; // 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 == NULL) + 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); + + print_document_to_file(doc, filename); + + 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){ + std::stringstream 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 clocwise 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); + 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); + + *(d->defs) += "\n"; + switch(hatchType){ + case U_HS_HORIZONTAL: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 6 0\" style=\"fill:none;stroke:#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\" />\n"; + break; + case U_HS_VERTICAL: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 0 6\" style=\"fill:none;stroke:#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\" />\n"; + break; + case U_HS_FDIAGONAL: + *(d->defs) += " <line id=\"sub"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\"/>\n"; + break; + case U_HS_BDIAGONAL: + *(d->defs) += " <line id=\"sub"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\"/>\n"; + break; + case U_HS_CROSS: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 6 0 M 0 0 0 6\" style=\"fill:none;stroke:#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\" />\n"; + break; + case U_HS_DIAGCROSS: + *(d->defs) += " <line id=\"subfd"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\"/>\n"; + *(d->defs) += " <line id=\"subbd"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->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: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 6 0 6 6 0 6 z\" style=\"fill:#"; + *(d->defs) += tmpcolor; + *(d->defs) += ";stroke:none"; + *(d->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); + *(d->defs) += "\n"; + *(d->defs) += " <pattern id=\""; + *(d->defs) += hatchname; + *(d->defs) += "\" xlink:href=\"#EMFhbasepattern\">\n"; + *(d->defs) += refpath; + *(d->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); + + *(d->defs) += "\n"; + *(d->defs) += " <rect id=\""; + *(d->defs) += hbkname; + *(d->defs) += "\" x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"#"; + *(d->defs) += bkcolor; + *(d->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); + *(d->defs) += "\n"; + *(d->defs) += " <pattern id=\""; + *(d->defs) += hatchname; + *(d->defs) += "\" xlink:href=\"#EMFhbasepattern\">\n"; + *(d->defs) += " <use xlink:href=\"#"; + *(d->defs) += hbkname; + *(d->defs) += "\" />\n"; + *(d->defs) += refpath; + *(d->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, 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 = NULL; + + char *rgba_px = NULL; // RGBA pixels + const char *px = NULL; // DIB pixels + const U_RGBQUAD *ct = NULL; // 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(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=NULL; + 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); + 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 + + *(d->defs) += "\n"; + *(d->defs) += " <image id=\""; + *(d->defs) += imagename; + *(d->defs) += "\"\n "; + *(d->defs) += xywh; + *(d->defs) += "\n"; + if(dibparams == U_BI_JPEG){ *(d->defs) += " xlink:href=\"data:image/jpeg;base64,"; } + else { *(d->defs) += " xlink:href=\"data:image/png;base64,"; } + *(d->defs) += base64String; + *(d->defs) += "\"\n"; + *(d->defs) += " preserveAspectRatio=\"none\"\n"; + *(d->defs) += " />\n"; + + + *(d->defs) += "\n"; + *(d->defs) += " <pattern id=\""; + *(d->defs) += imagename; + *(d->defs) += "_ref\"\n "; + *(d->defs) += xywh; + *(d->defs) += "\n patternUnits=\"userSpaceOnUse\""; + *(d->defs) += " >\n"; + *(d->defs) += " <use id=\""; + *(d->defs) += imagename; + *(d->defs) += "_ign\" "; + *(d->defs) += " xlink:href=\"#"; + *(d->defs) += imagename; + *(d->defs) += "\" />\n"; + *(d->defs) += " "; + *(d->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 stil 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++); + + *(d->defs) += "\n"; + *(d->defs) += " <pattern\n"; + *(d->defs) += " id=\""; + *(d->defs) += imrotname; + *(d->defs) += "_ref\"\n"; + *(d->defs) += " xlink:href=\"#"; + *(d->defs) += imagename; + *(d->defs) += "_ref\"\n"; + *(d->defs) += " patternTransform="; + *(d->defs) += current_matrix(d, 0.0, 0.0, 0); //j use offset 0,0 + *(d->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, 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); +} + + + +void +Emf::output_style(PEMF_CALLBACK_DATA d, int iType) +{ +// SVGOStringStream tmp_id; + SVGOStringStream tmp_style; + char tmp[1024] = {0}; + + float fill_rgb[3]; + sp_color_get_rgb_floatv( &(d->dc[d->level].style.fill.value.color), fill_rgb ); + float stroke_rgb[3]; + sp_color_get_rgb_floatv(&(d->dc[d->level].style.stroke.value.color), 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 == 0 ? "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 == 0 ? "butt" : + d->dc[d->level].style.stroke_linecap.computed == 1 ? "round" : + d->dc[d->level].style.stroke_linecap.computed == 2 ? "square" : + "unknown" + ) << ";"; + + tmp_style << "stroke-linejoin:" << + ( + d->dc[d->level].style.stroke_linejoin.computed == 0 ? "miter" : + d->dc[d->level].style.stroke_linejoin.computed == 1 ? "round" : + d->dc[d->level].style.stroke_linejoin.computed == 2 ? "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]; + } + tmp_style << ";"; + tmp_style << "stroke-dashoffset:0;"; + } else { + tmp_style << "stroke-dasharray:none;"; + } + tmp_style << "stroke-opacity:1;"; + } + tmp_style << "\" "; + if (clipset) + tmp_style << "\n\tclip-path=\"url(#clipEmfPath" << d->id << ")\" "; + clipset = false; + + *(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; +} + +/* returns "x,y" (without the quotes) in inkscape coordinates for a pair of EMF x,y coordinates +*/ +std::string Emf::pix_to_xy(PEMF_CALLBACK_DATA d, double x, double y){ + std::stringstream cxform; + cxform << pix_to_x_point(d,x,y); + cxform << ","; + cxform << pix_to_y_point(d,x,y); + return(cxform.str()); +} + + +void +Emf::select_pen(PEMF_CALLBACK_DATA d, int index) +{ + PU_EMRCREATEPEN pEmr = NULL; + + 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: + { + 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.values!=d->dc[d->level-1].style.stroke_dasharray.values))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 3 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + } + if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + } + if (penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + } + + d->dc[d->level].style.stroke_dasharray.set = 1; + break; + } + + case U_PS_SOLID: + default: + { + d->dc[d->level].style.stroke_dasharray.set = 0; + break; + } + } + + switch (pEmr->lopn.lopnStyle & U_PS_ENDCAP_MASK) { + case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = 1; break; } + case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = 2; break; } + case U_PS_ENDCAP_FLAT: + default: { d->dc[d->level].style.stroke_linecap.computed = 0; break; } + } + + switch (pEmr->lopn.lopnStyle & U_PS_JOIN_MASK) { + case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = 2; break; } + case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = 0; break; } + case U_PS_JOIN_ROUND: + default: { d->dc[d->level].style.stroke_linejoin.computed = 1; 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 = NULL; + + 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.values!=d->dc[d->level-1].style.stroke_dasharray.values))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + for (unsigned int i=0; i<pEmr->elp.elpNumEntries; i++) { + int cur_level = d->level; + d->level = d->emf_obj[index].level; +// Doing it this way typically results in a pattern that is tiny, better to assume the array +// is the same scale as for dot/dash below, that is, no scaling should be applied +// double dash_length = pix_to_abs_size( d, pEmr->elp.elpStyleEntry[i] ); + double dash_length = pEmr->elp.elpStyleEntry[i]; + d->level = cur_level; + d->dc[d->level].style.stroke_dasharray.values[i] = dash_length; + } + d->dc[d->level].style.stroke_dasharray.set = 1; + } else { + d->dc[d->level].style.stroke_dasharray.set = 0; + } + 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.values!=d->dc[d->level-1].style.stroke_dasharray.values))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 3 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 2 ); + } + if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 2 ); + } + if (penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 2 ); + } + + d->dc[d->level].style.stroke_dasharray.set = 1; + 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 = 0; + break; + } + } + + switch (pEmr->elp.elpPenStyle & U_PS_ENDCAP_MASK) { + case U_PS_ENDCAP_ROUND: + { + d->dc[d->level].style.stroke_linecap.computed = 1; + break; + } + case U_PS_ENDCAP_SQUARE: + { + d->dc[d->level].style.stroke_linecap.computed = 2; + break; + } + case U_PS_ENDCAP_FLAT: + default: + { + d->dc[d->level].style.stroke_linecap.computed = 0; + break; + } + } + + switch (pEmr->elp.elpPenStyle & U_PS_JOIN_MASK) { + case U_PS_JOIN_BEVEL: + { + d->dc[d->level].style.stroke_linejoin.computed = 2; + break; + } + case U_PS_JOIN_MITER: + { + d->dc[d->level].style.stroke_linejoin.computed = 0; + break; + } + case U_PS_JOIN_ROUND: + default: + { + d->dc[d->level].style.stroke_linejoin.computed = 1; + 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, 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 = NULL; + + 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 : + U_FW_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, NULL); + 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 +// reord 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 = NULL; + } +} + + +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 = NULL; + if(pEmr->nDescription)string = U_Utf16leToUtf8((uint16_t *)((char *) pEmr + pEmr->offDescription), pEmr->nDescription, NULL); + 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"; + tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n "; + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = NULL; + + char *rgba_px = NULL; // RGBA pixels + char *sub_px = NULL; // RGBA pixels, subarray + const char *px = NULL; // DIB pixels + const U_RGBQUAD *ct = NULL; // 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(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=NULL; + 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; + 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 + + /* initialize the tsp for text reassembly */ + 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.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. + + lpEMFR = (PU_ENHMETARECORD)(contents + off); +// Uncomment the following to track down toxic records +//std::cout << "record type: " << lpEMFR->iType << " length: " << lpEMFR->nSize << " offset: " << off <<std::endl; + off += lpEMFR->nSize; + + SVGOStringStream tmp_outsvg; + SVGOStringStream tmp_path; + SVGOStringStream tmp_str; + SVGOStringStream dbg_str; + + emr_mask = emr_properties(lpEMFR->iType); + if(emr_mask == U_EMR_INVALID){ throw "Inkscape fatal memory allocation error - cannot continue"; } + +/* 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); + 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].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" << std::endl; + *(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 (lpEMFR->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 != NULL ) + { + for( int i=0; i < d->n_obj; ++i ) + d->emf_obj[i].lpEMFR = NULL; + } //if + + } else { + d->emf_obj = NULL; + } + + 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 ? 0 : (pEmr->iMode == U_WINDING ? 1 : 0)); + 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"; 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"; break; + case U_EMR_INTERSECTCLIPRECT: + { + dbg_str << "<!-- U_EMR_INTERSECTCLIPRECT -->\n"; + + PU_EMRINTERSECTCLIPRECT pEmr = (PU_EMRINTERSECTCLIPRECT) lpEMFR; + U_RECTL rc = pEmr->rclClip; + clipset = true; + if ((rc.left == rc_old.left) && (rc.top == rc_old.top) && (rc.right == rc_old.right) && (rc.bottom == rc_old.bottom)) + break; + rc_old = rc; + + double dx = pix_to_x_point( d, rc.left, rc.top ); + double dy = pix_to_y_point( d, rc.left, rc.top ); + double dw = pix_to_abs_size( d, rc.right - rc.left + 1); + double dh = pix_to_abs_size( d, rc.bottom - rc.top + 1); + + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n<clipPath\n\tclipPathUnits=\"userSpaceOnUse\" "; + tmp_rectangle << "\nid=\"clipEmfPath" << ++(d->id) << "\" >"; + tmp_rectangle << "\n<rect "; + tmp_rectangle << "\n x=\"" << dx << "\" "; + tmp_rectangle << "\n y=\"" << dy << "\" "; + tmp_rectangle << "\n width=\"" << dw << "\" "; + tmp_rectangle << "\n height=\"" << dh << "\" "; + tmp_rectangle << "\n transform=" << current_matrix(d, dx, dy, 1); // calculate appropriate offset + tmp_rectangle << "/>\n</clipPath>"; + + *(d->outdef) += tmp_rectangle.str().c_str(); + *(d->path) = ""; + 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.values!=d->dc[old_level-1].style.stroke_dasharray.values))){ + 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 = NULL; + } + 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 = 0; + 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, fabs(rclBox.right - rclBox.left )/2.0 ); + double ry = pix_to_abs_size( d, fabs(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"; 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 (0 && 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; + if (pEmr->iMode == U_RGN_COPY) + clipset = false; + 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 = NULL; + + 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 someting is really screwed up! + */ + dup_wt = U_Utf8ToUtf32le((char *) pEmr + pEmr->emrtext.offString, pEmr->emrtext.nChars, NULL); + if(!dup_wt)dup_wt = U_Latin1ToUtf32le((char *) pEmr + pEmr->emrtext.offString, pEmr->emrtext.nChars, NULL); + 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, NULL); + 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, NULL); + } + else { + dup_wt = U_Utf16leToUtf32le((uint16_t *)((char *) pEmrS + roff), cChars, NULL); + } + 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, NULL); + 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=NULL; + } + + 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); + 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 + } + + 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/>\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 +// When testing, uncomment the following to place a comment for each processed EMR record in the SVG +// *(d->outsvg) += dbg_str.str().c_str(); + *(d->outsvg) += tmp_outsvg.str().c_str(); + *(d->path) += tmp_path.str().c_str(); + + } //end of while +// When testing, uncomment the following to show the final SVG derived from the EMF +// 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 1; +} + +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); + } +} + +SPDocument * +Emf::open( Inkscape::Extension::Input * /*mod*/, const gchar *uri ) +{ + EMF_CALLBACK_DATA d; + +// memset(&d, 0, sizeof(d)); + memset(&d, 0, sizeof(EMF_CALLBACK_DATA)); + + for(int i = 0; i < EMF_MAX_DC+1; i++){ // be sure all values and pointers are empty to start with + memset(&(d.dc[i]),0,sizeof(EMF_DEVICE_CONTEXT)); + } + + d.dc[0].worldTransform.eM11 = 1.0; + d.dc[0].worldTransform.eM12 = 0.0; + d.dc[0].worldTransform.eM21 = 0.0; + d.dc[0].worldTransform.eM22 = 1.0; + d.dc[0].worldTransform.eDx = 0.0; + d.dc[0].worldTransform.eDy = 0.0; + d.dc[0].font_name = strdup("Arial"); // Default font, EMF spec says device can pick whatever it wants + d.dc[0].textColor = U_RGB(0, 0, 0); // default foreground color (black) + d.dc[0].bkColor = U_RGB(255, 255, 255); // default background color (white) + d.dc[0].bkMode = U_TRANSPARENT; + d.dc[0].dirty = 0; + + if (uri == NULL) { + return NULL; + } + + d.outsvg = new Glib::ustring(""); + d.path = new Glib::ustring(""); + d.outdef = new Glib::ustring(""); + d.defs = new Glib::ustring(""); + d.mask = 0; + d.drawtype = 0; + d.arcdir = U_AD_COUNTERCLOCKWISE; + d.dwRop2 = U_R2_COPYPEN; + d.dwRop3 = 0; + d.E2IdirY = 1.0; + d.D2PscaleX = 1.0; + d.D2PscaleY = 1.0; + d.hatches.size = 0; + d.hatches.count = 0; + d.hatches.strings = NULL; + d.images.size = 0; + d.images.count = 0; + d.images.strings = NULL; + d.gradients.size = 0; + d.gradients.count = 0; + d.gradients.strings = NULL; + + // 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(NULL); + + d.pDesc = NULL; + + // set up the text reassembly system + if(!(d.tri = trinfo_init(NULL)))return(NULL); + (void) trinfo_load_ft_opts(d.tri, 1, + FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, + FT_KERNING_UNSCALED); + + (void) myEnhMetaFileProc(contents,length, &d); + free(contents); + + if (d.pDesc){ free( d.pDesc ); } + +// std::cout << "SVG Output: " << std::endl << *(d.outsvg) << std::endl; + + SPDocument *doc = SPDocument::createNewDocFromMem(d.outsvg->c_str(), strlen(d.outsvg->c_str()), TRUE); + + delete d.outsvg; + delete d.path; + delete d.outdef; + delete d.defs; + free_emf_strings(d.hatches); + free_emf_strings(d.images); + free_emf_strings(d.gradients); + + 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<=d.level;i++){ + if(d.dc[i].font_name)free(d.dc[i].font_name); + } + + d.tri = trinfo_release_except_FC(d.tri); + + // Set viewBox if it doesn't exist + 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, 0)->getRepr(); + Inkscape::SVGOStringStream os; + Inkscape::Util::Unit const* doc_unit = doc->getWidth().unit; + os << doc_unit->abbr; + repr->setAttribute("inkscape:document-units", os.str().c_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); + 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"); + } + doc->getRoot()->scaleChildItemsRec(Geom::Scale(scale), Geom::Point(0, dh)); + ShapeEditor::blockSetItem(false); + + Inkscape::DocumentUndo::setUndoSensitive(doc, saved); + } + + return doc; +} + + +void +Emf::init (void) +{ + /* 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=\"boolean\">true</param>\n" + "<param name=\"TnrToSymbol\" gui-text=\"" N_("Map Unicode to Symbol font") "\" type=\"boolean\">true</param>\n" + "<param name=\"TnrToWingdings\" gui-text=\"" N_("Map Unicode to Wingdings") "\" type=\"boolean\">true</param>\n" + "<param name=\"TnrToZapfDingbats\" gui-text=\"" N_("Map Unicode to Zapf Dingbats") "\" type=\"boolean\">true</param>\n" + "<param name=\"UsePUA\" gui-text=\"" N_("Use MS Unicode PUA (0xF020-0xF0FF) for converted characters") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTCharPos\" gui-text=\"" N_("Compensate for PPT font bug") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTDashLine\" gui-text=\"" N_("Convert dashed/dotted lines to single lines") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTGrad2Polys\" gui-text=\"" N_("Convert gradients to colored polygon series") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTLinGrad\" gui-text=\"" N_("Use native rectangular linear gradients") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTPatternAsHatch\" gui-text=\"" N_("Map all fill patterns to standard EMF hatches") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixImageRot\" gui-text=\"" N_("Ignore image rotations") "\" type=\"boolean\">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 000000000..a97cb0a54 --- /dev/null +++ b/src/extension/internal/emf-inout.h @@ -0,0 +1,183 @@ +/** @file + * @brief Enhanced Metafile Input/Output + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * David Mathog + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SEEN_EXTENSION_INTERNAL_EMF_H +#define SEEN_EXTENSION_INTERNAL_EMF_H + +#include <libuemf/uemf.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 + +typedef struct { + int type; + int level; + char *lpEMFR; +} EMF_OBJECT, *PEMF_OBJECT; + +typedef struct { + int size; // number of slots allocated in strings + int count; // number of slots used in strings + char **strings; // place to store strings +} EMF_STRINGS, *PEMF_STRINGS; + +typedef struct emf_device_context { + struct SPStyle style; + char *font_name; + 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; +} EMF_DEVICE_CONTEXT, *PEMF_DEVICE_CONTEXT; + +#define EMF_MAX_DC 128 + +typedef struct { + 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 id; + 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 + TR_INFO *tri; // Text Reassembly data structure + + + int n_obj; + PEMF_OBJECT emf_obj; +} EMF_CALLBACK_DATA, *PEMF_CALLBACK_DATA; + +class Emf : public Metafile +{ + +public: + + Emf(); // Empty constructor + + virtual ~Emf();//Destructor + + bool check(Inkscape::Extension::Extension *module); //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); + + virtual SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ); + + static void init(void);//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, 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, char *test); + static uint32_t add_gradient(PEMF_CALLBACK_DATA d, uint32_t gradientType, U_TRIVERTEX tv1, U_TRIVERTEX tv2); + 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 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 000000000..f4f7f08cb --- /dev/null +++ b/src/extension/internal/emf-print.cpp @@ -0,0 +1,2061 @@ +/** @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, 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 + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <string.h> +#include <glibmm/miscutils.h> +#include <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 "sp-item.h" +#include "util/units.h" + +#include "style.h" +#include "inkscape-version.h" +#include "sp-root.h" + +#include "extension/system.h" +#include "extension/print.h" +#include "document.h" +#include "path-prefix.h" +#include "sp-pattern.h" +#include "sp-image.h" +#include "sp-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-linear-gradient.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 "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 = NULL; +static EMFHANDLES *eht = NULL; + +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 = NULL; + *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"); + + // 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 = sp_repr_lookup_name(doc->rroot, "sodipodi:namedview"); + 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 (%s)\1%s\1", Inkscape::version_string, __DATE__, p); + uint16_t *Description = U_Utf8ToUtf16le(buff, 0, NULL); + 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, NULL, 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 (1) { + 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=%.1lfx%.1lfpx, %.1lfx%.1lfmm", _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*/) +{ + 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, NULL, 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 + sp_color_get_rgb_floatv(&style->fill.value.color, 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 = pattern_width(pat); + double dheight = pattern_height(pat); + 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 = NULL; + SPRadialGradient *rg = NULL; + + 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); + // Not sure why the next swap is needed because the preceding does it, and the code is identical + // to that in stretchdibits_set, which does not need this. + 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 = NULL; + char *rec = NULL; + 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 = NULL; + char *rgba_px; + uint32_t cbPx = 0; + uint32_t colortype; + PU_RGBQUAD ct = NULL; + int numCt = 0; + U_BITMAPINFOHEADER Bmih; + PU_BITMAPINFO Bmi = NULL; + + 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 = pattern_width(pat); + double dheight = pattern_height(pat); + 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); + // Not sure why the next swap is needed because the preceding does it, and the code is identical + // to that in stretchdibits_set, which does not need this. + 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 + sp_color_get_rgb_floatv(&style->stroke.value.color, 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] > 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] = (uint32_t)(Inkscape::Util::Quantity::convert(1, "mm", "px") * style->stroke_dasharray.values[i]); + } + } + } + } + + 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, + NULL); + } + + 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 = NULL; + // 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 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 . 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(); + ++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() ); + } + *vertices += 1; + Geom::LineSegment ls(P1_trail, P1); + output.append(ls); + 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 P1_lead; + 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::const_iterator cit = pR.begin(); cit != pR.end_open(); ++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 + } + } + + /* 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; + + for(Geom::Path::const_iterator cit = pR.begin(); cit != pR.end_open();++cit) { + P1_lead = cit->finalPoint(); + v1 = unit_vector(P1 - P1_trail); + v2 = unit_vector(P1_lead - P1 ); + // 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; + P1_trail = P1; + P1 = P1_lead; + vertex_count++; + } + 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) + 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; } + if(!stat){ + v2 = Geom::Point(0,1) * Geom::Rotate(-angle); // unit vertical side + if( Geom::are_near(dot(v1,v2), 1.0, 1e-5)){ stat = 3; } + else if(Geom::are_near(dot(v1,v2),-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::const_iterator cit = pathRect.begin(); cit != pathRect.end_open(); ++cit) { + center += cit->initialPoint()/4.0; + } + + 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::const_iterator cit = pathRect.begin(); cit != pathRect.end_open(); ++cit) { + P1 = cit->initialPoint(); + + if ( ( LR == (dot(P1 - center,v1) > 0 ? 0 : 1) ) + && ( UL == (dot(P1 - center,v2) > 0 ? 1 : 0) ) ) 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); +} + +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*/) +{ + using Geom::X; + using Geom::Y; + + Geom::Affine tf = m_tr_stack.top(); + + use_fill = true; + use_stroke = false; + + fill_transform = tf; + + int brush_stat = create_brush(style, NULL); + + /* 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(); + sp_color_get_rgb_floatv(&tg->vector.stops[0].color, rgb); + opa = tg->vector.stops[0].opacity; // first stop + c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + sp_color_get_rgb_floatv(&tg->vector.stops[nstops - 1].color, 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); + + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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; + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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){ + char *rec; + 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]); + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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); + + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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; + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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 (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { + 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 = NULL; + Geom::Affine tf = m_tr_stack.top(); + + 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 (unsigned int i = 0; i < pathv.size(); i++) { + tmp_pathpw.concat(pathv[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++]; + 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++]; // 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 = NULL; + + for (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + moves++; + nodes++; + + for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { + nodes++; + + if (is_straight_curve(*cit)) { + lines++; + } else if (&*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 (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + 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->points(); + //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 tranforms, use the one from m_tr_stack */ + SPStyle const * /*style*/) /** provides indirect link to image object */ +{ + double x1, y1, dw, dh; + char *rec = NULL; + Geom::Affine tf = m_tr_stack.top(); + + 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; +} + +// 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 = NULL; + + 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; + } + + /* 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 * tf); + + 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 (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + 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->points(); + //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"); + } + + // 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) { + return 0; + } + + char *rec = NULL; + 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, NULL); + 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->text->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 (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->text->font_family.value, 0, NULL); + } else { + wfacename = U_Utf8ToUtf16le(FontName(newfont), 0, NULL); + } + + // 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, NULL); + 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]; + sp_color_get_rgb_floatv(&style->fill.value.color, 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(void) +{ + _load_ppt_fontfix_data(); + + /* 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 name=\"destination\" type=\"string\"></param>\n" + "<param name=\"textToPath\" type=\"boolean\">true</param>\n" + "<param name=\"pageBoundingBox\" type=\"boolean\">true</param>\n" + "<param name=\"FixPPTCharPos\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTDashLine\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTGrad2Polys\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTLinGrad\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTPatternAsHatch\" type=\"boolean\">false</param>\n" + "<param name=\"FixImageRot\" type=\"boolean\">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 000000000..1e4970a46 --- /dev/null +++ b/src/extension/internal/emf-print.h @@ -0,0 +1,99 @@ +/** @file + * @brief Enhanced Metafile printing - implementation + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * David Mathog + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_EMF_PRINT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_EMF_PRINT_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <libuemf/uemf.h> +#include "extension/internal/metafile-print.h" + +#include "sp-gradient.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, hpenOld; + + 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 */ + virtual unsigned int setup (Inkscape::Extension::Print * module); + + virtual unsigned int begin (Inkscape::Extension::Print * module, SPDocument *doc); + virtual unsigned int finish (Inkscape::Extension::Print * module); + + /* Rendering methods */ + virtual 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); + virtual 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); + virtual 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); + virtual unsigned int comment(Inkscape::Extension::Print *module, const char * comment); + virtual unsigned int text(Inkscape::Extension::Print *module, char const *text, + Geom::Point const &p, SPStyle const *style); + + static void init (void); +protected: + static void smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale); + + 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); + void destroy_brush(); + int create_pen(SPStyle const *style, const Geom::Affine &transform); + void destroy_pen(); +}; + +} /* 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/emf-win32-inout.cpp b/src/extension/internal/emf-win32-inout.cpp deleted file mode 100644 index f54bfc6ea..000000000 --- a/src/extension/internal/emf-win32-inout.cpp +++ /dev/null @@ -1,2524 +0,0 @@ -/** @file - * @brief Windows-only Enhanced Metafile input and output. - */ -/* Authors: - * Ulf Erikson <ulferikson@users.sf.net> - * Jon A. Cruz <jon@joncruz.org> - * Abhishek Sharma - * - * Copyright (C) 2006-2008 Authors - * - * Released under GNU GPL, 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 - */ - -#ifdef WIN32 - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - -#include "sp-root.h" -#include "sp-path.h" -#include "style.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/drawing-item.h" -#include "unit-constants.h" -#include "clear-n_.h" -#include "document.h" - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include "emf-win32-print.h" -#include "emf-win32-inout.h" - - -#define PRINT_EMF_WIN32 "org.inkscape.print.emf.win32" - -#ifndef PS_JOIN_MASK -#define PS_JOIN_MASK (PS_JOIN_BEVEL|PS_JOIN_MITER|PS_JOIN_ROUND) -#endif - -#define DPA 0x00A000C9 // TernaryRasterOperation - -namespace Inkscape { -namespace Extension { -namespace Internal { - -static float device_scale = DEVICESCALE; -static RECTL rc_old; -static bool clipset = false; - -EmfWin32::EmfWin32 (void) // The null constructor -{ - return; -} - - -EmfWin32::~EmfWin32 (void) //The destructor -{ - return; -} - - -bool -EmfWin32::check (Inkscape::Extension::Extension * /*module*/) -{ - if (NULL == Inkscape::Extension::db.get(PRINT_EMF_WIN32)) - return FALSE; - return TRUE; -} - - -static void -emf_print_document_to_file(SPDocument *doc, gchar const *filename) -{ - Inkscape::Extension::Print *mod; - SPPrintContext context; - gchar const *oldconst; - gchar *oldoutput; - unsigned int ret; - - doc->ensureUpToDate(); - - mod = Inkscape::Extension::get_print(PRINT_EMF_WIN32); - 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); - ret = mod->finish(); - /* Release arena */ - mod->base->invoke_hide(mod->dkey); - mod->base = NULL; - mod->root = NULL; // deleted by invoke_hide -/* end */ - - mod->set_param_string("destination", oldoutput); - g_free(oldoutput); - - return; -} - - -void -EmfWin32::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename) -{ - Inkscape::Extension::Extension * ext; - - ext = Inkscape::Extension::db.get(PRINT_EMF_WIN32); - if (ext == NULL) - return; - - bool old_textToPath = ext->get_param_bool("textToPath"); - bool new_val = mod->get_param_bool("textToPath"); - ext->set_param_bool("textToPath", new_val); - - emf_print_document_to_file(doc, filename); - - ext->set_param_bool("textToPath", old_textToPath); - - return; -} - - - -typedef struct { - int type; - int level; - ENHMETARECORD *lpEMFR; -} EMF_OBJECT, *PEMF_OBJECT; - -typedef struct emf_device_context { - struct SPStyle style; - class SPTextStyle tstyle; - bool stroke_set; - bool fill_set; - - SIZEL sizeWnd; - SIZEL sizeView; - float PixelsInX, PixelsInY; - float PixelsOutX, PixelsOutY; - POINTL winorg; - POINTL vieworg; - double ScaleInX, ScaleInY; - double ScaleOutX, ScaleOutY; - COLORREF textColor; - bool textColorSet; - DWORD textAlign; - XFORM worldTransform; - POINTL cur; -} EMF_DEVICE_CONTEXT, *PEMF_DEVICE_CONTEXT; - -#define EMF_MAX_DC 128 - -typedef struct emf_callback_data { - Glib::ustring *outsvg; - Glib::ustring *path; - Glib::ustring *outdef; - - EMF_DEVICE_CONTEXT dc[EMF_MAX_DC+1]; // FIXME: This should be dynamic.. - int level; - - double xDPI, yDPI; - bool pathless_stroke; - bool inpath; - - float MMX; - float MMY; - float dwInchesX; - float dwInchesY; - - unsigned int id; - CHAR *pDesc; - - int n_obj; - PEMF_OBJECT emf_obj; -} EMF_CALLBACK_DATA, *PEMF_CALLBACK_DATA; - - -static void -output_style(PEMF_CALLBACK_DATA d, int iType) -{ -// SVGOStringStream tmp_id; - SVGOStringStream tmp_style; - char tmp[1024] = {0}; - - float fill_rgb[3]; - sp_color_get_rgb_floatv( &(d->dc[d->level].style.fill.value.color), fill_rgb ); - - float stroke_rgb[3]; - sp_color_get_rgb_floatv(&(d->dc[d->level].style.stroke.value.color), stroke_rgb); - -// tmp_id << "\n\tid=\"" << (d->id++) << "\""; -// *(d->outsvg) += tmp_id.str().c_str(); - *(d->outsvg) += "\n\tstyle=\""; - if (iType == EMR_STROKEPATH || !d->dc[d->level].fill_set) { - tmp_style << "fill:none;"; - } else { - 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; - snprintf(tmp, 1023, - "fill-rule:%s;", - d->dc[d->level].style.fill_rule.value == 0 ? "evenodd" : "nonzero"); - tmp_style << tmp; - tmp_style << "fill-opacity:1;"; - - if (d->dc[d->level].fill_set && d->dc[d->level].stroke_set && d->dc[d->level].style.stroke_width.value == 1 && - 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 == EMR_FILLPATH || !d->dc[d->level].stroke_set) { - tmp_style << "stroke:none;"; - } else { - 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; - - 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 == 0 ? "butt" : - d->dc[d->level].style.stroke_linecap.computed == 1 ? "round" : - d->dc[d->level].style.stroke_linecap.computed == 2 ? "square" : - "unknown") << ";"; - - tmp_style << "stroke-linejoin:" << - (d->dc[d->level].style.stroke_linejoin.computed == 0 ? "miter" : - d->dc[d->level].style.stroke_linejoin.computed == 1 ? "round" : - d->dc[d->level].style.stroke_linejoin.computed == 2 ? "bevel" : - "unknown") << ";"; - - if (d->dc[d->level].style.stroke_linejoin.computed == 0) { - tmp_style << "stroke-miterlimit:" << - MAX( 0.01, d->dc[d->level].style.stroke_miterlimit.value ) << ";"; - } - - if (d->dc[d->level].style.stroke_dasharray_set && - d->dc[d->level].style.stroke_dash.n_dash && d->dc[d->level].style.stroke_dash.dash) - { - tmp_style << "stroke-dasharray:"; - for (int i=0; i<d->dc[d->level].style.stroke_dash.n_dash; i++) { - if (i) - tmp_style << ","; - tmp_style << d->dc[d->level].style.stroke_dash.dash[i]; - } - tmp_style << ";"; - tmp_style << "stroke-dashoffset:0;"; - } else { - tmp_style << "stroke-dasharray:none;"; - } - tmp_style << "stroke-opacity:1;"; - } - tmp_style << "\" "; - if (clipset) - tmp_style << "\n\tclip-path=\"url(#clipEmfPath" << d->id << ")\" "; - clipset = false; - - *(d->outsvg) += tmp_style.str().c_str(); -} - - -static double -_pix_x_to_point(PEMF_CALLBACK_DATA d, double px) -{ - double tmp = px - d->dc[d->level].winorg.x; - tmp *= d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0; - tmp += d->dc[d->level].vieworg.x; - return tmp; -} - -static double -_pix_y_to_point(PEMF_CALLBACK_DATA d, double px) -{ - double tmp = px - d->dc[d->level].winorg.y; - tmp *= d->dc[d->level].ScaleInY ? d->dc[d->level].ScaleInY : 1.0; - tmp += d->dc[d->level].vieworg.y; - return tmp; -} - - -static double -pix_to_x_point(PEMF_CALLBACK_DATA d, double px, double py) -{ - double ppx = _pix_x_to_point(d, px); - double ppy = _pix_y_to_point(d, py); - - double x = ppx * d->dc[d->level].worldTransform.eM11 + ppy * d->dc[d->level].worldTransform.eM21 + d->dc[d->level].worldTransform.eDx; - x *= device_scale; - - return x; -} - -static double -pix_to_y_point(PEMF_CALLBACK_DATA d, double px, double py) -{ - double ppx = _pix_x_to_point(d, px); - double ppy = _pix_y_to_point(d, py); - - double y = ppx * d->dc[d->level].worldTransform.eM12 + ppy * d->dc[d->level].worldTransform.eM22 + d->dc[d->level].worldTransform.eDy; - y *= device_scale; - - return y; -} - -static double -pix_to_size_point(PEMF_CALLBACK_DATA d, double px) -{ - double ppx = px * (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0); - double ppy = 0; - - double dx = ppx * d->dc[d->level].worldTransform.eM11 + ppy * d->dc[d->level].worldTransform.eM21; - dx *= device_scale; - double dy = ppx * d->dc[d->level].worldTransform.eM12 + ppy * d->dc[d->level].worldTransform.eM22; - dy *= device_scale; - - double tmp = sqrt(dx * dx + dy * dy); - return tmp; -} - - -static void -select_pen(PEMF_CALLBACK_DATA d, int index) -{ - PEMRCREATEPEN pEmr = NULL; - - if (index >= 0 && index < d->n_obj) - pEmr = (PEMRCREATEPEN) d->emf_obj[index].lpEMFR; - - if (!pEmr) - return; - - switch (pEmr->lopn.lopnStyle & PS_STYLE_MASK) { - case PS_DASH: - case PS_DOT: - case PS_DASHDOT: - case PS_DASHDOTDOT: - { - int i = 0; - int penstyle = (pEmr->lopn.lopnStyle & PS_STYLE_MASK); - d->dc[d->level].style.stroke_dash.n_dash = - penstyle == PS_DASHDOTDOT ? 6 : penstyle == PS_DASHDOT ? 4 : 2; - if (d->dc[d->level].style.stroke_dash.dash && (d->level==0 || (d->level>0 && d->dc[d->level].style.stroke_dash.dash!=d->dc[d->level-1].style.stroke_dash.dash))) - delete[] d->dc[d->level].style.stroke_dash.dash; - d->dc[d->level].style.stroke_dash.dash = new double[d->dc[d->level].style.stroke_dash.n_dash]; - if (penstyle==PS_DASH || penstyle==PS_DASHDOT || penstyle==PS_DASHDOTDOT) { - d->dc[d->level].style.stroke_dash.dash[i++] = 3; - d->dc[d->level].style.stroke_dash.dash[i++] = 1; - } - if (penstyle==PS_DOT || penstyle==PS_DASHDOT || penstyle==PS_DASHDOTDOT) { - d->dc[d->level].style.stroke_dash.dash[i++] = 1; - d->dc[d->level].style.stroke_dash.dash[i++] = 1; - } - if (penstyle==PS_DASHDOTDOT) { - d->dc[d->level].style.stroke_dash.dash[i++] = 1; - d->dc[d->level].style.stroke_dash.dash[i++] = 1; - } - - d->dc[d->level].style.stroke_dasharray_set = 1; - break; - } - - case PS_SOLID: - default: - { - d->dc[d->level].style.stroke_dasharray_set = 0; - break; - } - } - - switch (pEmr->lopn.lopnStyle & PS_ENDCAP_MASK) { - case PS_ENDCAP_ROUND: - { - d->dc[d->level].style.stroke_linecap.computed = 1; - break; - } - case PS_ENDCAP_SQUARE: - { - d->dc[d->level].style.stroke_linecap.computed = 2; - break; - } - case PS_ENDCAP_FLAT: - default: - { - d->dc[d->level].style.stroke_linecap.computed = 0; - break; - } - } - - switch (pEmr->lopn.lopnStyle & PS_JOIN_MASK) { - case PS_JOIN_BEVEL: - { - d->dc[d->level].style.stroke_linejoin.computed = 2; - break; - } - case PS_JOIN_MITER: - { - d->dc[d->level].style.stroke_linejoin.computed = 0; - break; - } - case PS_JOIN_ROUND: - default: - { - d->dc[d->level].style.stroke_linejoin.computed = 1; - break; - } - } - - d->dc[d->level].stroke_set = true; - - if (pEmr->lopn.lopnStyle == 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_size_point( 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_size_point( 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( GetRValue(pEmr->lopn.lopnColor) ); - g = SP_COLOR_U_TO_F( GetGValue(pEmr->lopn.lopnColor) ); - b = SP_COLOR_U_TO_F( GetBValue(pEmr->lopn.lopnColor) ); - d->dc[d->level].style.stroke.value.color.set( r, g, b ); -} - - -static void -select_extpen(PEMF_CALLBACK_DATA d, int index) -{ - PEMREXTCREATEPEN pEmr = NULL; - - if (index >= 0 && index < d->n_obj) - pEmr = (PEMREXTCREATEPEN) d->emf_obj[index].lpEMFR; - - if (!pEmr) - return; - - switch (pEmr->elp.elpPenStyle & PS_STYLE_MASK) { - case PS_USERSTYLE: - { - if (pEmr->elp.elpNumEntries) { - d->dc[d->level].style.stroke_dash.n_dash = pEmr->elp.elpNumEntries; - if (d->dc[d->level].style.stroke_dash.dash && (d->level==0 || (d->level>0 && d->dc[d->level].style.stroke_dash.dash!=d->dc[d->level-1].style.stroke_dash.dash))) - delete[] d->dc[d->level].style.stroke_dash.dash; - d->dc[d->level].style.stroke_dash.dash = new double[pEmr->elp.elpNumEntries]; - for (unsigned int i=0; i<pEmr->elp.elpNumEntries; i++) { - int cur_level = d->level; - d->level = d->emf_obj[index].level; - double dash_length = pix_to_size_point( d, pEmr->elp.elpStyleEntry[i] ); - d->level = cur_level; - d->dc[d->level].style.stroke_dash.dash[i] = dash_length; - } - d->dc[d->level].style.stroke_dasharray_set = 1; - } else { - d->dc[d->level].style.stroke_dasharray_set = 0; - } - break; - } - - case PS_DASH: - case PS_DOT: - case PS_DASHDOT: - case PS_DASHDOTDOT: - { - int i = 0; - int penstyle = (pEmr->elp.elpPenStyle & PS_STYLE_MASK); - d->dc[d->level].style.stroke_dash.n_dash = - penstyle == PS_DASHDOTDOT ? 6 : penstyle == PS_DASHDOT ? 4 : 2; - if (d->dc[d->level].style.stroke_dash.dash && (d->level==0 || (d->level>0 && d->dc[d->level].style.stroke_dash.dash!=d->dc[d->level-1].style.stroke_dash.dash))) - delete[] d->dc[d->level].style.stroke_dash.dash; - d->dc[d->level].style.stroke_dash.dash = new double[d->dc[d->level].style.stroke_dash.n_dash]; - if (penstyle==PS_DASH || penstyle==PS_DASHDOT || penstyle==PS_DASHDOTDOT) { - d->dc[d->level].style.stroke_dash.dash[i++] = 3; - d->dc[d->level].style.stroke_dash.dash[i++] = 2; - } - if (penstyle==PS_DOT || penstyle==PS_DASHDOT || penstyle==PS_DASHDOTDOT) { - d->dc[d->level].style.stroke_dash.dash[i++] = 1; - d->dc[d->level].style.stroke_dash.dash[i++] = 2; - } - if (penstyle==PS_DASHDOTDOT) { - d->dc[d->level].style.stroke_dash.dash[i++] = 1; - d->dc[d->level].style.stroke_dash.dash[i++] = 2; - } - - d->dc[d->level].style.stroke_dasharray_set = 1; - break; - } - - case PS_SOLID: - default: - { - d->dc[d->level].style.stroke_dasharray_set = 0; - break; - } - } - - switch (pEmr->elp.elpPenStyle & PS_ENDCAP_MASK) { - case PS_ENDCAP_ROUND: - { - d->dc[d->level].style.stroke_linecap.computed = 1; - break; - } - case PS_ENDCAP_SQUARE: - { - d->dc[d->level].style.stroke_linecap.computed = 2; - break; - } - case PS_ENDCAP_FLAT: - default: - { - d->dc[d->level].style.stroke_linecap.computed = 0; - break; - } - } - - switch (pEmr->elp.elpPenStyle & PS_JOIN_MASK) { - case PS_JOIN_BEVEL: - { - d->dc[d->level].style.stroke_linejoin.computed = 2; - break; - } - case PS_JOIN_MITER: - { - d->dc[d->level].style.stroke_linejoin.computed = 0; - break; - } - case PS_JOIN_ROUND: - default: - { - d->dc[d->level].style.stroke_linejoin.computed = 1; - break; - } - } - - d->dc[d->level].stroke_set = true; - - if (pEmr->elp.elpPenStyle == PS_NULL) { - d->dc[d->level].style.stroke_width.value = 0; - d->dc[d->level].stroke_set = false; - } else if (pEmr->elp.elpWidth) { - int cur_level = d->level; - d->level = d->emf_obj[index].level; - double pen_width = pix_to_size_point( 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_size_point( 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( GetRValue(pEmr->elp.elpColor) ); - g = SP_COLOR_U_TO_F( GetGValue(pEmr->elp.elpColor) ); - b = SP_COLOR_U_TO_F( GetBValue(pEmr->elp.elpColor) ); - - d->dc[d->level].style.stroke.value.color.set( r, g, b ); -} - - -static void -select_brush(PEMF_CALLBACK_DATA d, int index) -{ - PEMRCREATEBRUSHINDIRECT pEmr = NULL; - - if (index >= 0 && index < d->n_obj) - pEmr = (PEMRCREATEBRUSHINDIRECT) d->emf_obj[index].lpEMFR; - - if (!pEmr) - return; - - if (pEmr->lb.lbStyle == BS_SOLID) { - double r, g, b; - r = SP_COLOR_U_TO_F( GetRValue(pEmr->lb.lbColor) ); - g = SP_COLOR_U_TO_F( GetGValue(pEmr->lb.lbColor) ); - b = SP_COLOR_U_TO_F( GetBValue(pEmr->lb.lbColor) ); - d->dc[d->level].style.fill.value.color.set( r, g, b ); - } - - d->dc[d->level].fill_set = true; -} - - -static void -select_font(PEMF_CALLBACK_DATA d, int index) -{ - PEMREXTCREATEFONTINDIRECTW pEmr = NULL; - - if (index >= 0 && index < d->n_obj) - pEmr = (PEMREXTCREATEFONTINDIRECTW) d->emf_obj[index].lpEMFR; - - if (!pEmr) - return; - - int cur_level = d->level; - d->level = d->emf_obj[index].level; - double font_size = pix_to_size_point( d, pEmr->elfw.elfLogFont.lfHeight ); - 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 == FW_THIN ? SP_CSS_FONT_WEIGHT_100 : - pEmr->elfw.elfLogFont.lfWeight == FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_200 : - pEmr->elfw.elfLogFont.lfWeight == FW_LIGHT ? SP_CSS_FONT_WEIGHT_300 : - pEmr->elfw.elfLogFont.lfWeight == FW_NORMAL ? SP_CSS_FONT_WEIGHT_400 : - pEmr->elfw.elfLogFont.lfWeight == FW_MEDIUM ? SP_CSS_FONT_WEIGHT_500 : - pEmr->elfw.elfLogFont.lfWeight == FW_SEMIBOLD ? SP_CSS_FONT_WEIGHT_600 : - pEmr->elfw.elfLogFont.lfWeight == FW_BOLD ? SP_CSS_FONT_WEIGHT_700 : - pEmr->elfw.elfLogFont.lfWeight == FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_800 : - pEmr->elfw.elfLogFont.lfWeight == FW_HEAVY ? SP_CSS_FONT_WEIGHT_900 : - pEmr->elfw.elfLogFont.lfWeight == FW_NORMAL ? SP_CSS_FONT_WEIGHT_NORMAL : - pEmr->elfw.elfLogFont.lfWeight == FW_BOLD ? SP_CSS_FONT_WEIGHT_BOLD : - pEmr->elfw.elfLogFont.lfWeight == FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_LIGHTER : - pEmr->elfw.elfLogFont.lfWeight == FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_BOLDER : - FW_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.underline = pEmr->elfw.elfLogFont.lfUnderline; - d->dc[d->level].style.text_decoration.line_through = pEmr->elfw.elfLogFont.lfStrikeOut; - if (d->dc[d->level].tstyle.font_family.value) - g_free(d->dc[d->level].tstyle.font_family.value); - d->dc[d->level].tstyle.font_family.value = - (gchar *) g_utf16_to_utf8( (gunichar2*) pEmr->elfw.elfLogFont.lfFaceName, -1, NULL, NULL, NULL ); - d->dc[d->level].style.baseline_shift.value = ((pEmr->elfw.elfLogFont.lfEscapement + 3600) % 3600) / 10; // use baseline_shift instead of text_transform to avoid overflow -} - -static void -delete_object(PEMF_CALLBACK_DATA d, int index) -{ - if (index >= 0 && index < d->n_obj) { - d->emf_obj[index].type = 0; - if (d->emf_obj[index].lpEMFR) - free(d->emf_obj[index].lpEMFR); - d->emf_obj[index].lpEMFR = NULL; - } -} - - -static void -insert_object(PEMF_CALLBACK_DATA d, int index, int type, 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 = pObj; - } -} - -static void -assert_empty_path(PEMF_CALLBACK_DATA d, const char * /*fun*/) -{ - if (!d->path->empty()) { - // g_debug("emf-win32-inout: assert_empty_path failed for %s\n", fun); - - *(d->outsvg) += "<!--\n"; - *(d->outsvg) += " <path \t"; - output_style(d, EMR_STROKEPATH); - if (strstr(d->path->c_str(), "d=\"") == NULL) { - *(d->outsvg) += "d=\""; - *(d->outsvg) += "\n\t"; - } - *(d->outsvg) += *(d->path); - *(d->outsvg) += " \" /> \n"; - *(d->outsvg) += "-->\n"; - - *(d->path) = ""; - } -} - - -static int CALLBACK -myEnhMetaFileProc(HDC /*hDC*/, HANDLETABLE * /*lpHTable*/, ENHMETARECORD const *lpEMFR, int /*nObj*/, LPARAM lpData) -{ - PEMF_CALLBACK_DATA d; - SVGOStringStream tmp_outsvg; - SVGOStringStream tmp_path; - SVGOStringStream tmp_str; - SVGOStringStream dbg_str; - - d = (PEMF_CALLBACK_DATA) lpData; - - if (d->pathless_stroke) { - if (lpEMFR->iType!=EMR_POLYBEZIERTO && lpEMFR->iType!=EMR_POLYBEZIERTO16 && - lpEMFR->iType!=EMR_POLYLINETO && lpEMFR->iType!=EMR_POLYLINETO16 && - lpEMFR->iType!=EMR_LINETO && lpEMFR->iType!=EMR_ARCTO && - lpEMFR->iType!=EMR_SETBKCOLOR && lpEMFR->iType!=EMR_SETROP2 && - lpEMFR->iType!=EMR_SETBKMODE && lpEMFR->iType!=EMR_BEGINPATH) - { - *(d->outsvg) += " <path "; - output_style(d, EMR_STROKEPATH); - *(d->outsvg) += "\n\t"; - *(d->outsvg) += *(d->path); - *(d->outsvg) += " \" /> \n"; - *(d->path) = ""; - d->pathless_stroke = false; - } - } - - switch (lpEMFR->iType) - { - case EMR_HEADER: - { - dbg_str << "<!-- 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"; - } - - ENHMETAHEADER *pEmr = (ENHMETAHEADER *) 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 << " version=\"1.0\"\n"; - - d->xDPI = 2540; - d->yDPI = 2540; - - d->dc[d->level].PixelsInX = pEmr->rclFrame.right; // - pEmr->rclFrame.left; - d->dc[d->level].PixelsInY = pEmr->rclFrame.bottom; // - pEmr->rclFrame.top; - - d->MMX = d->dc[d->level].PixelsInX / 100.0; - d->MMY = d->dc[d->level].PixelsInY / 100.0; - - d->dc[d->level].PixelsOutX = d->MMX * PX_PER_MM; - d->dc[d->level].PixelsOutY = d->MMY * PX_PER_MM; - - // calculate ratio of Inkscape dpi/device dpi - if (pEmr->szlMillimeters.cx && pEmr->szlDevice.cx) - device_scale = PX_PER_MM*pEmr->szlMillimeters.cx/pEmr->szlDevice.cx; - - 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 - - tmp_outsvg << "\n</defs>\n<g>\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 != NULL ) - { - for( int i=0; i < d->n_obj; ++i ) - d->emf_obj[i].lpEMFR = NULL; - } //if - - } else { - d->emf_obj = NULL; - } - - break; - } - case EMR_POLYBEZIER: - { - dbg_str << "<!-- EMR_POLYBEZIER -->\n"; - - PEMRPOLYBEZIER pEmr = (PEMRPOLYBEZIER) lpEMFR; - DWORD i,j; - - if (pEmr->cptl<4) - break; - - if (!d->inpath) { - assert_empty_path(d, "EMR_POLYBEZIER"); - - *(d->outsvg) += " <path "; - output_style(d, EMR_STROKEPATH); - *(d->outsvg) += "\n\td=\""; - } - - tmp_str << - "\n\tM " << - pix_to_x_point( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " " << - pix_to_y_point( 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_x_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " " << - pix_to_y_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; - } - } - - if (d->inpath) { - tmp_path << tmp_str.str().c_str(); - } - else { - *(d->outsvg) += tmp_str.str().c_str(); - *(d->outsvg) += " \" /> \n"; - } - - break; - } - case EMR_POLYGON: - { - dbg_str << "<!-- EMR_POLYGON -->\n"; - - EMRPOLYGON *pEmr = (EMRPOLYGON *) lpEMFR; - DWORD i; - - if (pEmr->cptl < 2) - break; - - assert_empty_path(d, "EMR_POLYGON"); - - *(d->outsvg) += " <path "; - output_style(d, EMR_STROKEANDFILLPATH); - *(d->outsvg) += "\n\td=\""; - - tmp_str << - "\n\tM " << - pix_to_x_point( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " " << - pix_to_y_point( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " "; - - for (i=1; i<pEmr->cptl; i++) { - tmp_str << - "\n\tL " << - pix_to_x_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " " << - pix_to_y_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; - } - - *(d->outsvg) += tmp_str.str().c_str(); - *(d->outsvg) += " z \" /> \n"; - - break; - } - case EMR_POLYLINE: - { - dbg_str << "<!-- EMR_POLYLINE -->\n"; - - EMRPOLYLINE *pEmr = (EMRPOLYLINE *) lpEMFR; - DWORD i; - - if (pEmr->cptl<2) - break; - - if (!d->inpath) { - assert_empty_path(d, "EMR_POLYLINE"); - - *(d->outsvg) += " <path "; - output_style(d, EMR_STROKEPATH); - *(d->outsvg) += "\n\td=\""; - } - - tmp_str << - "\n\tM " << - pix_to_x_point( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " " << - pix_to_y_point( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " "; - - for (i=1; i<pEmr->cptl; i++) { - tmp_str << - "\n\tL " << - pix_to_x_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " " << - pix_to_y_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; - } - - if (d->inpath) { - tmp_path << tmp_str.str().c_str(); - } - else { - *(d->outsvg) += tmp_str.str().c_str(); - *(d->outsvg) += " \" /> \n"; - } - - break; - } - case EMR_POLYBEZIERTO: - { - dbg_str << "<!-- EMR_POLYBEZIERTO -->\n"; - - PEMRPOLYBEZIERTO pEmr = (PEMRPOLYBEZIERTO) lpEMFR; - DWORD i,j; - - if (d->path->empty()) { - d->pathless_stroke = true; - *(d->path) = "d=\""; - } - - for (i=0; i<pEmr->cptl;) { - tmp_path << "\n\tC "; - for (j=0; j<3 && i<pEmr->cptl; j++,i++) { - tmp_path << - pix_to_x_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " " << - pix_to_y_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; - } - } - - break; - } - case EMR_POLYLINETO: - { - dbg_str << "<!-- EMR_POLYLINETO -->\n"; - - PEMRPOLYLINETO pEmr = (PEMRPOLYLINETO) lpEMFR; - DWORD i; - - if (d->path->empty()) { - d->pathless_stroke = true; - *(d->path) = "d=\""; - } - - for (i=0; i<pEmr->cptl;i++) { - tmp_path << - "\n\tL " << - pix_to_x_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " " << - pix_to_y_point( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " "; - } - - break; - } - case EMR_POLYPOLYLINE: - case EMR_POLYPOLYGON: - { - if (lpEMFR->iType == EMR_POLYPOLYLINE) - dbg_str << "<!-- EMR_POLYPOLYLINE -->\n"; - if (lpEMFR->iType == EMR_POLYPOLYGON) - dbg_str << "<!-- EMR_POLYPOLYGON -->\n"; - - PEMRPOLYPOLYGON pEmr = (PEMRPOLYPOLYGON) lpEMFR; - unsigned int n, i, j; - - if (!d->inpath) { - assert_empty_path(d, lpEMFR->iType == EMR_POLYPOLYGON ? "EMR_POLYPOLYGON" : "EMR_POLYPOLYLINE"); - - *(d->outsvg) += " <path "; - output_style(d, lpEMFR->iType==EMR_POLYPOLYGON ? EMR_STROKEANDFILLPATH : EMR_STROKEPATH); - *(d->outsvg) += "\n\td=\""; - } - - POINTL *aptl = (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_x_point( d, aptl[i].x, aptl[i].y ) << " " << - pix_to_y_point( 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_x_point( d, aptl[i].x, aptl[i].y ) << " " << - pix_to_y_point( d, aptl[i].x, aptl[i].y ) << " "; - i++; - } - - tmp_str << poly_path.str().c_str(); - if (lpEMFR->iType == EMR_POLYPOLYGON) - tmp_str << " z"; - tmp_str << " \n"; - } - - if (d->inpath) { - tmp_path << tmp_str.str().c_str(); - } - else { - *(d->outsvg) += tmp_str.str().c_str(); - *(d->outsvg) += " \" /> \n"; - } - - break; - } - case EMR_SETWINDOWEXTEX: - { - dbg_str << "<!-- EMR_SETWINDOWEXTEX -->\n"; - - PEMRSETWINDOWEXTEX pEmr = (PEMRSETWINDOWEXTEX) 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->dc[d->level].PixelsOutX; - d->dc[d->level].sizeWnd.cy = d->dc[d->level].PixelsOutY; - } - } - - if (!d->dc[d->level].sizeView.cx || !d->dc[d->level].sizeView.cy) { - d->dc[d->level].sizeView = d->dc[d->level].sizeWnd; - } - - d->dc[d->level].PixelsInX = d->dc[d->level].sizeWnd.cx; - d->dc[d->level].PixelsInY = d->dc[d->level].sizeWnd.cy; - - if (d->dc[d->level].PixelsInX && d->dc[d->level].PixelsInY) { - d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].PixelsInX; - d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].PixelsInY; - } - else { - d->dc[d->level].ScaleInX = 1; - d->dc[d->level].ScaleInY = 1; - } - - break; - } - case EMR_SETWINDOWORGEX: - { - dbg_str << "<!-- EMR_SETWINDOWORGEX -->\n"; - - PEMRSETWINDOWORGEX pEmr = (PEMRSETWINDOWORGEX) lpEMFR; - d->dc[d->level].winorg = pEmr->ptlOrigin; - break; - } - case EMR_SETVIEWPORTEXTEX: - { - dbg_str << "<!-- EMR_SETVIEWPORTEXTEX -->\n"; - - PEMRSETVIEWPORTEXTEX pEmr = (PEMRSETVIEWPORTEXTEX) 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->dc[d->level].PixelsOutX; - d->dc[d->level].sizeView.cy = d->dc[d->level].PixelsOutY; - } - } - - if (!d->dc[d->level].sizeWnd.cx || !d->dc[d->level].sizeWnd.cy) { - d->dc[d->level].sizeWnd = d->dc[d->level].sizeView; - } - - d->dc[d->level].PixelsInX = d->dc[d->level].sizeWnd.cx; - d->dc[d->level].PixelsInY = d->dc[d->level].sizeWnd.cy; - - if (d->dc[d->level].PixelsInX && d->dc[d->level].PixelsInY) { - d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].PixelsInX; - d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].PixelsInY; - } - else { - d->dc[d->level].ScaleInX = 1; - d->dc[d->level].ScaleInY = 1; - } - - break; - } - case EMR_SETVIEWPORTORGEX: - { - dbg_str << "<!-- EMR_SETVIEWPORTORGEX -->\n"; - - PEMRSETVIEWPORTORGEX pEmr = (PEMRSETVIEWPORTORGEX) lpEMFR; - d->dc[d->level].vieworg = pEmr->ptlOrigin; - break; - } - case EMR_SETBRUSHORGEX: - dbg_str << "<!-- EMR_SETBRUSHORGEX -->\n"; - break; - case EMR_EOF: - { - dbg_str << "<!-- EMR_EOF -->\n"; - - assert_empty_path(d, "EMR_EOF"); - tmp_outsvg << "</g>\n"; - tmp_outsvg << "</svg>\n"; - *(d->outsvg) = *(d->outdef) + *(d->outsvg); - break; - } - case EMR_SETPIXELV: - dbg_str << "<!-- EMR_SETPIXELV -->\n"; - break; - case EMR_SETMAPPERFLAGS: - dbg_str << "<!-- EMR_SETMAPPERFLAGS -->\n"; - break; - case EMR_SETMAPMODE: - dbg_str << "<!-- EMR_SETMAPMODE -->\n"; - break; - case EMR_SETBKMODE: - dbg_str << "<!-- EMR_SETBKMODE -->\n"; - break; - case EMR_SETPOLYFILLMODE: - { - dbg_str << "<!-- EMR_SETPOLYFILLMODE -->\n"; - - PEMRSETPOLYFILLMODE pEmr = (PEMRSETPOLYFILLMODE) lpEMFR; - d->dc[d->level].style.fill_rule.value = - (pEmr->iMode == ALTERNATE ? 0 : - pEmr->iMode == WINDING ? 1 : 0); - break; - } - case EMR_SETROP2: - dbg_str << "<!-- EMR_SETROP2 -->\n"; - break; - case EMR_SETSTRETCHBLTMODE: - dbg_str << "<!-- EMR_SETSTRETCHBLTMODE -->\n"; - break; - case EMR_SETTEXTALIGN: - { - dbg_str << "<!-- EMR_SETTEXTALIGN -->\n"; - - PEMRSETTEXTALIGN pEmr = (PEMRSETTEXTALIGN) lpEMFR; - d->dc[d->level].textAlign = pEmr->iMode; - break; - } - case EMR_SETCOLORADJUSTMENT: - dbg_str << "<!-- EMR_SETCOLORADJUSTMENT -->\n"; - break; - case EMR_SETTEXTCOLOR: - { - dbg_str << "<!-- EMR_SETTEXTCOLOR -->\n"; - - PEMRSETTEXTCOLOR pEmr = (PEMRSETTEXTCOLOR) lpEMFR; - d->dc[d->level].textColor = pEmr->crColor; - d->dc[d->level].textColorSet = true; - break; - } - case EMR_SETBKCOLOR: - dbg_str << "<!-- EMR_SETBKCOLOR -->\n"; - break; - case EMR_OFFSETCLIPRGN: - dbg_str << "<!-- EMR_OFFSETCLIPRGN -->\n"; - break; - case EMR_MOVETOEX: - { - dbg_str << "<!-- EMR_MOVETOEX -->\n"; - - PEMRMOVETOEX pEmr = (PEMRMOVETOEX) lpEMFR; - - if (d->path->empty()) { - d->pathless_stroke = true; - *(d->path) = "d=\""; - } - - d->dc[d->level].cur = pEmr->ptl; - - tmp_path << - "\n\tM " << - pix_to_x_point( d, pEmr->ptl.x, pEmr->ptl.y ) << " " << - pix_to_y_point( d, pEmr->ptl.x, pEmr->ptl.y ) << " "; - break; - } - case EMR_SETMETARGN: - dbg_str << "<!-- EMR_SETMETARGN -->\n"; - break; - case EMR_EXCLUDECLIPRECT: - dbg_str << "<!-- EMR_EXCLUDECLIPRECT -->\n"; - break; - case EMR_INTERSECTCLIPRECT: - { - dbg_str << "<!-- EMR_INTERSECTCLIPRECT -->\n"; - - PEMRINTERSECTCLIPRECT pEmr = (PEMRINTERSECTCLIPRECT) lpEMFR; - RECTL rc = pEmr->rclClip; - clipset = true; - if ((rc.left == rc_old.left) && (rc.top == rc_old.top) && (rc.right == rc_old.right) && (rc.bottom == rc_old.bottom)) - break; - rc_old = rc; - - double l = pix_to_x_point( d, rc.left, rc.top ); - double t = pix_to_y_point( d, rc.left, rc.top ); - double r = pix_to_x_point( d, rc.right, rc.bottom ); - double b = pix_to_y_point( d, rc.right, rc.bottom ); - - SVGOStringStream tmp_rectangle; - tmp_rectangle << "\n<clipPath\n\tclipPathUnits=\"userSpaceOnUse\" "; - tmp_rectangle << "\n\tid=\"clipEmfPath" << ++(d->id) << "\" >"; - tmp_rectangle << "\n<rect "; - tmp_rectangle << "\n\tx=\"" << l << "\" "; - tmp_rectangle << "\n\ty=\"" << t << "\" "; - tmp_rectangle << "\n\twidth=\"" << r-l << "\" "; - tmp_rectangle << "\n\theight=\"" << b-t << "\" />"; - tmp_rectangle << "\n</clipPath>"; - - assert_empty_path(d, "EMR_RECTANGLE"); - - *(d->outdef) += tmp_rectangle.str().c_str(); - *(d->path) = ""; - break; - } - case EMR_SCALEVIEWPORTEXTEX: - dbg_str << "<!-- EMR_SCALEVIEWPORTEXTEX -->\n"; - break; - case EMR_SCALEWINDOWEXTEX: - dbg_str << "<!-- EMR_SCALEWINDOWEXTEX -->\n"; - break; - case EMR_SAVEDC: - dbg_str << "<!-- EMR_SAVEDC -->\n"; - - if (d->level < EMF_MAX_DC) { - d->dc[d->level + 1] = d->dc[d->level]; - d->level = d->level + 1; - } - break; - case EMR_RESTOREDC: - { - dbg_str << "<!-- EMR_RESTOREDC -->\n"; - - PEMRRESTOREDC pEmr = (PEMRRESTOREDC) 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_dash.dash && (old_level==0 || (old_level>0 && d->dc[old_level].style.stroke_dash.dash!=d->dc[old_level-1].style.stroke_dash.dash))) - delete[] d->dc[old_level].style.stroke_dash.dash; - old_level--; - } - break; - } - case EMR_SETWORLDTRANSFORM: - { - dbg_str << "<!-- EMR_SETWORLDTRANSFORM -->\n"; - - PEMRSETWORLDTRANSFORM pEmr = (PEMRSETWORLDTRANSFORM) lpEMFR; - d->dc[d->level].worldTransform = pEmr->xform; - break; - } - case EMR_MODIFYWORLDTRANSFORM: - { - dbg_str << "<!-- EMR_MODIFYWORLDTRANSFORM -->\n"; - - PEMRMODIFYWORLDTRANSFORM pEmr = (PEMRMODIFYWORLDTRANSFORM) lpEMFR; - switch (pEmr->iMode) - { - case 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 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 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 EMR_SELECTOBJECT: - { - dbg_str << "<!-- EMR_SELECTOBJECT -->\n"; - - PEMRSELECTOBJECT pEmr = (PEMRSELECTOBJECT) lpEMFR; - unsigned int index = pEmr->ihObject; - - if (index >= ENHMETA_STOCK_OBJECT) { - index -= ENHMETA_STOCK_OBJECT; - switch (index) { - case NULL_BRUSH: - d->dc[d->level].fill_set = false; - break; - case BLACK_BRUSH: - case DKGRAY_BRUSH: - case GRAY_BRUSH: - case LTGRAY_BRUSH: - case WHITE_BRUSH: - { - float val = 0; - switch (index) { - case BLACK_BRUSH: - val = 0.0 / 255.0; - break; - case DKGRAY_BRUSH: - val = 64.0 / 255.0; - break; - case GRAY_BRUSH: - val = 128.0 / 255.0; - break; - case LTGRAY_BRUSH: - val = 192.0 / 255.0; - break; - case 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_set = true; - break; - } - case NULL_PEN: - d->dc[d->level].stroke_set = false; - break; - case BLACK_PEN: - case WHITE_PEN: - { - float val = index == BLACK_PEN ? 0 : 1; - d->dc[d->level].style.stroke_dasharray_set = 0; - 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_set = true; - - break; - } - } - } else { - if ( /*index >= 0 &&*/ index < (unsigned int) d->n_obj) { - switch (d->emf_obj[index].type) - { - case EMR_CREATEPEN: - select_pen(d, index); - break; - case EMR_CREATEBRUSHINDIRECT: - select_brush(d, index); - break; - case EMR_EXTCREATEPEN: - select_extpen(d, index); - break; - case EMR_EXTCREATEFONTINDIRECTW: - select_font(d, index); - break; - } - } - } - break; - } - case EMR_CREATEPEN: - { - dbg_str << "<!-- EMR_CREATEPEN -->\n"; - - PEMRCREATEPEN pEmr = (PEMRCREATEPEN) lpEMFR; - int index = pEmr->ihPen; - - EMRCREATEPEN *pPen = - (EMRCREATEPEN *) malloc( sizeof(EMRCREATEPEN) ); - pPen->lopn = pEmr->lopn; - insert_object(d, index, EMR_CREATEPEN, (ENHMETARECORD *) pPen); - - break; - } - case EMR_CREATEBRUSHINDIRECT: - { - dbg_str << "<!-- EMR_CREATEBRUSHINDIRECT -->\n"; - - PEMRCREATEBRUSHINDIRECT pEmr = (PEMRCREATEBRUSHINDIRECT) lpEMFR; - int index = pEmr->ihBrush; - - EMRCREATEBRUSHINDIRECT *pBrush = - (EMRCREATEBRUSHINDIRECT *) malloc( sizeof(EMRCREATEBRUSHINDIRECT) ); - pBrush->lb = pEmr->lb; - insert_object(d, index, EMR_CREATEBRUSHINDIRECT, (ENHMETARECORD *) pBrush); - - break; - } - case EMR_DELETEOBJECT: - dbg_str << "<!-- EMR_DELETEOBJECT -->\n"; - break; - case EMR_ANGLEARC: - dbg_str << "<!-- EMR_ANGLEARC -->\n"; - break; - case EMR_ELLIPSE: - { - dbg_str << "<!-- EMR_ELLIPSE -->\n"; - - PEMRELLIPSE pEmr = (PEMRELLIPSE) lpEMFR; - RECTL rclBox = pEmr->rclBox; - - double l = pix_to_x_point( d, pEmr->rclBox.left, pEmr->rclBox.top ); - double t = pix_to_y_point( d, pEmr->rclBox.left, pEmr->rclBox.top ); - double r = pix_to_x_point( d, pEmr->rclBox.right, pEmr->rclBox.bottom ); - double b = pix_to_y_point( d, pEmr->rclBox.right, pEmr->rclBox.bottom ); - - double cx = (l + r) / 2.0; - double cy = (t + b) / 2.0; - double rx = fabs(l - r) / 2.0; - double ry = fabs(t - b) / 2.0; - - SVGOStringStream tmp_ellipse; - tmp_ellipse << "cx=\"" << cx << "\" "; - tmp_ellipse << "cy=\"" << cy << "\" "; - tmp_ellipse << "rx=\"" << rx << "\" "; - tmp_ellipse << "ry=\"" << ry << "\" "; - - assert_empty_path(d, "EMR_ELLIPSE"); - - *(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 EMR_RECTANGLE: - { - dbg_str << "<!-- EMR_RECTANGLE -->\n"; - - PEMRRECTANGLE pEmr = (PEMRRECTANGLE) lpEMFR; - RECTL rc = pEmr->rclBox; - - double l = pix_to_x_point( d, rc.left, rc.top ); - double t = pix_to_y_point( d, rc.left, rc.top ); - double r = pix_to_x_point( d, rc.right, rc.bottom ); - double b = pix_to_y_point( d, rc.right, rc.bottom ); - - SVGOStringStream tmp_rectangle; - tmp_rectangle << "d=\""; - tmp_rectangle << "\n\tM " << l << " " << t << " "; - tmp_rectangle << "\n\tL " << r << " " << t << " "; - tmp_rectangle << "\n\tL " << r << " " << b << " "; - tmp_rectangle << "\n\tL " << l << " " << b << " "; - tmp_rectangle << "\n\tz"; - - assert_empty_path(d, "EMR_RECTANGLE"); - - *(d->outsvg) += " <path "; - output_style(d, lpEMFR->iType); - *(d->outsvg) += "\n\t"; - *(d->outsvg) += tmp_rectangle.str().c_str(); - *(d->outsvg) += " \" /> \n"; - *(d->path) = ""; - break; - } - case EMR_ROUNDRECT: - { - dbg_str << "<!-- EMR_ROUNDRECT -->\n"; - - PEMRROUNDRECT pEmr = (PEMRROUNDRECT) lpEMFR; - RECTL rc = pEmr->rclBox; - SIZEL corner = pEmr->szlCorner; - double f = 4.*(sqrt(2) - 1)/3; - - double l = pix_to_x_point(d, rc.left, rc.top); - double t = pix_to_y_point(d, rc.left, rc.top); - double r = pix_to_x_point(d, rc.right, rc.bottom); - double b = pix_to_y_point(d, rc.right, rc.bottom); - double cnx = pix_to_size_point(d, corner.cx/2); - double cny = pix_to_size_point(d, corner.cy/2); - - SVGOStringStream tmp_rectangle; - tmp_rectangle << "d=\""; - tmp_rectangle << "\n\tM " << l << ", " << t + cny << " "; - tmp_rectangle << "\n\tC " << l << ", " << t + (1-f)*cny << " " << l + (1-f)*cnx << ", " << t << " " << l + cnx << ", " << t << " "; - tmp_rectangle << "\n\tL " << r - cnx << ", " << t << " "; - tmp_rectangle << "\n\tC " << r - (1-f)*cnx << ", " << t << " " << r << ", " << t + (1-f)*cny << " " << r << ", " << t + cny << " "; - tmp_rectangle << "\n\tL " << r << ", " << b - cny << " "; - tmp_rectangle << "\n\tC " << r << ", " << b - (1-f)*cny << " " << r - (1-f)*cnx << ", " << b << " " << r - cnx << ", " << b << " "; - tmp_rectangle << "\n\tL " << l + cnx << ", " << b << " "; - tmp_rectangle << "\n\tC " << l + (1-f)*cnx << ", " << b << " " << l << ", " << b - (1-f)*cny << " " << l << ", " << b - cny << " "; - tmp_rectangle << "\n\tz"; - assert_empty_path(d, "EMR_ROUNDRECT"); - - *(d->outsvg) += " <path "; - output_style(d, lpEMFR->iType); - *(d->outsvg) += "\n\t"; - *(d->outsvg) += tmp_rectangle.str().c_str(); - *(d->outsvg) += " \" /> \n"; - *(d->path) = ""; - break; - } - case EMR_ARC: - dbg_str << "<!-- EMR_ARC -->\n"; - break; - case EMR_CHORD: - dbg_str << "<!-- EMR_CHORD -->\n"; - break; - case EMR_PIE: - dbg_str << "<!-- EMR_PIE -->\n"; - break; - case EMR_SELECTPALETTE: - dbg_str << "<!-- EMR_SELECTPALETTE -->\n"; - break; - case EMR_CREATEPALETTE: - dbg_str << "<!-- EMR_CREATEPALETTE -->\n"; - break; - case EMR_SETPALETTEENTRIES: - dbg_str << "<!-- EMR_SETPALETTEENTRIES -->\n"; - break; - case EMR_RESIZEPALETTE: - dbg_str << "<!-- EMR_RESIZEPALETTE -->\n"; - break; - case EMR_REALIZEPALETTE: - dbg_str << "<!-- EMR_REALIZEPALETTE -->\n"; - break; - case EMR_EXTFLOODFILL: - dbg_str << "<!-- EMR_EXTFLOODFILL -->\n"; - break; - case EMR_LINETO: - { - dbg_str << "<!-- EMR_LINETO -->\n"; - - PEMRLINETO pEmr = (PEMRLINETO) lpEMFR; - - if (d->path->empty()) { - d->pathless_stroke = true; - *(d->path) = "d=\""; - } - - tmp_path << - "\n\tL " << - pix_to_x_point( d, pEmr->ptl.x, pEmr->ptl.y ) << " " << - pix_to_y_point( d, pEmr->ptl.x, pEmr->ptl.y ) << " "; - break; - } - case EMR_ARCTO: - dbg_str << "<!-- EMR_ARCTO -->\n"; - break; - case EMR_POLYDRAW: - dbg_str << "<!-- EMR_POLYDRAW -->\n"; - break; - case EMR_SETARCDIRECTION: - dbg_str << "<!-- EMR_SETARCDIRECTION -->\n"; - break; - case EMR_SETMITERLIMIT: - { - dbg_str << "<!-- EMR_SETMITERLIMIT -->\n"; - - PEMRSETMITERLIMIT pEmr = (PEMRSETMITERLIMIT) lpEMFR; - - float miterlimit = pEmr->eMiterLimit; - miterlimit = miterlimit * 4.0 / 10.0; - d->dc[d->level].style.stroke_miterlimit.value = pix_to_size_point( d, miterlimit ); - if (d->dc[d->level].style.stroke_miterlimit.value < 1) - d->dc[d->level].style.stroke_miterlimit.value = 4.0; - break; - } - case EMR_BEGINPATH: - { - dbg_str << "<!-- EMR_BEGINPATH -->\n"; - - if (!d->pathless_stroke) { - tmp_path << "d=\""; - *(d->path) = ""; - } - d->pathless_stroke = false; - d->inpath = true; - break; - } - case EMR_ENDPATH: - { - dbg_str << "<!-- EMR_ENDPATH -->\n"; - - tmp_path << "\""; - d->inpath = false; - break; - } - case EMR_CLOSEFIGURE: - { - dbg_str << "<!-- EMR_CLOSEFIGURE -->\n"; - - tmp_path << "\n\tz"; - break; - } - case EMR_FILLPATH: - case EMR_STROKEANDFILLPATH: - case EMR_STROKEPATH: - { - if (lpEMFR->iType == EMR_FILLPATH) - dbg_str << "<!-- EMR_FILLPATH -->\n"; - if (lpEMFR->iType == EMR_STROKEANDFILLPATH) - dbg_str << "<!-- EMR_STROKEANDFILLPATH -->\n"; - if (lpEMFR->iType == EMR_STROKEPATH) - dbg_str << "<!-- EMR_STROKEPATH -->\n"; - - *(d->outsvg) += " <path "; - output_style(d, lpEMFR->iType); - *(d->outsvg) += "\n\t"; - *(d->outsvg) += *(d->path); - *(d->outsvg) += " /> \n"; - *(d->path) = ""; - break; - } - case EMR_FLATTENPATH: - dbg_str << "<!-- EMR_FLATTENPATH -->\n"; - break; - case EMR_WIDENPATH: - dbg_str << "<!-- EMR_WIDENPATH -->\n"; - break; - case EMR_SELECTCLIPPATH: - dbg_str << "<!-- EMR_SELECTCLIPPATH -->\n"; - break; - case EMR_ABORTPATH: - dbg_str << "<!-- EMR_ABORTPATH -->\n"; - break; - case EMR_GDICOMMENT: - { - dbg_str << "<!-- EMR_GDICOMMENT -->\n"; - - PEMRGDICOMMENT pEmr = (PEMRGDICOMMENT) lpEMFR; - - CHAR *szTxt = (CHAR *) pEmr->Data; - - for (DWORD i = 0; i < pEmr->cbData; i++) { - if ( *szTxt) { - if ( *szTxt >= ' ' && *szTxt < 'z' && *szTxt != '<' && *szTxt != '>' ) { - tmp_str << *szTxt; - } - szTxt++; - } - } - - if (0 && strlen(tmp_str.str().c_str())) { - tmp_outsvg << " <!-- \""; - tmp_outsvg << tmp_str.str().c_str(); - tmp_outsvg << "\" -->\n"; - } - - break; - } - case EMR_FILLRGN: - dbg_str << "<!-- EMR_FILLRGN -->\n"; - break; - case EMR_FRAMERGN: - dbg_str << "<!-- EMR_FRAMERGN -->\n"; - break; - case EMR_INVERTRGN: - dbg_str << "<!-- EMR_INVERTRGN -->\n"; - break; - case EMR_PAINTRGN: - dbg_str << "<!-- EMR_PAINTRGN -->\n"; - break; - case EMR_EXTSELECTCLIPRGN: - { - dbg_str << "<!-- EMR_EXTSELECTCLIPRGN -->\n"; - - PEMREXTSELECTCLIPRGN pEmr = (PEMREXTSELECTCLIPRGN) lpEMFR; - if (pEmr->iMode == RGN_COPY) - clipset = false; - break; - } - case EMR_BITBLT: - { - dbg_str << "<!-- EMR_BITBLT -->\n"; - - PEMRBITBLT pEmr = (PEMRBITBLT) lpEMFR; - if (pEmr->dwRop == DPA) { - // should be an application of a DIBPATTERNBRUSHPT, use a solid color instead - double l = pix_to_x_point( d, pEmr->xDest, pEmr->yDest); - double t = pix_to_y_point( d, pEmr->xDest, pEmr->yDest); - double r = pix_to_x_point( d, pEmr->xDest + pEmr->cxDest, pEmr->yDest + pEmr->cyDest); - double b = pix_to_y_point( d, pEmr->xDest + pEmr->cxDest, pEmr->yDest + pEmr->cyDest); - - SVGOStringStream tmp_rectangle; - tmp_rectangle << "d=\""; - tmp_rectangle << "\n\tM " << l << " " << t << " "; - tmp_rectangle << "\n\tL " << r << " " << t << " "; - tmp_rectangle << "\n\tL " << r << " " << b << " "; - tmp_rectangle << "\n\tL " << l << " " << b << " "; - tmp_rectangle << "\n\tz"; - - assert_empty_path(d, "EMR_BITBLT"); - - *(d->outsvg) += " <path "; - output_style(d, lpEMFR->iType); - *(d->outsvg) += "\n\t"; - *(d->outsvg) += tmp_rectangle.str().c_str(); - *(d->outsvg) += " \" /> \n"; - *(d->path) = ""; - } - break; - } - case EMR_STRETCHBLT: - dbg_str << "<!-- EMR_STRETCHBLT -->\n"; - break; - case EMR_MASKBLT: - dbg_str << "<!-- EMR_MASKBLT -->\n"; - break; - case EMR_PLGBLT: - dbg_str << "<!-- EMR_PLGBLT -->\n"; - break; - case EMR_SETDIBITSTODEVICE: - dbg_str << "<!-- EMR_SETDIBITSTODEVICE -->\n"; - break; - case EMR_STRETCHDIBITS: - dbg_str << "<!-- EMR_STRETCHDIBITS -->\n"; - break; - case EMR_EXTCREATEFONTINDIRECTW: - { - dbg_str << "<!-- EMR_EXTCREATEFONTINDIRECTW -->\n"; - - PEMREXTCREATEFONTINDIRECTW pEmr = (PEMREXTCREATEFONTINDIRECTW) lpEMFR; - int index = pEmr->ihFont; - - EMREXTCREATEFONTINDIRECTW *pFont = - (EMREXTCREATEFONTINDIRECTW *) malloc( sizeof(EMREXTCREATEFONTINDIRECTW) ); - pFont->elfw = pEmr->elfw; - insert_object(d, index, EMR_EXTCREATEFONTINDIRECTW, (ENHMETARECORD *) pFont); - break; - } - case EMR_EXTTEXTOUTA: - { - dbg_str << "<!-- EMR_EXTTEXTOUTA -->\n"; - break; - } - case EMR_EXTTEXTOUTW: - { - dbg_str << "<!-- EMR_EXTTEXTOUTW -->\n"; - - PEMREXTTEXTOUTW pEmr = (PEMREXTTEXTOUTW) lpEMFR; - - double x1 = pEmr->emrtext.ptlReference.x; - double y1 = pEmr->emrtext.ptlReference.y; - - if (d->dc[d->level].textAlign & 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); - - if (!(d->dc[d->level].textAlign & TA_BOTTOM)) - if (d->dc[d->level].style.baseline_shift.value) { - x += std::sin(d->dc[d->level].style.baseline_shift.value*M_PI/180.0)*fabs(d->dc[d->level].style.font_size.computed); - y += std::cos(d->dc[d->level].style.baseline_shift.value*M_PI/180.0)*fabs(d->dc[d->level].style.font_size.computed); - } - else - y += fabs(d->dc[d->level].style.font_size.computed); - - wchar_t *wide_text = (wchar_t *) ((char *) pEmr + pEmr->emrtext.offString); - - gchar *ansi_text = - (gchar *) g_utf16_to_utf8( (gunichar2 *) wide_text, pEmr->emrtext.nChars, NULL, NULL, NULL ); - - if (ansi_text) { -// gchar *p = ansi_text; -// while (*p) { -// if (*p < 32 || *p >= 127) { -// g_free(ansi_text); -// ansi_text = g_strdup(""); -// break; -// } -// p++; -// } - - SVGOStringStream ts; - - gchar *escaped_text = g_markup_escape_text(ansi_text, -1); - -// float text_rgb[3]; -// sp_color_get_rgb_floatv( &(d->dc[d->level].style.fill.value.color), text_rgb ); - -// if (!d->dc[d->level].textColorSet) { -// d->dc[d->level].textColor = RGB(SP_COLOR_F_TO_U(text_rgb[0]), -// SP_COLOR_F_TO_U(text_rgb[1]), -// SP_COLOR_F_TO_U(text_rgb[2])); -// } - - char tmp[128]; - snprintf(tmp, 127, - "fill:#%02x%02x%02x;", - GetRValue(d->dc[d->level].textColor), - GetGValue(d->dc[d->level].textColor), - GetBValue(d->dc[d->level].textColor)); - - bool i = (d->dc[d->level].style.font_style.value == SP_CSS_FONT_STYLE_ITALIC); - //bool o = (d->dc[d->level].style.font_style.value == SP_CSS_FONT_STYLE_OBLIQUE); - bool b = (d->dc[d->level].style.font_weight.value == SP_CSS_FONT_WEIGHT_BOLD) || - (d->dc[d->level].style.font_weight.value >= SP_CSS_FONT_WEIGHT_500 && d->dc[d->level].style.font_weight.value <= SP_CSS_FONT_WEIGHT_900); - int lcr = ((d->dc[d->level].textAlign & TA_CENTER) == TA_CENTER) ? 2 : ((d->dc[d->level].textAlign & TA_RIGHT) == TA_RIGHT) ? 1 : 0; - - assert_empty_path(d, "EMR_EXTTEXTOUTW"); - - ts << " <text\n"; -// ts << " id=\"" << (d->id++) << "\"\n"; - ts << " xml:space=\"preserve\"\n"; - ts << " x=\"" << x << "\"\n"; - ts << " y=\"" << y << "\"\n"; - if (d->dc[d->level].style.baseline_shift.value) { - ts << " transform=\"" - << "rotate(-" << d->dc[d->level].style.baseline_shift.value - << " " << x << " " << y << ")" - << "\"\n"; - } - ts << " style=\"" - << "font-size:" << fabs(d->dc[d->level].style.font_size.computed) << "px;" - << tmp - << "font-style:" << (i ? "italic" : "normal") << ";" - << "font-weight:" << (b ? "bold" : "normal") << ";" - << "text-align:" << (lcr==2 ? "center" : lcr==1 ? "end" : "start") << ";" - << "text-anchor:" << (lcr==2 ? "middle" : lcr==1 ? "end" : "start") << ";" - << "font-family:" << d->dc[d->level].tstyle.font_family.value << ";" - << "\"\n"; - ts << " >"; - ts << escaped_text; - ts << "</text>\n"; - - *(d->outsvg) += ts.str().c_str(); - - g_free(escaped_text); - g_free(ansi_text); - } - - break; - } - case EMR_POLYBEZIER16: - { - dbg_str << "<!-- EMR_POLYBEZIER16 -->\n"; - - PEMRPOLYBEZIER16 pEmr = (PEMRPOLYBEZIER16) lpEMFR; - POINTS *apts = (POINTS *) pEmr->apts; // Bug in MinGW wingdi.h ? - DWORD i,j; - - if (pEmr->cpts<4) - break; - - if (!d->inpath) { - assert_empty_path(d, "EMR_POLYBEZIER16"); - - *(d->outsvg) += " <path "; - output_style(d, EMR_STROKEPATH); - *(d->outsvg) += "\n\td=\""; - } - - tmp_str << - "\n\tM " << - pix_to_x_point( d, apts[0].x, apts[0].y ) << " " << - pix_to_y_point( 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_x_point( d, apts[i].x, apts[i].y ) << " " << - pix_to_y_point( d, apts[i].x, apts[i].y ) << " "; - } - } - - if (d->inpath) { - tmp_path << tmp_str.str().c_str(); - } - else { - *(d->outsvg) += tmp_str.str().c_str(); - *(d->outsvg) += " \" /> \n"; - } - - break; - } - case EMR_POLYGON16: - { - dbg_str << "<!-- EMR_POLYGON16 -->\n"; - - PEMRPOLYGON16 pEmr = (PEMRPOLYGON16) lpEMFR; - POINTS *apts = (POINTS *) pEmr->apts; // Bug in MinGW wingdi.h ? - SVGOStringStream tmp_poly; - unsigned int i; - unsigned int first = 0; - - assert_empty_path(d, "EMR_POLYGON16"); - - *(d->outsvg) += " <path "; - output_style(d, EMR_STROKEANDFILLPATH); - *(d->outsvg) += "\n\td=\""; - - // skip the first point? - tmp_poly << "\n\tM " << - pix_to_x_point( d, apts[first].x, apts[first].y ) << " " << - pix_to_y_point( d, apts[first].x, apts[first].y ) << " "; - - for (i=first+1; i<pEmr->cpts; i++) { - tmp_poly << "\n\tL " << - pix_to_x_point( d, apts[i].x, apts[i].y ) << " " << - pix_to_y_point( d, apts[i].x, apts[i].y ) << " "; - } - - *(d->outsvg) += tmp_poly.str().c_str(); - *(d->outsvg) += " z \" /> \n"; - - break; - } - case EMR_POLYLINE16: - { - dbg_str << "<!-- EMR_POLYLINE16 -->\n"; - - EMRPOLYLINE16 *pEmr = (EMRPOLYLINE16 *) lpEMFR; - POINTS *apts = (POINTS *) pEmr->apts; // Bug in MinGW wingdi.h ? - DWORD i; - - if (pEmr->cpts<2) - break; - - if (!d->inpath) { - assert_empty_path(d, "EMR_POLYLINE16"); - - *(d->outsvg) += " <path "; - output_style(d, EMR_STROKEPATH); - *(d->outsvg) += "\n\td=\""; - } - - tmp_str << - "\n\tM " << - pix_to_x_point( d, apts[0].x, apts[0].y ) << " " << - pix_to_y_point( d, apts[0].x, apts[0].y ) << " "; - - for (i=1; i<pEmr->cpts; i++) { - tmp_str << - "\n\tL " << - pix_to_x_point( d, apts[i].x, apts[i].y ) << " " << - pix_to_y_point( d, apts[i].x, apts[i].y ) << " "; - } - - if (d->inpath) { - tmp_path << tmp_str.str().c_str(); - } - else { - *(d->outsvg) += tmp_str.str().c_str(); - *(d->outsvg) += " \" /> \n"; - } - - break; - } - case EMR_POLYBEZIERTO16: - { - dbg_str << "<!-- EMR_POLYBEZIERTO16 -->\n"; - - PEMRPOLYBEZIERTO16 pEmr = (PEMRPOLYBEZIERTO16) lpEMFR; - POINTS *apts = (POINTS *) pEmr->apts; // Bug in MinGW wingdi.h ? - DWORD i,j; - - if (d->path->empty()) { - d->pathless_stroke = true; - *(d->path) = "d=\""; - } - - for (i=0; i<pEmr->cpts;) { - tmp_path << "\n\tC "; - for (j=0; j<3 && i<pEmr->cpts; j++,i++) { - tmp_path << - pix_to_x_point( d, apts[i].x, apts[i].y ) << " " << - pix_to_y_point( d, apts[i].x, apts[i].y ) << " "; - } - } - - break; - } - case EMR_POLYLINETO16: - { - dbg_str << "<!-- EMR_POLYLINETO16 -->\n"; - - PEMRPOLYLINETO16 pEmr = (PEMRPOLYLINETO16) lpEMFR; - POINTS *apts = (POINTS *) pEmr->apts; // Bug in MinGW wingdi.h ? - DWORD i; - - if (d->path->empty()) { - d->pathless_stroke = true; - *(d->path) = "d=\""; - } - - for (i=0; i<pEmr->cpts;i++) { - tmp_path << - "\n\tL " << - pix_to_x_point( d, apts[i].x, apts[i].y ) << " " << - pix_to_y_point( d, apts[i].x, apts[i].y ) << " "; - } - - break; - } - case EMR_POLYPOLYLINE16: - case EMR_POLYPOLYGON16: - { - if (lpEMFR->iType == EMR_POLYPOLYLINE16) - dbg_str << "<!-- EMR_POLYPOLYLINE16 -->\n"; - if (lpEMFR->iType == EMR_POLYPOLYGON16) - dbg_str << "<!-- EMR_POLYPOLYGON16 -->\n"; - - PEMRPOLYPOLYGON16 pEmr = (PEMRPOLYPOLYGON16) lpEMFR; - unsigned int n, i, j; - - if (!d->inpath) { - assert_empty_path(d, lpEMFR->iType == EMR_POLYPOLYGON16 ? "EMR_POLYPOLYGON16" : "EMR_POLYPOLYLINE16"); - - *(d->outsvg) += " <path "; - output_style(d, lpEMFR->iType==EMR_POLYPOLYGON16 ? EMR_STROKEANDFILLPATH : EMR_STROKEPATH); - *(d->outsvg) += "\n\td=\""; - } - - POINTS *apts = (POINTS *) &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_x_point( d, apts[i].x, apts[i].y ) << " " << - pix_to_y_point( 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_x_point( d, apts[i].x, apts[i].y ) << " " << - pix_to_y_point( d, apts[i].x, apts[i].y ) << " "; - i++; - } - - tmp_str << poly_path.str().c_str(); - if (lpEMFR->iType == EMR_POLYPOLYGON16) - tmp_str << " z"; - tmp_str << " \n"; - } - - if (d->inpath) { - tmp_path << tmp_str.str().c_str(); - } - else { - *(d->outsvg) += tmp_str.str().c_str(); - *(d->outsvg) += " \" /> \n"; - } - - break; - } - case EMR_POLYDRAW16: - dbg_str << "<!-- EMR_POLYDRAW16 -->\n"; - break; - case EMR_CREATEMONOBRUSH: - dbg_str << "<!-- EMR_CREATEMONOBRUSH -->\n"; - break; - case EMR_CREATEDIBPATTERNBRUSHPT: - { - dbg_str << "<!-- EMR_CREATEDIBPATTERNBRUSHPT -->\n"; - - PEMRCREATEDIBPATTERNBRUSHPT pEmr = (PEMRCREATEDIBPATTERNBRUSHPT) lpEMFR; - int index = pEmr->ihBrush; - - EMRCREATEDIBPATTERNBRUSHPT *pBrush = - (EMRCREATEDIBPATTERNBRUSHPT *) malloc( sizeof(EMRCREATEDIBPATTERNBRUSHPT) ); - insert_object(d, index, EMR_CREATEDIBPATTERNBRUSHPT, (ENHMETARECORD *) pBrush); - break; - } - case EMR_EXTCREATEPEN: - { - dbg_str << "<!-- EMR_EXTCREATEPEN -->\n"; - - PEMREXTCREATEPEN pEmr = (PEMREXTCREATEPEN) lpEMFR; - int index = pEmr->ihPen; - - EMREXTCREATEPEN *pPen = - (EMREXTCREATEPEN *) malloc( sizeof(EMREXTCREATEPEN) + - sizeof(DWORD) * pEmr->elp.elpNumEntries ); - pPen->ihPen = pEmr->ihPen; - pPen->offBmi = pEmr->offBmi; - pPen->cbBmi = pEmr->cbBmi; - pPen->offBits = pEmr->offBits; - pPen->cbBits = pEmr->cbBits; - pPen->elp = pEmr->elp; - for (unsigned int i=0; i<pEmr->elp.elpNumEntries; i++) { - pPen->elp.elpStyleEntry[i] = pEmr->elp.elpStyleEntry[i]; - } - insert_object(d, index, EMR_EXTCREATEPEN, (ENHMETARECORD *) pPen); - - break; - } - case EMR_POLYTEXTOUTA: - dbg_str << "<!-- EMR_POLYTEXTOUTA -->\n"; - break; - case EMR_POLYTEXTOUTW: - dbg_str << "<!-- EMR_POLYTEXTOUTW -->\n"; - break; - case EMR_SETICMMODE: - dbg_str << "<!-- EMR_SETICMMODE -->\n"; - break; - case EMR_CREATECOLORSPACE: - dbg_str << "<!-- EMR_CREATECOLORSPACE -->\n"; - break; - case EMR_SETCOLORSPACE: - dbg_str << "<!-- EMR_SETCOLORSPACE -->\n"; - break; - case EMR_DELETECOLORSPACE: - dbg_str << "<!-- EMR_DELETECOLORSPACE -->\n"; - break; - case EMR_GLSRECORD: - dbg_str << "<!-- EMR_GLSRECORD -->\n"; - break; - case EMR_GLSBOUNDEDRECORD: - dbg_str << "<!-- EMR_GLSBOUNDEDRECORD -->\n"; - break; - case EMR_PIXELFORMAT: - dbg_str << "<!-- EMR_PIXELFORMAT -->\n"; - break; - default: - dbg_str << "<!-- EMR_??? -->\n"; - break; - } - -// *(d->outsvg) += dbg_str.str().c_str(); - *(d->outsvg) += tmp_outsvg.str().c_str(); - *(d->path) += tmp_path.str().c_str(); - - return 1; -} - -static int CALLBACK -myMetaFileProc(HDC /*hDC*/, HANDLETABLE * /*lpHTable*/, METARECORD * /*lpMFR*/, int /*nObj*/, LPARAM /*lpData*/) -{ - g_warning("Unable to import Windows Meta File.\n"); - return 0; -} - -// Aldus Placeable Header =================================================== -// Since we are a 32bit app, we have to be sure this structure compiles to -// be identical to a 16 bit app's version. To do this, we use the #pragma -// to adjust packing, we use a WORD for the hmf handle, and a SMALL_RECT -// for the bbox rectangle. -#pragma pack( push ) -#pragma pack( 2 ) -typedef struct -{ - DWORD dwKey; - WORD hmf; - SMALL_RECT bbox; - WORD wInch; - DWORD dwReserved; - WORD wCheckSum; -} APMHEADER, *PAPMHEADER; -#pragma pack( pop ) - - -SPDocument * -EmfWin32::open( Inkscape::Extension::Input * /*mod*/, const gchar *uri ) -{ - EMF_CALLBACK_DATA d; - - memset(&d, 0, sizeof(d)); - - d.dc[0].worldTransform.eM11 = 1.0; - d.dc[0].worldTransform.eM12 = 0.0; - d.dc[0].worldTransform.eM21 = 0.0; - d.dc[0].worldTransform.eM22 = 1.0; - d.dc[0].worldTransform.eDx = 0.0; - d.dc[0].worldTransform.eDy = 0.0; - - gsize bytesRead = 0; - gsize bytesWritten = 0; - GError* error = NULL; - gchar *local_fn = - g_filename_from_utf8( uri, -1, &bytesRead, &bytesWritten, &error ); - - if (local_fn == NULL) { - return NULL; - } - - d.outsvg = new Glib::ustring(""); - d.path = new Glib::ustring(""); - d.outdef = new Glib::ustring(""); - - CHAR *ansi_uri = (CHAR *) local_fn; - gunichar2 *unicode_fn = g_utf8_to_utf16( local_fn, -1, NULL, NULL, NULL ); - WCHAR *unicode_uri = (WCHAR *) unicode_fn; - - DWORD filesize = 0; - HANDLE fp = NULL; - - HMETAFILE hmf; - HENHMETAFILE hemf; - - fp = CreateFileW(unicode_uri, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - - if ( fp != INVALID_HANDLE_VALUE ) { - filesize = GetFileSize(fp, NULL); - CloseHandle(fp); - } - - // Try open as Enhanced Metafile - hemf = GetEnhMetaFileW(unicode_uri); - - if (!hemf) { - // Try open as Windows Metafile - hmf = GetMetaFileW(unicode_uri); - - METAFILEPICT mp; - HDC hDC; - - if (!hmf) { - WCHAR szTemp[MAX_PATH]; - - DWORD dw = GetShortPathNameW( unicode_uri, szTemp, MAX_PATH ); - if (dw) { - hmf = GetMetaFileW( szTemp ); - } - } - - if (hmf) { - DWORD nSize = GetMetaFileBitsEx( hmf, 0, NULL ); - - if (!nSize) - nSize = filesize; - - if (nSize) { - BYTE *lpvData = new BYTE[nSize]; - if (lpvData) { - DWORD dw = GetMetaFileBitsEx( hmf, nSize, lpvData ); - if (dw) { - // Fill out a METAFILEPICT structure - mp.mm = MM_ANISOTROPIC; - mp.xExt = 1000; - mp.yExt = 1000; - mp.hMF = NULL; - // Get a reference DC - hDC = GetDC( NULL ); - // Make an enhanced metafile from the windows metafile - hemf = SetWinMetaFileBits( nSize, lpvData, hDC, &mp ); - // Clean up - ReleaseDC( NULL, hDC ); - DeleteMetaFile( hmf ); - hmf = NULL; - } - else { - // EnumMetaFile - } - delete[] lpvData; - } - else { - DeleteMetaFile( hmf ); - hmf = NULL; - } - } - else { - DeleteMetaFile( hmf ); - hmf = NULL; - } - } - else { - // Try open as Aldus Placeable Metafile - HANDLE hFile; - hFile = CreateFileW( unicode_uri, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); - - if (hFile != INVALID_HANDLE_VALUE) { - DWORD nSize = GetFileSize( hFile, NULL ); - if (nSize) { - BYTE *lpvData = new BYTE[nSize]; - if (lpvData) { - DWORD dw = ReadFile( hFile, lpvData, nSize, &nSize, NULL ); - if (dw) { - if ( ((PAPMHEADER)lpvData)->dwKey == 0x9ac6cdd7l ) { - // Fill out a METAFILEPICT structure - mp.mm = MM_ANISOTROPIC; - mp.xExt = ((PAPMHEADER)lpvData)->bbox.Right - ((PAPMHEADER)lpvData)->bbox.Left; - mp.xExt = ( mp.xExt * 2540l ) / (DWORD)(((PAPMHEADER)lpvData)->wInch); - mp.yExt = ((PAPMHEADER)lpvData)->bbox.Bottom - ((PAPMHEADER)lpvData)->bbox.Top; - mp.yExt = ( mp.yExt * 2540l ) / (DWORD)(((PAPMHEADER)lpvData)->wInch); - mp.hMF = NULL; - // Get a reference DC - hDC = GetDC( NULL ); - // Create an enhanced metafile from the bits - hemf = SetWinMetaFileBits( nSize, lpvData+sizeof(APMHEADER), hDC, &mp ); - // Clean up - ReleaseDC( NULL, hDC ); - } - } - delete[] lpvData; - } - } - CloseHandle( hFile ); - } - } - } - - if ((!hemf && !hmf) || !d.outsvg || !d.path) { - if (d.outsvg) - delete d.outsvg; - if (d.path) - delete d.path; - if (d.outdef) - delete d.outdef; - if (local_fn) - g_free(local_fn); - if (unicode_fn) - g_free(unicode_fn); - if (hemf) - DeleteEnhMetaFile(hemf); - if (hmf) - DeleteMetaFile(hmf); - return NULL; - } - - d.pDesc = NULL; - - if (hemf) { - DWORD dwNeeded = GetEnhMetaFileDescriptionA( hemf, 0, NULL ); - if ( dwNeeded > 0 ) { - d.pDesc = (CHAR *) malloc( dwNeeded + 1 ); - if ( GetEnhMetaFileDescription( hemf, dwNeeded, d.pDesc ) == 0 ) - lstrcpy( d.pDesc, "" ); - if ( lstrlen( d.pDesc ) > 1 ) - d.pDesc[lstrlen(d.pDesc)] = '#'; - } - - // This ugly reinterpret_cast is to prevent old versions of gcc from whining about a mismatch in the const-ness of the arguments - EnumEnhMetaFile(NULL, hemf, reinterpret_cast<ENHMFENUMPROC>(myEnhMetaFileProc), (LPVOID) &d, NULL); - DeleteEnhMetaFile(hemf); - } - else { - EnumMetaFile(NULL, hmf, myMetaFileProc, (LPARAM) &d); - DeleteMetaFile(hmf); - } - - if (d.pDesc) - free( d.pDesc ); - -// std::cout << "SVG Output: " << std::endl << *(d.outsvg) << std::endl; - - SPDocument *doc = SPDocument::createNewDocFromMem(d.outsvg->c_str(), strlen(d.outsvg->c_str()), TRUE); - - delete d.outsvg; - delete d.path; - delete d.outdef; - - if (d.emf_obj) { - int i; - for (i=0; i<d.n_obj; i++) - delete_object(&d, i); - delete[] d.emf_obj; - } - - if (d.dc[0].style.stroke_dash.dash) - delete[] d.dc[0].style.stroke_dash.dash; - - if (local_fn) - g_free(local_fn); - if (unicode_fn) - g_free(unicode_fn); - - return doc; -} - - -void -EmfWin32::init (void) -{ - /* 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.win32</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.win32</output_extension>\n" - "</input>\n" - "</inkscape-extension>", new EmfWin32()); - - /* 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.win32</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.emf.win32</output_extension>\n" - "</input>\n" - "</inkscape-extension>", new EmfWin32()); - - /* 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.win32</id>\n" - "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"boolean\">true</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 EmfWin32()); - - return; -} - - -} } } /* namespace Inkscape, Extension, Implementation */ - -#endif /* WIN32 */ -/* - 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-win32-inout.h b/src/extension/internal/emf-win32-inout.h deleted file mode 100644 index 4b975c8de..000000000 --- a/src/extension/internal/emf-win32-inout.h +++ /dev/null @@ -1,57 +0,0 @@ -/** @file - * @brief Enhanced Metafile Input/Output - */ -/* Authors: - * Ulf Erikson <ulferikson@users.sf.net> - * - * Copyright (C) 2006-2008 Authors - * - * Released under GNU GPL, read the file 'COPYING' for more information - */ -#ifndef SEEN_EXTENSION_INTERNAL_EMF_WIN32_H -#define SEEN_EXTENSION_INTERNAL_EMF_WIN32_H -#ifdef WIN32 - -#include "extension/implementation/implementation.h" - -namespace Inkscape { -namespace Extension { -namespace Internal { - -class EmfWin32 : Inkscape::Extension::Implementation::Implementation { //This is a derived class - -public: - EmfWin32(); // Empty constructor - - virtual ~EmfWin32();//Destructor - - bool check(Inkscape::Extension::Extension *module); //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); - - virtual SPDocument *open( Inkscape::Extension::Input *mod, - const gchar *uri ); - - static void init(void);//Initialize the class - -private: -}; - -} } } /* namespace Inkscape, Extension, Implementation */ - -#endif /* WIN32 */ - -#endif /* EXTENSION_INTERNAL_EMF_WIN32_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-win32-print.cpp b/src/extension/internal/emf-win32-print.cpp deleted file mode 100644 index ef76c2c14..000000000 --- a/src/extension/internal/emf-win32-print.cpp +++ /dev/null @@ -1,948 +0,0 @@ -/** @file - * @brief Enhanced Metafile printing - */ -/* Authors: - * Ulf Erikson <ulferikson@users.sf.net> - * Jon A. Cruz <jon@joncruz.org> - * Abhishek Sharma - * - * Copyright (C) 2006-2009 Authors - * - * Released under GNU GPL, 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 - */ - -#ifdef WIN32 - -#ifdef HAVE_CONFIG_H -# include "config.h" -#endif - - -#include <2geom/pathvector.h> -#include <2geom/rect.h> -#include <2geom/bezier-curve.h> -#include <2geom/hvlinesegment.h> -#include "helper/geom.h" -#include "helper/geom-curves.h" -#include "sp-item.h" - -#include "style.h" -#include "inkscape-version.h" -#include "sp-root.h" - -#include "emf-win32-print.h" - -#include "unit-constants.h" - -#include "extension/system.h" -#include "extension/print.h" -#include "document.h" - - -namespace Inkscape { -namespace Extension { -namespace Internal { - -static float dwDPI = 2540; - - -PrintEmfWin32::PrintEmfWin32 (void): - _width(0), - _height(0), - hdc(NULL), - hbrush(NULL), - hbrushOld(NULL), - hpen(NULL), - stroke_and_fill(false), - fill_only(false), - simple_shape(false) -{ -} - - -PrintEmfWin32::~PrintEmfWin32 (void) -{ - if (hdc) { - HENHMETAFILE metafile = CloseEnhMetaFile( hdc ); - if ( metafile ) { - DeleteEnhMetaFile( metafile ); - } - DeleteDC( hdc ); - } - - /* restore default signal handling for SIGPIPE */ -#if !defined(_WIN32) && !defined(__WIN32__) - (void) signal(SIGPIPE, SIG_DFL); -#endif - return; -} - - -unsigned int PrintEmfWin32::setup (Inkscape::Extension::Print * /*mod*/) -{ - return TRUE; -} - - -unsigned int PrintEmfWin32::begin (Inkscape::Extension::Print *mod, SPDocument *doc) -{ - gchar const *utf8_fn = mod->get_param_string("destination"); - - gsize bytesRead = 0; - gsize bytesWritten = 0; - GError* error = NULL; - gchar *local_fn = - g_filename_from_utf8( utf8_fn, -1, &bytesRead, &bytesWritten, &error ); - - if (local_fn == NULL) { - return 1; - } - - CHAR *ansi_uri = (CHAR *) local_fn; - gunichar2 *unicode_fn = g_utf8_to_utf16( local_fn, -1, NULL, NULL, NULL ); - WCHAR *unicode_uri = (WCHAR *) unicode_fn; - - // width and height in px - _width = doc->getWidth(); - _height = doc->getHeight(); - - 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(IN_PER_PX); - - float dwInchesX = d.width(); - float dwInchesY = d.height(); - - // dwInchesX x dwInchesY in .01mm units - SetRect( &rc, 0, 0, (int) ceil(dwInchesX*2540), (int) ceil(dwInchesY*2540) ); - - // Get a Reference DC - HDC hScreenDC = GetDC( NULL ); - - // Get the physical characteristics of the reference DC - int PixelsX = GetDeviceCaps( hScreenDC, HORZRES ); - int PixelsY = GetDeviceCaps( hScreenDC, VERTRES ); - int MMX = GetDeviceCaps( hScreenDC, HORZSIZE ); - int MMY = GetDeviceCaps( hScreenDC, VERTSIZE ); - - CHAR buff[1024]; - ZeroMemory(buff, sizeof(buff)); - snprintf(buff, sizeof(buff)-1, "Inkscape %s (%s)", Inkscape::version_string, __DATE__); - INT len = strlen(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+len+1, sizeof(buff)-len-2, "%s", p); - - // Create the Metafile - { - WCHAR wbuff[1024]; - ZeroMemory(wbuff, sizeof(wbuff)); - MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, buff, sizeof(buff)/sizeof(buff[0]), wbuff, sizeof(wbuff)/sizeof(wbuff[0])); - hdc = CreateEnhMetaFileW( hScreenDC, unicode_uri, &rc, wbuff ); - } - - // Release the reference DC - ReleaseDC( NULL, hScreenDC ); - - // Did we get a good metafile? - if (hdc == NULL) - { - g_free(local_fn); - g_free(unicode_fn); - return 1; - } - - // Anisotropic mapping mode - SetMapMode( hdc, MM_ANISOTROPIC ); - - // Set the Windows extent - int windowextX = (int) ceil(dwInchesX*dwDPI); - int windowextY = (int) ceil(dwInchesY*dwDPI); - SetWindowExtEx( hdc, windowextX, windowextY, NULL ); - - // Set the viewport extent to reflect - // dwInchesX" x dwInchesY" in device units - int viewportextX = (int)((float)dwInchesX*25.4f*(float)PixelsX/(float)MMX); - int viewportextY = (int)((float)dwInchesY*25.4f*(float)PixelsY/(float)MMY); - SetViewportExtEx( hdc, viewportextX, viewportextY, NULL ); - - if (1) { - snprintf(buff, sizeof(buff)-1, "Screen=%dx%dpx, %dx%dmm", PixelsX, PixelsY, MMX, MMY); - GdiComment(hdc, strlen(buff), (BYTE*) buff); - - snprintf(buff, sizeof(buff)-1, "Drawing=%.1lfx%.1lfpx, %.1lfx%.1lfmm", _width, _height, dwInchesX * MM_PER_IN, dwInchesY * MM_PER_IN); - GdiComment(hdc, strlen(buff), (BYTE*) buff); - } - - SetRect( &rc, 0, 0, (int) ceil(dwInchesX*dwDPI), (int) ceil(dwInchesY*dwDPI) ); - - g_free(local_fn); - g_free(unicode_fn); - - m_tr_stack.push( Geom::Scale(1, -1) * Geom::Translate(0, doc->getHeight())); /// @fixme hardcoded doc2dt transform - - return 0; -} - - -unsigned int PrintEmfWin32::finish (Inkscape::Extension::Print * /*mod*/) -{ - if (!hdc) return 0; - - flush_fill(); // flush any pending fills - - HENHMETAFILE metafile = CloseEnhMetaFile( hdc ); - if ( metafile ) { - DeleteEnhMetaFile( metafile ); - } - DeleteDC( hdc ); - - hdc = NULL; - - return 0; -} - - -unsigned int PrintEmfWin32::comment (Inkscape::Extension::Print * /*module*/, - const char * /*comment*/) -{ - if (!hdc) return 0; - - flush_fill(); // flush any pending fills - - return 0; -} - - -int PrintEmfWin32::create_brush(SPStyle const *style) -{ - float rgb[3]; - - if (style) { - float opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); - if (opacity <= 0.0) - return 1; - - sp_color_get_rgb_floatv( &style->fill.value.color, rgb ); - hbrush = CreateSolidBrush( RGB(255*rgb[0], 255*rgb[1], 255*rgb[2]) ); - hbrushOld = (HBRUSH) SelectObject( hdc, hbrush ); - - SetPolyFillMode( hdc, - style->fill_rule.computed == 0 ? WINDING : - style->fill_rule.computed == 2 ? ALTERNATE : ALTERNATE ); - } else { // if (!style) - hbrush = CreateSolidBrush( RGB(255, 255, 255) ); - hbrushOld = (HBRUSH) SelectObject( hdc, hbrush ); - SetPolyFillMode( hdc, ALTERNATE ); - } - - return 0; -} - - -void PrintEmfWin32::destroy_brush() -{ - SelectObject( hdc, hbrushOld ); - if (hbrush) - DeleteObject( hbrush ); - hbrush = NULL; - hbrushOld = NULL; -} - - -void PrintEmfWin32::create_pen(SPStyle const *style, const Geom::Affine &transform) -{ - if (style) { - float rgb[3]; - - sp_color_get_rgb_floatv(&style->stroke.value.color, rgb); - - LOGBRUSH lb; - ZeroMemory(&lb, sizeof(lb)); - lb.lbStyle = BS_SOLID; - lb.lbColor = RGB( 255*rgb[0], 255*rgb[1], 255*rgb[2] ); - - int linestyle = PS_SOLID; - int linecap = 0; - int linejoin = 0; - DWORD n_dash = 0; - DWORD *dash = NULL; - - 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); - - DWORD linewidth = MAX( 1, (DWORD) (scale * style->stroke_width.computed * IN_PER_PX * dwDPI) ); - - if (style->stroke_linecap.computed == 0) { - linecap = PS_ENDCAP_FLAT; - } - else if (style->stroke_linecap.computed == 1) { - linecap = PS_ENDCAP_ROUND; - } - else if (style->stroke_linecap.computed == 2) { - linecap = PS_ENDCAP_SQUARE; - } - - if (style->stroke_linejoin.computed == 0) { - linejoin = PS_JOIN_MITER; - } - else if (style->stroke_linejoin.computed == 1) { - linejoin = PS_JOIN_ROUND; - } - else if (style->stroke_linejoin.computed == 2) { - linejoin = PS_JOIN_BEVEL; - } - - if (style->stroke_dash.n_dash && - style->stroke_dash.dash ) - { - int i = 0; - while (linestyle != PS_USERSTYLE && - (i < style->stroke_dash.n_dash)) { - if (style->stroke_dash.dash[i] > 0.00000001) - linestyle = PS_USERSTYLE; - i++; - } - - if (linestyle == PS_USERSTYLE) { - n_dash = style->stroke_dash.n_dash; - dash = new DWORD[n_dash]; - for (i = 0; i < style->stroke_dash.n_dash; i++) { - dash[i] = (DWORD) (style->stroke_dash.dash[i] * IN_PER_PX * dwDPI); - } - } - } - - hpen = ExtCreatePen( - PS_GEOMETRIC | linestyle | linecap | linejoin, - linewidth, - &lb, - n_dash, - dash ); - - if ( !hpen && linestyle == PS_USERSTYLE ) { - hpen = ExtCreatePen( - PS_GEOMETRIC | PS_SOLID | linecap | linejoin, - linewidth, - &lb, - 0, - NULL ); - } - - if ( !hpen ) { - hpen = CreatePen( - PS_SOLID, - linewidth, - lb.lbColor ); - } - - hpenOld = (HPEN) SelectObject( hdc, hpen ); - - if (linejoin == PS_JOIN_MITER) { - float oldmiterlimit; - float miterlimit = style->stroke_miterlimit.value; - - miterlimit = miterlimit * 10.0 / 4.0; - if (miterlimit < 1) - miterlimit = 10.0; - - miterlimit = miterlimit * IN_PER_PX * dwDPI; - - SetMiterLimit( - hdc, - miterlimit, - &oldmiterlimit ); - } - - if (n_dash) { - delete[] dash; - } - } - else { // if (!style) - hpen = CreatePen( PS_SOLID, 1, RGB(0, 0, 0) ); - hpenOld = (HPEN) SelectObject( hdc, hpen ); - } -} - - -void PrintEmfWin32::destroy_pen() -{ - SelectObject( hdc, hpenOld ); - if (hpen) - DeleteObject( hpen ); - hpen = NULL; -} - - -void PrintEmfWin32::flush_fill() -{ - if (!fill_pathv.empty()) { - stroke_and_fill = false; - fill_only = true; - print_pathv(fill_pathv, fill_transform); - fill_only = false; - if (!simple_shape) - FillPath( hdc ); - destroy_brush(); - fill_pathv.clear(); - } -} - -unsigned int PrintEmfWin32::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 PrintEmfWin32::release(Inkscape::Extension::Print * /*mod*/) -{ - m_tr_stack.pop(); - return 1; -} - -unsigned int PrintEmfWin32::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 (!hdc) return 0; - - Geom::Affine tf = m_tr_stack.top(); - - flush_fill(); // flush any pending fills - - if (style->fill.isColor()) { - if (create_brush(style)) - return 0; - } else { - // create_brush(NULL); - return 0; - } - - fill_pathv.clear(); - std::copy(pathv.begin(), pathv.end(), std::back_inserter(fill_pathv)); - fill_transform = tf; - - // postpone fill in case of stroke-and-fill - - return 0; -} - - -unsigned int PrintEmfWin32::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*/) -{ - if (!hdc) return 0; - - Geom::Affine tf = m_tr_stack.top(); - - stroke_and_fill = ( pathv == fill_pathv ); - - if (!stroke_and_fill) { - flush_fill(); // flush any pending fills - } - - if (style->stroke.isColor()) { - create_pen(style, tf); - } else { - // create_pen(NULL, tf); - return 0; - } - - print_pathv(pathv, tf); - - if (stroke_and_fill) { - if (!simple_shape) - StrokeAndFillPath( hdc ); - destroy_brush(); - fill_pathv.clear(); - } else { - if (!simple_shape) - StrokePath( hdc ); - } - - destroy_pen(); - - return 0; -} - - -bool PrintEmfWin32::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; - - for (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) - { - moves++; - nodes++; - - for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) - { - nodes++; - - if ( is_straight_curve(*cit) ) { - lines++; - } - else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) { - cubic = cubic; - curves++; - } - } - } - - if (!nodes) - return false; - - POINT *lpPoints = new POINT[moves + lines + curves*3]; - int i = 0; - - /** - * For all Subpaths in the <path> - */ - for (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) - { - using Geom::X; - using Geom::Y; - - Geom::Point p0 = pit->initialPoint(); - - p0[X] = (p0[X] * IN_PER_PX * dwDPI); - p0[Y] = (p0[Y] * IN_PER_PX * dwDPI); - - LONG const x0 = (LONG) round(p0[X]); - LONG const y0 = (LONG) round(rc.bottom-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] * IN_PER_PX * dwDPI); - p1[X] = (p1[X] * IN_PER_PX * dwDPI); - //p0[Y] = (p0[Y] * IN_PER_PX * dwDPI); - p1[Y] = (p1[Y] * IN_PER_PX * dwDPI); - - //LONG const x0 = (LONG) round(p0[X]); - //LONG const y0 = (LONG) round(rc.bottom-p0[Y]); - LONG const x1 = (LONG) round(p1[X]); - LONG const y1 = (LONG) round(rc.bottom-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->points(); - //Geom::Point p0 = points[0]; - Geom::Point p1 = points[1]; - Geom::Point p2 = points[2]; - Geom::Point p3 = points[3]; - - //p0[X] = (p0[X] * IN_PER_PX * dwDPI); - p1[X] = (p1[X] * IN_PER_PX * dwDPI); - p2[X] = (p2[X] * IN_PER_PX * dwDPI); - p3[X] = (p3[X] * IN_PER_PX * dwDPI); - //p0[Y] = (p0[Y] * IN_PER_PX * dwDPI); - p1[Y] = (p1[Y] * IN_PER_PX * dwDPI); - p2[Y] = (p2[Y] * IN_PER_PX * dwDPI); - p3[Y] = (p3[Y] * IN_PER_PX * dwDPI); - - //LONG const x0 = (LONG) round(p0[X]); - //LONG const y0 = (LONG) round(rc.bottom-p0[Y]); - LONG const x1 = (LONG) round(p1[X]); - LONG const y1 = (LONG) round(rc.bottom-p1[Y]); - LONG const x2 = (LONG) round(p2[X]); - LONG const y2 = (LONG) round(rc.bottom-p2[Y]); - LONG const x3 = (LONG) round(p3[X]); - LONG const y3 = (LONG) round(rc.bottom-p3[Y]); - - POINT 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; - - 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) { - HPEN hpenTmp = NULL; - HPEN hpenOld = NULL; - HBRUSH hbrushTmp = NULL; - HBRUSH hbrushOld = NULL; - - if (!stroke_and_fill) { - if (fill_only) { - hpenTmp = (HPEN) GetStockObject(NULL_PEN); - hpenOld = (HPEN) SelectObject( hdc, hpenTmp ); - } - else { // if (stroke_only) - hbrushTmp = (HBRUSH) GetStockObject(NULL_BRUSH); - hbrushOld = (HBRUSH) SelectObject( hdc, hbrushTmp ); - } - } - - if (polygon) { - if (rectangle) - Rectangle( hdc, lpPoints[0].x, lpPoints[0].y, lpPoints[2].x, lpPoints[2].y ); - else - Polygon( hdc, lpPoints, nodes ); - } - else if (ellipse) { - Ellipse( hdc, lpPoints[6].x, lpPoints[3].y, lpPoints[0].x, lpPoints[9].y); - } - - done = true; - - if (hpenOld) - SelectObject( hdc, hpenOld ); - if (hpenTmp) - DeleteObject( hpenTmp ); - if (hbrushOld) - SelectObject( hdc, hbrushOld ); - if (hbrushTmp) - DeleteObject( hbrushTmp ); - } - - delete[] lpPoints; - - return done; -} - -unsigned int PrintEmfWin32::print_pathv(Geom::PathVector const &pathv, const Geom::Affine &transform) -{ - simple_shape = print_simple_shape(pathv, transform); - - if (simple_shape) - return TRUE; - - Geom::PathVector pv = pathv_to_linear_and_cubic_beziers( pathv * transform ); - - BeginPath( hdc ); - - /** - * For all Subpaths in the <path> - */ - for (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) - { - using Geom::X; - using Geom::Y; - - Geom::Point p0 = pit->initialPoint(); - - p0[X] = (p0[X] * IN_PER_PX * dwDPI); - p0[Y] = (p0[Y] * IN_PER_PX * dwDPI); - - LONG const x0 = (LONG) round(p0[X]); - LONG const y0 = (LONG) round(rc.bottom-p0[Y]); - - MoveToEx( hdc, x0, y0, NULL ); - - /** - * 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] * IN_PER_PX * dwDPI); - p1[X] = (p1[X] * IN_PER_PX * dwDPI); - //p0[Y] = (p0[Y] * IN_PER_PX * dwDPI); - p1[Y] = (p1[Y] * IN_PER_PX * dwDPI); - - //LONG const x0 = (LONG) round(p0[X]); - //LONG const y0 = (LONG) round(rc.bottom-p0[Y]); - LONG const x1 = (LONG) round(p1[X]); - LONG const y1 = (LONG) round(rc.bottom-p1[Y]); - - LineTo( hdc, x1, y1 ); - } - else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) - { - std::vector<Geom::Point> points = cubic->points(); - //Geom::Point p0 = points[0]; - Geom::Point p1 = points[1]; - Geom::Point p2 = points[2]; - Geom::Point p3 = points[3]; - - //p0[X] = (p0[X] * IN_PER_PX * dwDPI); - p1[X] = (p1[X] * IN_PER_PX * dwDPI); - p2[X] = (p2[X] * IN_PER_PX * dwDPI); - p3[X] = (p3[X] * IN_PER_PX * dwDPI); - //p0[Y] = (p0[Y] * IN_PER_PX * dwDPI); - p1[Y] = (p1[Y] * IN_PER_PX * dwDPI); - p2[Y] = (p2[Y] * IN_PER_PX * dwDPI); - p3[Y] = (p3[Y] * IN_PER_PX * dwDPI); - - //LONG const x0 = (LONG) round(p0[X]); - //LONG const y0 = (LONG) round(rc.bottom-p0[Y]); - LONG const x1 = (LONG) round(p1[X]); - LONG const y1 = (LONG) round(rc.bottom-p1[Y]); - LONG const x2 = (LONG) round(p2[X]); - LONG const y2 = (LONG) round(rc.bottom-p2[Y]); - LONG const x3 = (LONG) round(p3[X]); - LONG const y3 = (LONG) round(rc.bottom-p3[Y]); - - POINT 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; - - PolyBezierTo( hdc, pt, 3 ); - } - else - { - g_warning("logical error, because pathv_to_linear_and_cubic_beziers was used"); - } - } - - if (pit->end_default() == pit->end_closed()) { - CloseFigure( hdc ); - } - } - - EndPath( hdc ); - - return TRUE; -} - - -bool PrintEmfWin32::textToPath(Inkscape::Extension::Print * ext) -{ - return ext->get_param_bool("textToPath"); -} - -unsigned int PrintEmfWin32::text(Inkscape::Extension::Print * /*mod*/, char const *text, Geom::Point const &p, - SPStyle const *const style) -{ - if (!hdc) return 0; - - HFONT hfont = NULL; - Geom::Affine tf = m_tr_stack.top(); - double rot = 1800.0*std::atan2(tf[1], tf[0])/M_PI; // 0.1 degree rotation - -#ifdef USE_PANGO_WIN32 -/* - font_instance *tf = (font_factory::Default())->Face(style->text->font_family.value, font_style_to_pos(*style)); - if (tf) { - LOGFONT *lf = pango_win32_font_logfont(tf->pFont); - tf->Unref(); - hfont = CreateFontIndirect(lf); - g_free(lf); - } -*/ -#endif - - if (!hfont) { - LOGFONTW *lf = (LOGFONTW*)g_malloc(sizeof(LOGFONTW)); - g_assert(lf != NULL); - - lf->lfHeight = -style->font_size.computed * IN_PER_PX * dwDPI; - lf->lfWidth = 0; - lf->lfEscapement = rot; - lf->lfOrientation = rot; - lf->lfWeight = - style->font_weight.computed == SP_CSS_FONT_WEIGHT_100 ? FW_THIN : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_200 ? FW_EXTRALIGHT : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_300 ? FW_LIGHT : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_400 ? FW_NORMAL : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_500 ? FW_MEDIUM : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_600 ? FW_SEMIBOLD : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_700 ? FW_BOLD : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_800 ? FW_EXTRABOLD : - style->font_weight.computed == SP_CSS_FONT_WEIGHT_900 ? FW_HEAVY : - FW_NORMAL; - lf->lfItalic = (style->font_style.computed == SP_CSS_FONT_STYLE_ITALIC); - lf->lfUnderline = style->text_decoration.underline; - lf->lfStrikeOut = style->text_decoration.line_through; - lf->lfCharSet = DEFAULT_CHARSET; - lf->lfOutPrecision = OUT_DEFAULT_PRECIS; - lf->lfClipPrecision = CLIP_DEFAULT_PRECIS; - lf->lfQuality = DEFAULT_QUALITY; - lf->lfPitchAndFamily = DEFAULT_PITCH | FF_DONTCARE; - - gunichar2 *unicode_name = g_utf8_to_utf16( style->text->font_family.value, -1, NULL, NULL, NULL ); - wcsncpy(lf->lfFaceName, (wchar_t*) unicode_name, LF_FACESIZE-1); - g_free(unicode_name); - - hfont = CreateFontIndirectW(lf); - - g_free(lf); - } - - HFONT hfontOld = (HFONT) SelectObject(hdc, hfont); - - float rgb[3]; - sp_color_get_rgb_floatv( &style->fill.value.color, rgb ); - SetTextColor(hdc, RGB(255*rgb[0], 255*rgb[1], 255*rgb[2])); - - // 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 EMF text alignment must always be TA_BASELINE|TA_LEFT. - SetTextAlign(hdc, TA_BASELINE | TA_LEFT); - - // Transparent text background - SetBkMode(hdc, TRANSPARENT); - - Geom::Point p2 = p * tf; - p2[Geom::X] = (p2[Geom::X] * IN_PER_PX * dwDPI); - p2[Geom::Y] = (p2[Geom::Y] * IN_PER_PX * dwDPI); - - LONG const xpos = (LONG) round(p2[Geom::X]); - LONG const ypos = (LONG) round(rc.bottom - p2[Geom::Y]); - - { - gunichar2 *unicode_text = g_utf8_to_utf16( text, -1, NULL, NULL, NULL ); - TextOutW(hdc, xpos, ypos, (WCHAR*)unicode_text, wcslen((wchar_t*)unicode_text)); - } - - SelectObject(hdc, hfontOld); - DeleteObject(hfont); - - return 0; -} - -void PrintEmfWin32::init (void) -{ - /* 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.win32</id>\n" - "<param name=\"destination\" type=\"string\"></param>\n" - "<param name=\"textToPath\" type=\"boolean\">true</param>\n" - "<param name=\"pageBoundingBox\" type=\"boolean\">true</param>\n" - "<print/>\n" - "</inkscape-extension>", new PrintEmfWin32()); - - return; -} - -unsigned int PrintEmfWin32::image(Inkscape::Extension::Print * /* module */, /** not used */ - unsigned char *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_ignore*/, /** WRONG affine transform, use the one from m_tr_stack */ - SPStyle const * /*style*/) /** provides indirect link to image object */ -{ - free(px); - return 0; -} - -} /* namespace Internal */ -} /* namespace Extension */ -} /* namespace Inkscape */ - -#endif /* WIN32 */ - -/* - 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/bevels.h b/src/extension/internal/filter/bevels.h index 6fc73e58a..ea9d537ad 100644 --- a/src/extension/internal/filter/bevels.h +++ b/src/extension/internal/filter/bevels.h @@ -52,9 +52,9 @@ public: "<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=\"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" @@ -132,10 +132,10 @@ public: "<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=\"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" @@ -216,10 +216,10 @@ public: "<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=\"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" diff --git a/src/extension/internal/filter/blurs.h b/src/extension/internal/filter/blurs.h index 59790b1be..fe1b9c930 100644 --- a/src/extension/internal/filter/blurs.h +++ b/src/extension/internal/filter/blurs.h @@ -53,8 +53,8 @@ public: "<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=\"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=\"boolean\">False</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -124,7 +124,7 @@ public: "<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" + "<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" @@ -184,10 +184,10 @@ public: "<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=\"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=\"enum\">\n" "<_item value=\"darken\">" N_("Darken") "</_item>\n" "<_item value=\"screen\">" N_("Screen") "</_item>\n" @@ -259,7 +259,7 @@ public: "<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" + "<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" @@ -326,11 +326,11 @@ public: "<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" + "<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" diff --git a/src/extension/internal/filter/bumps.h b/src/extension/internal/filter/bumps.h index 8db6c67d2..b59b1ef50 100644 --- a/src/extension/internal/filter/bumps.h +++ b/src/extension/internal/filter/bumps.h @@ -81,13 +81,13 @@ public: "<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" + "<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" "<_param name=\"sourceHeader\" type=\"description\" appearance=\"header\">" N_("Bump source") "</_param>\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=\"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=\"boolean\">false</param>\n" "</page>\n" "<page name=\"lightingtab\" _gui-text=\"Lighting\">\n" @@ -95,9 +95,9 @@ public: "<_item value=\"specular\">" N_("Specular") "</_item>\n" "<_item value=\"diffuse\">" N_("Diffuse") "</_item>\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=\"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" @@ -107,21 +107,21 @@ public: "<_item value=\"spot\">" N_("Spot") "</_item>\n" "</param>\n" "<_param name=\"distantHeader\" type=\"description\" appearance=\"header\">" N_("Distant light options") "</_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\">45</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\">45</param>\n" "<_param name=\"pointHeader\" type=\"description\" appearance=\"header\">" N_("Point light options") "</_param>\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" + "<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" "<_param name=\"spotHeader\" type=\"description\" appearance=\"header\">" N_("Spot light options") "</_param>\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" + "<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" @@ -167,9 +167,6 @@ Bump::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream lightOptions; std::ostringstream lightEnd; - std::ostringstream lightRed; - std::ostringstream lightGreen; - std::ostringstream lightBlue; std::ostringstream floodRed; std::ostringstream floodGreen; std::ostringstream floodBlue; @@ -313,27 +310,27 @@ public: "<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" + "<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" "<_param name=\"sourceHeader\" type=\"description\" appearance=\"header\">" N_("Bump source") "</_param>\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=\"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=\"enum\">\n" "<_item value=\"flood1\">" N_("Color") "</_item>\n" "<_item value=\"SourceGraphic\">" N_("Image") "</_item>\n" "<_item value=\"blur1\">" N_("Blurred image") "</_item>\n" "</param>\n" - "<param name=\"bgopacity\" gui-text=\"" N_("Background opacity:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">0</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=\"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=\"enum\">\n" "<_item value=\"screen\">" N_("Screen") "</_item>\n" "<_item value=\"normal\">" N_("Normal") "</_item>\n" diff --git a/src/extension/internal/filter/color.h b/src/extension/internal/filter/color.h index b6b194c8b..7059ad29b 100644 --- a/src/extension/internal/filter/color.h +++ b/src/extension/internal/filter/color.h @@ -3,7 +3,7 @@ /* Change the 'COLOR' above to be your file name */ /* - * Copyright (C) 2011 Authors: + * Copyright (C) 2013 Authors: * Ivan Louette (filters) * Nicolas Dufour (UI) <nicoduf@yahoo.fr> * @@ -20,7 +20,8 @@ * Invert * Lighting * Lightness-contrast - * Nudge + * Nudge RGB + * Nudge CMY * Quadritone * Solarize * Tritone @@ -70,9 +71,9 @@ public: "<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=\"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=\"boolean\">false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -152,11 +153,11 @@ public: "<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=\"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=\"boolean\">false</param>\n" "</page>\n" "<page name=\"colortab\" _gui-text=\"Color\">\n" @@ -211,7 +212,7 @@ ChannelPaint::get_filter_text (Inkscape::Extension::Extension * ext) } _filter = g_strdup_printf( - "<filter inkscape:label=\"Channel Painting\" style=\"color-interpolation-filters:sRGB;\" >\n" + "<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" @@ -252,8 +253,8 @@ public: "<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" + "<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" @@ -317,8 +318,8 @@ public: "<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=\"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=\"boolean\" >false</param>\n" "<param name=\"blend1\" gui-text=\"" N_("Blend 1:") "\" type=\"enum\">\n" "<_item value=\"multiply\">" N_("Multiply") "</_item>\n" @@ -509,7 +510,7 @@ public: "<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=\"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=\"enum\">\n" "<_item value=\"none\">" N_("No swap") "</_item>\n" "<_item value=\"full\">" N_("Color and alpha") "</_item>\n" @@ -615,9 +616,8 @@ Duochrome::get_filter_text (Inkscape::Extension::Extension * ext) Filter's parameters: * Channel (enum, all colors, default Red) -> colormatrix (values) - * Background blend (enum, all blend modes, default Multiply) -> blend (mode) + * Background blend (enum, Normal, Multiply, Screen, default Normal) -> blend (mode) * Channel to alpha (boolean, default false) -> colormatrix (values) - * Invert (boolean, default false) -> colormatrix (values) */ class ExtractChannel : public Inkscape::Extension::Internal::Filter::Filter { @@ -637,16 +637,16 @@ public: "<_item value=\"r\">" N_("Red") "</_item>\n" "<_item value=\"g\">" N_("Green") "</_item>\n" "<_item value=\"b\">" N_("Blue") "</_item>\n" + "<_item value=\"c\">" N_("Cyan") "</_item>\n" + "<_item value=\"m\">" N_("Magenta") "</_item>\n" + "<_item value=\"y\">" N_("Yellow") "</_item>\n" "</param>\n" "<param name=\"blend\" gui-text=\"" N_("Background blend mode:") "\" type=\"enum\">\n" "<_item value=\"multiply\">" N_("Multiply") "</_item>\n" "<_item value=\"normal\">" N_("Normal") "</_item>\n" "<_item value=\"screen\">" N_("Screen") "</_item>\n" - "<_item value=\"darken\">" N_("Darken") "</_item>\n" - "<_item value=\"lighten\">" N_("Lighten") "</_item>\n" "</param>\n" "<param name=\"alpha\" gui-text=\"" N_("Channel to alpha") "\" type=\"boolean\">false</param>\n" - "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"boolean\">false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" "<effects-menu>\n" @@ -667,45 +667,45 @@ ExtractChannel::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream blend; std::ostringstream colors; - std::ostringstream alpha; - std::ostringstream invert; blend << ext->get_param_enum("blend"); const gchar *channel = ext->get_param_enum("source"); if (ext->get_param_bool("alpha")) { - colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 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"; - } else if ((g_ascii_strcasecmp("g", channel) == 0)) { - colors << "0 0 0 0 0 0 0 0 0 1 0 0 0 0 0"; - } else { - colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 1"; - } - - if (ext->get_param_bool("invert")) { if ((g_ascii_strcasecmp("r", channel) == 0)) { - alpha << "-1 0 0 1"; + 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)) { - alpha << "0 -1 0 1"; + 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 { - alpha << "0 0 -1 1"; + 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)) { - alpha << "1 0 0 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)) { - alpha << "0 1 0 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 { - alpha << "0 0 1 0"; + 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 %s %s 0 \" result=\"colormatrix\" />\n" + "<feColorMatrix in=\"SourceGraphic\" values=\"%s 0 \" result=\"colormatrix\" />\n" "<feBlend in2=\"BackgroundImage\" mode=\"%s\" result=\"blend\" />\n" - "</filter>\n", colors.str().c_str(), alpha.str().c_str(), invert.str().c_str(), blend.str().c_str() ); + "</filter>\n", colors.str().c_str(), blend.str().c_str() ); return _filter; }; /* ExtractChannel filter */ @@ -739,7 +739,7 @@ public: "<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=\"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=\"enum\">\n" "<_item value=\"black\">" N_("Black") "</_item>\n" "<_item value=\"white\">" N_("White") "</_item>\n" @@ -818,10 +818,10 @@ public: "<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=\"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=\"boolean\" >false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -910,7 +910,7 @@ public: "<_item value=\"2\">" N_("Red and green") "</_item>\n" "<_item value=\"3\">" N_("Green and blue") "</_item>\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=\"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=\"boolean\" >false</param>\n" "<param name=\"lightness\" gui-text=\"" N_("Invert lightness") "\" type=\"boolean\" >false</param>\n" "<param name=\"transparency\" gui-text=\"" N_("Invert transparency") "\" type=\"boolean\" >false</param>\n" @@ -1039,9 +1039,9 @@ public: "<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" + "<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" @@ -1110,8 +1110,8 @@ public: "<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" + "<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" @@ -1157,72 +1157,52 @@ LightnessContrast::get_filter_text (Inkscape::Extension::Extension * ext) }; /* Lightness-Contrast filter */ /** - \brief Custom predefined Nudge filter. + \brief Custom predefined Nudge RGB filter. - Nudge separately RGB channels and blend them to different types of backgrounds + Nudge RGB channels separately and blend them to different types of backgrounds Filter's parameters: Offsets * Red - * x (-100.->100., default -7) -> offset1 (dx) - * y (-100.->100., default 5) -> offset1 (dy) + * x (-100.->100., default -6) -> offset1 (dx) + * y (-100.->100., default -6) -> offset1 (dy) * Green - * x (-100.->100., default 0) -> offset2 (dx) - * y (-100.->100., default 10) -> offset2 (dy) + * x (-100.->100., default 6) -> offset2 (dx) + * y (-100.->100., default 7) -> offset2 (dy) * Blue - * x (-100.->100., default 3) -> offset3 (dx) - * y (-100.->100., default -9) -> offset3 (dy) + * 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) - * Blend type (enum [normal,multiply and screen], default screen) -> blend1,2,3 (mode) - * Blend source (enum, default color) -> - * color: blend1 (in="flood") - * image: blend1 (in="SourceGraphic") - * background: blend1 (in="BackgroundImage") - * Composite (enum [in,over], default over) -> composite (operator) + * Background color (guint, default 255)-> flood (flood-color, flood-opacity) */ -class Nudge : public Inkscape::Extension::Internal::Filter::Filter { +class NudgeRGB : public Inkscape::Extension::Internal::Filter::Filter { protected: virtual gchar const * get_filter_text (Inkscape::Extension::Extension * ext); public: - Nudge ( ) : Filter() { }; - virtual ~Nudge ( ) { if (_filter != NULL) g_free((void *)_filter); return; } + NudgeRGB ( ) : Filter() { }; + virtual ~NudgeRGB ( ) { if (_filter != NULL) g_free((void *)_filter); return; } static void init (void) { Inkscape::Extension::build_from_mem( "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" - "<name>" N_("Nudge") "</name>\n" - "<id>org.inkscape.effect.filter.Nudge</id>\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" "<_param name=\"redOffset\" type=\"description\" appearance=\"header\">" N_("Red offset") "</_param>\n" - "<param name=\"rx\" gui-text=\"" N_("X:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-7</param>\n" - "<param name=\"ry\" gui-text=\"" N_("Y:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">5</param>\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" "<_param name=\"greenOffset\" type=\"description\" appearance=\"header\">" N_("Green offset") "</_param>\n" - "<param name=\"gx\" gui-text=\"" N_("X:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">0</param>\n" - "<param name=\"gy\" gui-text=\"" N_("Y:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">10</param>\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" "<_param name=\"redOffset\" type=\"description\" appearance=\"header\">" N_("Blue offset") "</_param>\n" - "<param name=\"bx\" gui-text=\"" N_("X:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">3</param>\n" - "<param name=\"by\" gui-text=\"" N_("Y:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-9</param>\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" - "<param name=\"blend\" gui-text=\"" N_("Blend type:") "\" type=\"enum\">\n" - "<_item value=\"screen\">" N_("Screen") "</_item>\n" - "<_item value=\"multiply\">" N_("Multiply") "</_item>\n" - "<_item value=\"normal\">" N_("Normal") "</_item>\n" - "</param>\n" - "<param name=\"source\" gui-text=\"" N_("Blend source:") "\" type=\"enum\">\n" - "<_item value=\"flood\">" N_("Color") "</_item>\n" - "<_item value=\"SourceGraphic\">" N_("Image") "</_item>\n" - "<_item value=\"BackgroundImage\">" N_("Background") "</_item>\n" - "</param>\n" - "<param name=\"composite\" gui-text=\"" N_("Composite:") "\" type=\"enum\">\n" - "<_item value=\"over\">" N_("Over") "</_item>\n" - "<_item value=\"in\">" N_("In") "</_item>\n" - "</param>\n" "</page>\n" "</param>\n" "<effect>\n" @@ -1232,14 +1212,14 @@ public: "<submenu name=\"" N_("Color") "\"/>\n" "</submenu>\n" "</effects-menu>\n" - "<menu-tip>" N_("Nudge separately RGB channels and blend them to different types of backgrounds") "</menu-tip>\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 Nudge()); + "</inkscape-extension>\n", new NudgeRGB()); }; }; gchar const * -Nudge::get_filter_text (Inkscape::Extension::Extension * ext) +NudgeRGB::get_filter_text (Inkscape::Extension::Extension * ext) { if (_filter != NULL) g_free((void *)_filter); @@ -1250,10 +1230,6 @@ Nudge::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream bx; std::ostringstream by; - std::ostringstream blend; - std::ostringstream source; - std::ostringstream composite; - std::ostringstream a; std::ostringstream r; std::ostringstream g; @@ -1266,10 +1242,6 @@ Nudge::get_filter_text (Inkscape::Extension::Extension * ext) bx << ext->get_param_float("bx"); by << ext->get_param_float("by"); - blend << ext->get_param_enum("blend"); - source << ext->get_param_enum("source"); - composite << ext->get_param_enum("composite"); - guint32 color = ext->get_param_color("color"); r << ((color >> 24) & 0xff); g << ((color >> 16) & 0xff); @@ -1277,27 +1249,137 @@ Nudge::get_filter_text (Inkscape::Extension::Extension * ext) 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\">\n" + "<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=\"%s\" mode=\"%s\" result=\"blend1\" />\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=\"%s\" result=\"blend2\" />\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=\"%s\" result=\"blend3\" />\n" - "<feComposite in2=\"SourceGraphic\" operator=\"%s\" />\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: + virtual gchar const * get_filter_text (Inkscape::Extension::Extension * ext); + +public: + NudgeCMY ( ) : Filter() { }; + virtual ~NudgeCMY ( ) { if (_filter != NULL) g_free((void *)_filter); return; } + + static void init (void) { + 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" + "<_param name=\"cyanOffset\" type=\"description\" appearance=\"header\">" N_("Cyan offset") "</_param>\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" + "<_param name=\"magentaOffset\" type=\"description\" appearance=\"header\">" N_("Magenta offset") "</_param>\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" + "<_param name=\"yellowOffset\" type=\"description\" appearance=\"header\">" N_("Yellow offset") "</_param>\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 != NULL) 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(), - rx.str().c_str(), ry.str().c_str(), source.str().c_str(), blend.str().c_str(), - gx.str().c_str(), gy.str().c_str(), blend.str().c_str(), - bx.str().c_str(), by.str().c_str(), blend.str().c_str(), - composite.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 filter */ +}; /* Nudge CMY filter */ /** \brief Custom predefined Quadritone filter. @@ -1325,14 +1407,14 @@ public: "<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=\"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=\"enum\">\n" "<_item value=\"normal\">" N_("Normal") "</_item>\n" "<_item value=\"multiply\">" N_("Multiply") "</_item>\n" "<_item value=\"screen\">" N_("Screen") "</_item>\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=\"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=\"enum\">\n" "<_item value=\"normal\">" N_("Normal") "</_item>\n" "<_item value=\"screen\">" N_("Screen") "</_item>\n" @@ -1411,7 +1493,7 @@ public: "<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=\"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=\"enum\">\n" "<_item value=\"solarize\">" N_("Solarize") "</_item>\n" "<_item value=\"moonarize\">" N_("Moonarize") "</_item>\n" @@ -1513,14 +1595,14 @@ public: "<_item value=\"multiply\">" N_("Multiply") "</_item>\n" "<_item value=\"darken\">" N_("Darken") "</_item>\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=\"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=\"enum\">\n" "<_item value=\"normal\">" N_("Normal") "</_item>\n" "<_item value=\"multiply\">" N_("Multiply") "</_item>\n" "<_item value=\"darken\">" N_("Darken") "</_item>\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" + "<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" diff --git a/src/extension/internal/filter/distort.h b/src/extension/internal/filter/distort.h index 415762466..586d34226 100644 --- a/src/extension/internal/filter/distort.h +++ b/src/extension/internal/filter/distort.h @@ -70,10 +70,10 @@ public: "<_item value=\"in\">" N_("In") "</_item>\n" "<_item value=\"out\">" N_("Out") "</_item>\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=\"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=\"enum\">\n" "<_item value=\"atop\">" N_("Normal") "</_item>\n" "<_item value=\"over\">" N_("Wide") "</_item>\n" @@ -84,11 +84,11 @@ public: "<_item value=\"fractalNoise\">" N_("Fractal noise") "</_item>\n" "<_item value=\"turbulence\">" N_("Turbulence") "</_item>\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" + "<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" @@ -193,11 +193,11 @@ public: "<_item value=\"fractalNoise\">" N_("Fractal noise") "</_item>\n" "<_item value=\"turbulence\">" N_("Turbulence") "</_item>\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" + "<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" diff --git a/src/extension/internal/filter/filter-all.cpp b/src/extension/internal/filter/filter-all.cpp index b5d47ae45..0273d1669 100755..100644 --- a/src/extension/internal/filter/filter-all.cpp +++ b/src/extension/internal/filter/filter-all.cpp @@ -64,7 +64,8 @@ Filter::filters_all (void ) Invert::init(); Lighting::init(); LightnessContrast::init(); - Nudge::init(); + NudgeRGB::init(); + NudgeCMY::init(); Quadritone::init(); Solarize::init(); Tritone::init(); diff --git a/src/extension/internal/filter/filter-file.cpp b/src/extension/internal/filter/filter-file.cpp index d129f590c..48e64f089 100644 --- a/src/extension/internal/filter/filter-file.cpp +++ b/src/extension/internal/filter/filter-file.cpp @@ -20,6 +20,7 @@ /* System includes */ #include <glibmm/i18n.h> +#include <glibmm/fileutils.h> namespace Inkscape { namespace Extension { @@ -43,7 +44,7 @@ void Filter::filters_load_dir (gchar const * dirname, gchar * menuname) { if (!dirname) { - g_warning(_("Null external module directory name. Filters will not be loaded.")); + g_warning("%s", _("Null external module directory name. Filters will not be loaded.")); return; } diff --git a/src/extension/internal/filter/filter.cpp b/src/extension/internal/filter/filter.cpp index af597685b..a2c565699 100644 --- a/src/extension/internal/filter/filter.cpp +++ b/src/extension/internal/filter/filter.cpp @@ -144,11 +144,11 @@ void Filter::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::Vie if (filter == NULL) { Inkscape::XML::Node * newfilterroot = xmldoc->createElement("svg:filter"); + merge_filters(newfilterroot, filterdoc->root(), xmldoc); defsrepr->appendChild(newfilterroot); Glib::ustring url = "url(#"; url += newfilterroot->attribute("id"); url += ")"; - merge_filters(newfilterroot, filterdoc->root(), xmldoc); Inkscape::GC::release(newfilterroot); @@ -172,7 +172,7 @@ void Filter::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::Vie // no filter if (filternode == NULL) { - g_warning("no assoziating filter found!"); + g_warning("no assigned filter found!"); continue; } diff --git a/src/extension/internal/filter/filter.h b/src/extension/internal/filter/filter.h index 1e8a3be0f..9aaa84aec 100644 --- a/src/extension/internal/filter/filter.h +++ b/src/extension/internal/filter/filter.h @@ -19,7 +19,7 @@ namespace Inkscape { namespace XML { - class Document; + struct Document; } namespace Extension { diff --git a/src/extension/internal/filter/image.h b/src/extension/internal/filter/image.h index 60e1a1665..00cd2cf21 100644 --- a/src/extension/internal/filter/image.h +++ b/src/extension/internal/filter/image.h @@ -53,7 +53,7 @@ public: "<_item value=\"vertical\">" N_("Vertical lines") "</_item>\n" "<_item value=\"horizontal\">" N_("Horizontal lines") "</_item>\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=\"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=\"boolean\" >false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" diff --git a/src/extension/internal/filter/morphology.h b/src/extension/internal/filter/morphology.h index 123d912b0..7e10884a1 100644 --- a/src/extension/internal/filter/morphology.h +++ b/src/extension/internal/filter/morphology.h @@ -62,11 +62,11 @@ public: "<_item value=\"over\">" N_("Outer") "</_item>\n" "<_item value=\"xor\">" N_("Open") "</_item>\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=\"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=\"boolean\" >false</param>\n" "<effect>\n" @@ -181,13 +181,13 @@ public: "<_item value=\"outside\">" N_("Outside") "</_item>\n" "<_item value=\"overlayed\">" N_("Overlayed") "</_item>\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=\"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=\"boolean\" >false</param>\n" "</page>\n" "<page name=\"co11tab\" _gui-text=\"Color\">\n" diff --git a/src/extension/internal/filter/paint.h b/src/extension/internal/filter/paint.h index debdd7d79..941177f39 100644 --- a/src/extension/internal/filter/paint.h +++ b/src/extension/internal/filter/paint.h @@ -3,7 +3,7 @@ /* Change the 'PAINT' above to be your file name */ /* - * Copyright (C) 2011 Authors: + * Copyright (C) 2012 Authors: * Ivan Louette (filters) * Nicolas Dufour (UI) <nicoduf@yahoo.fr> * @@ -83,19 +83,19 @@ public: "<param name=\"transparent\" gui-text=\"" N_("Transparent") "\" type=\"boolean\" >false</param>\n" "<param name=\"dented\" gui-text=\"" N_("Dented") "\" type=\"boolean\" >false</param>\n" "<param name=\"inverted\" gui-text=\"" N_("Inverted") "\" type=\"boolean\" >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" + "<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" + "<page name=\"graintab\" _gui-text=\"" N_("Grain") "\">\n" "<param name=\"grain\" gui-text=\"" N_("Grain mode") "\" type=\"boolean\" >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=\"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=\"boolean\" >true</param>\n" "<param name=\"gblend\" gui-text=\"" N_("Grain blend:") "\" type=\"enum\">\n" "<_item value=\"normal\">Normal</_item>\n" @@ -133,7 +133,6 @@ Chromolitho::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream noise; std::ostringstream dblend; std::ostringstream smooth; - std::ostringstream grain; std::ostringstream grainxf; std::ostringstream grainyf; std::ostringstream grainc; @@ -232,11 +231,11 @@ public: "<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=\"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=\"boolean\" >false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -334,18 +333,18 @@ public: "<param name=\"tab\" type=\"notebook\">\n" "<page name=\"optionstab\" _gui-text=\"Options\">\n" "<_param name=\"simplifyheader\" type=\"description\" appearance=\"header\">" N_("Simplify") "</_param>\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=\"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=\"boolean\" >false</param>\n" "<_param name=\"smoothheader\" type=\"description\" appearance=\"header\">" N_("Smoothness") "</_param>\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" + "<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" "<_param name=\"meltheader\" type=\"description\" appearance=\"header\">" N_("Melt") "</_param>\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" + "<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" @@ -354,7 +353,7 @@ public: "<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=\"boolean\" >false</param>\n" - "<param name=\"offset\" gui-text=\"" N_("Offset:") "\" type=\"int\" appearance=\"full\" min=\"-100\" max=\"100\">0</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" @@ -494,12 +493,12 @@ public: "<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=\"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=\"enum\">\n" "<_item value=\"table\">" N_("Table") "</_item>\n" "<_item value=\"discrete\">" N_("Discrete") "</_item>\n" "</param>\n" - "<param name=\"levels\" gui-text=\"" N_("Levels:") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"10\">3</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=\"boolean\">false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -563,13 +562,13 @@ Electrize::get_filter_text (Inkscape::Extension::Extension * ext) Filter's parameters: * Lines type (enum, default smooth) -> - smooth = component1 (type="table"), component2 (type="table"), composite1 (in2="blur2") - hard = component1 (type="discrete"), component2 (type="discrete"), composite1 (in2="component1") - * Simplify (0.01->20., default 1.5) -> blur1 (stdDeviation) - * Line width (0.01->20., default 1.5) -> blur2 (stdDeviation) - * Lightness (0.->10., default 0.5) -> composite1 (k3) + 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) -> composite1 (true: in2="component2") + * Dark mode (boolean, default false) -> composite2 (true: in2="component2") */ class NeonDraw : public Inkscape::Extension::Internal::Filter::Filter { protected: @@ -588,15 +587,14 @@ public: "<_item value=\"table\">" N_("Smoothed") "</_item>\n" "<_item value=\"discrete\">" N_("Contrasted") "</_item>\n" "</param>\n" - "<param name=\"simply\" gui-text=\"" N_("Simplify:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">1.5</param>\n" - "<param name=\"width\" gui-text=\"" N_("Line width:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">1.5</param>\n" - "<param name=\"lightness\" gui-text=\"" N_("Lightness:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"10.00\">0.5</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=\"enum\">\n" "<_item value=\"normal\">Normal</_item>\n" "<_item value=\"multiply\">Multiply</_item>\n" "<_item value=\"screen\">Screen</_item>\n" "</param>\n" - "<param name=\"dark\" gui-text=\"" N_("Dark mode") "\" type=\"boolean\" >false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" "<effects-menu>\n" @@ -620,7 +618,6 @@ NeonDraw::get_filter_text (Inkscape::Extension::Extension * ext) std::ostringstream width; std::ostringstream lightness; std::ostringstream type; - std::ostringstream dark; type << ext->get_param_enum("type"); blend << ext->get_param_enum("blend"); @@ -628,33 +625,26 @@ NeonDraw::get_filter_text (Inkscape::Extension::Extension * ext) width << ext->get_param_float("width"); lightness << ext->get_param_float("lightness"); - const gchar *typestr = ext->get_param_enum("type"); - if (ext->get_param_bool("dark")) { - dark << "component2"; - } else if ((g_ascii_strcasecmp("table", typestr) == 0)) { - dark << "blur2"; - } else { - dark << "component1"; - } - _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.6 1 1\" />\n" - "<feFuncG type=\"discrete\" tableValues=\"0 0.3 0.6 1 1\" />\n" - "<feFuncB type=\"discrete\" tableValues=\"0 0.3 0.6 1 1\" />\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" - "<feComponentTransfer in=\"blur2\" result=\"component2\">\n" - "<feFuncR type=\"%s\" tableValues=\"0 1 0 1 0 1 0 1\" />\n" - "<feFuncG type=\"%s\" tableValues=\"0 1 0 1 0 1 0 1\" />\n" - "<feFuncB type=\"%s\" tableValues=\"0 1 0 1 0 1 0 1\" />\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=\"%s\" k3=\"%s\" operator=\"arithmetic\" k2=\"1\" result=\"composite1\" />\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(), dark.str().c_str(), lightness.str().c_str()); + "</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 */ @@ -702,11 +692,11 @@ public: "<_item value=\"fractalNoise\">" N_("Fractal noise") "</_item>\n" "<_item value=\"turbulence\">" N_("Turbulence") "</_item>\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=\"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=\"enum\">\n" "<_item value=\"multiply\">" N_("Multiply") "</_item>\n" "<_item value=\"normal\">" N_("Normal") "</_item>\n" @@ -714,10 +704,10 @@ public: "<_item value=\"lighten\">" N_("Lighten") "</_item>\n" "<_item value=\"darken\">" N_("Darken") "</_item>\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" + "<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" @@ -867,7 +857,7 @@ public: "<_item value=\"discrete\">" N_("Poster") "</_item>\n" "<_item value=\"table\">" N_("Painting") "</_item>\n" "</param>\n" - "<param name=\"levels\" gui-text=\"" N_("Levels:") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"15\">5</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=\"enum\">\n" "<_item value=\"lighten\">Lighten</_item>\n" "<_item value=\"normal\">Normal</_item>\n" @@ -875,10 +865,10 @@ public: "<_item value=\"multiply\">Multiply</_item>\n" "<_item value=\"screen\">Screen</_item>\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=\"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=\"boolean\">false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -982,8 +972,8 @@ public: "<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" + "<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" @@ -1009,9 +999,8 @@ PosterizeBasic::get_filter_text (Inkscape::Extension::Extension * ext) transf << "0"; int levels = ext->get_param_int("levels") + 1; - float val = 0.0; for ( int step = 1 ; step <= levels ; step++ ) { - val = (float) step / levels; + const float val = (float) step / levels; transf << " " << val; } transf << " 1"; diff --git a/src/extension/internal/filter/protrusions.h b/src/extension/internal/filter/protrusions.h index 8ba35db62..965a1bdc5 100644 --- a/src/extension/internal/filter/protrusions.h +++ b/src/extension/internal/filter/protrusions.h @@ -47,7 +47,7 @@ public: "<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" + "<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" diff --git a/src/extension/internal/filter/shadows.h b/src/extension/internal/filter/shadows.h index d76358a96..0d54950bf 100644 --- a/src/extension/internal/filter/shadows.h +++ b/src/extension/internal/filter/shadows.h @@ -3,7 +3,7 @@ /* Change the 'SHADOWS' above to be your file name */ /* - * Copyright (C) 2011 Authors: + * Copyright (C) 2013 Authors: * Ivan Louette (filters) * Nicolas Dufour (UI) <nicoduf@yahoo.fr> * @@ -39,6 +39,7 @@ namespace Filter { 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) */ @@ -57,14 +58,15 @@ public: "<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_("Blur type:") "\" type=\"enum\" >\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=\"enum\" >\n" "<_item value=\"outer\">" N_("Outer") "</_item>\n" "<_item value=\"inner\">" N_("Inner") "</_item>\n" "<_item value=\"outercut\">" N_("Outer cutout") "</_item>\n" "<_item value=\"innercut\">" N_("Inner cutout") "</_item>\n" + "<_item value=\"shadow\">" N_("Shadow only") "</_item>\n" "</param>\n" "</page>\n" "<page name=\"coltab\" _gui-text=\"" N_("Blur color") "\">\n" @@ -151,11 +153,16 @@ ColorizableDropShadow::get_filter_text (Inkscape::Extension::Extension * ext) comp2op << "out"; comp2in1 << "offset"; comp2in2 << "SourceGraphic"; - } else { //innercut + } 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( diff --git a/src/extension/internal/filter/transparency.h b/src/extension/internal/filter/transparency.h index b29a0a1b3..dcbae26b5 100644 --- a/src/extension/internal/filter/transparency.h +++ b/src/extension/internal/filter/transparency.h @@ -129,10 +129,10 @@ public: "<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=\"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=\"boolean\">false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -204,9 +204,9 @@ public: "<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=\"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=\"boolean\">false</param>\n" "<effect>\n" "<object-type>all</object-type>\n" @@ -278,9 +278,9 @@ public: "<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" + "<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" @@ -340,7 +340,7 @@ public: "<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=\"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=\"boolean\">false</param>\n" "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">255</param>\n" "<effect>\n" diff --git a/src/extension/internal/gdkpixbuf-input.cpp b/src/extension/internal/gdkpixbuf-input.cpp index 80be2ecc2..a384c7bde 100644 --- a/src/extension/internal/gdkpixbuf-input.cpp +++ b/src/extension/internal/gdkpixbuf-input.cpp @@ -1,6 +1,7 @@ #ifdef HAVE_CONFIG_H # include <config.h> #endif +#include <boost/scoped_ptr.hpp> #include <glib/gprintf.h> #include <glibmm/i18n.h> #include "document-private.h" @@ -12,96 +13,111 @@ #include "selection-chemistry.h" #include "sp-image.h" #include "document-undo.h" +#include "util/units.h" +#include "image-resolution.h" +#include "display/cairo-utils.h" #include <set> namespace Inkscape { -namespace IO { -GdkPixbuf* pixbuf_new_from_file( char const *utf8name, GError **error ); -} - namespace Extension { namespace Internal { -static std::set<Glib::ustring> create_lossy_set() -{ - std::set<Glib::ustring> lossy; - lossy.insert(".jpg"); - lossy.insert(".jpeg"); - return lossy; -} - SPDocument * GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) { - bool embed = false; + // Determine whether the image should be embedded Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - Glib::ustring attr = prefs->getString("/dialogs/import/link"); - if (strcmp(attr.c_str(), "embed") == 0) { - embed = true; - } else if (strcmp(attr.c_str(), "link") == 0) { - embed = false; - } else { - embed = (strcmp(mod->get_param_optiongroup("link"), "embed") == 0); - if (mod->get_param_bool("ask")) { - prefs->setString("/dialogs/import/link", mod->get_param_optiongroup("link")); - mod->set_param_bool("ask", false); + bool ask = prefs->getBool("/dialogs/import/ask"); + Glib::ustring link = prefs->getString("/dialogs/import/link"); + bool forcexdpi = prefs->getBool("/dialogs/import/forcexdpi"); + Glib::ustring scale = prefs->getString("/dialogs/import/scale"); + // std::cout << "GkdpixbufInput::open: " + // << " ask: " << ask + // << ", link: " << link + // << ", forcexdpi: " << forcexdpi + // << ", scale: " << scale << std::endl; + // std::cout << " in preferences: " + // << " ask: " << !mod->get_param_bool("do_not_ask") + // << ", link: " << mod->get_param_optiongroup("link") + // << ", mod_dpi: " << mod->get_param_optiongroup("dpi") + // << ", scale: " << mod->get_param_optiongroup("scale") << std::endl; + if( ask ) { + Glib::ustring mod_link = mod->get_param_optiongroup("link"); + Glib::ustring mod_dpi = mod->get_param_optiongroup("dpi"); + bool mod_forcexdpi = ( mod_dpi.compare( "from_default" ) == 0 ); + Glib::ustring mod_scale = mod->get_param_optiongroup("scale"); + if( link.compare( mod_link ) != 0 ) { + link = mod_link; + } + prefs->setString("/dialogs/import/link", link ); + if( forcexdpi != mod_forcexdpi ) { + forcexdpi = mod_forcexdpi; + } + prefs->setBool("/dialogs/import/forcexdpi", forcexdpi ); + if( scale.compare( mod_scale ) != 0 ) { + scale = mod_scale; } + prefs->setString("/dialogs/import/scale", scale ); + prefs->setBool("/dialogs/import/ask", !mod->get_param_bool("do_not_ask") ); } - + bool embed = ( link.compare( "embed" ) == 0 ); + SPDocument *doc = NULL; - GdkPixbuf *pb = Inkscape::IO::pixbuf_new_from_file( uri, NULL ); - static std::set<Glib::ustring> lossy = create_lossy_set(); - - if (pb) { /* We are readable */ - // TODO revisit: bool is_lossy; - Glib::ustring mime_type, ext; - Glib::ustring u = uri; - std::size_t dotpos = u.rfind('.'); - if (dotpos != Glib::ustring::npos) { - ext = u.substr(dotpos, Glib::ustring::npos); - } + boost::scoped_ptr<Inkscape::Pixbuf> pb(Inkscape::Pixbuf::create_from_file(uri)); - // HACK: replace with something better based on GIO - if (!ext.empty() && lossy.find(ext) != lossy.end()) { - // TODO revisit: is_lossy = true; - mime_type = "image/jpeg"; - } else { - // TODO revisit: is_lossy = false; - mime_type = "image/png"; - } + // 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(NULL, TRUE, TRUE); bool saved = DocumentUndo::getUndoSensitive(doc); DocumentUndo::setUndoSensitive(doc, false); // no need to undo in this temporary document - double width = gdk_pixbuf_get_width(pb); - double height = gdk_pixbuf_get_height(pb); - gchar const *str = gdk_pixbuf_get_option( pb, "Inkscape::DpiX" ); - if ( str ) { - gint dpi = atoi(str); - if ( dpi > 0 && dpi != 72 ) { - double scale = 72.0 / (double)dpi; - width *= scale; - } + double width = pb->width(); + double height = pb->height(); + double defaultxdpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", Inkscape::Util::Quantity::convert(1, "in", "px")); + //bool forcexdpi = prefs->getBool("/dialogs/import/forcexdpi"); + ImageResolution *ir = 0; + double xscale = 1; + double yscale = 1; + + + if (!ir && !forcexdpi) { + ir = new ImageResolution(uri); } - str = gdk_pixbuf_get_option( pb, "Inkscape::DpiY" ); - if ( str ) { - gint dpi = atoi(str); - if ( dpi > 0 && dpi != 72 ) { - double scale = 72.0 / (double)dpi; - height *= scale; - } + if (ir && ir->ok()) { + xscale = 900.0 / floor(10.*ir->x() + .5); // round-off to 0.1 dpi + yscale = 900.0 / floor(10.*ir->y() + .5); + } else { + xscale = 90.0 / defaultxdpi; + yscale = 90.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); + // Added 11 Feb 2014 as we now honor "preserveAspectRatio" and this is + // what Inkscaper's expect. + image_node->setAttribute("preserveAspectRatio", "none"); + + 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, mime_type); + sp_embed_image(image_node, pb.get()); } else { // convert filename to uri gchar* _uri = g_filename_to_uri(uri, NULL, NULL); @@ -113,12 +129,17 @@ GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) } } - g_object_unref(pb); - // Add it to the current layer doc->getRoot()->appendChildRepr(image_node); Inkscape::GC::release(image_node); fit_canvas_to_drawing(doc); + + // Set viewBox if it doesn't exist + if (!doc->getRoot()->viewBox_set) { + std::cout << "Viewbox not set, setting" << std::endl; + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDefaultUnit()), doc->getHeight().value(doc->getDefaultUnit()))); + } + // restore undo, as now this document may be shown to the user if a bitmap was opened DocumentUndo::setUndoSensitive(doc, saved); } else { @@ -166,12 +187,24 @@ GdkpixbufInput::init(void) "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" "<name>%s</name>\n" "<id>org.inkscape.input.gdkpixbuf.%s</id>\n" - "<param name='link' type='optiongroup' appearance='full' _gui-text='" N_("Link or embed image:") "' >\n" + + "<param name='link' type='optiongroup' appearance='full' _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='help' type='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.") "</_param>\n" - "<param name=\"ask\" _gui-description='" N_("Hide the dialog next time and always apply the same action.") "' gui-text=\"" N_("Don't ask again") "\" type=\"boolean\" >false</param>\n" + + "<param name='dpi' type='optiongroup' appearance='full' _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' appearance='full' _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=\"boolean\" >false</param>\n" "<input>\n" "<extension>.%s</extension>\n" "<mimetype>%s</mimetype>\n" diff --git a/src/extension/internal/grid.cpp b/src/extension/internal/grid.cpp index 56bce2fe8..0059bbec2 100644 --- a/src/extension/internal/grid.cpp +++ b/src/extension/internal/grid.cpp @@ -11,6 +11,14 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include <glibmm/threads.h> +#endif + #include <gtkmm/box.h> #include <gtkmm/adjustment.h> #include <gtkmm/spinbutton.h> @@ -21,12 +29,14 @@ #include "selection.h" #include "sp-object.h" #include "util/glib-list-iterators.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" @@ -49,31 +59,31 @@ Grid::load (Inkscape::Extension::Extension */*module*/) namespace { Glib::ustring build_lines(Geom::Rect bounding_area, - float offset[], float spacing[]) + Geom::Point const &offset, Geom::Point const &spacing) { Geom::Point point_offset(0.0, 0.0); SVG::PathString path_data; - for ( int axis = 0 ; axis < 2 ; ++axis ) { + 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]) { + 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); + .lineTo(end_point + point_offset); } } - // std::cout << "Path data:" << path_data.c_str() << std::endl; - return path_data; - } - + // 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) @@ -89,30 +99,31 @@ Grid::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc /* get page size */ SPDocument * doc = document->doc(); bounding_area = Geom::Rect( Geom::Point(0,0), - Geom::Point(doc->getWidth(), doc->getHeight()) ); + 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(); + 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; } - float spacings[2] = { module->get_param_float("xspacing"), - module->get_param_float("yspacing") }; - float line_width = module->get_param_float("lineWidth"); - float offsets[2] = { module->get_param_float("xoffset"), - module->get_param_float("yoffset") }; + gdouble scale = Inkscape::Util::Quantity::convert(1, "px", (document->doc())->getDefaultUnit()); + 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); + 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. @@ -132,9 +143,7 @@ Grid::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc path->setAttribute("style", style.c_str()); current_layer->appendChild(path); - Inkscape::GC::release(path); - - return; + Inkscape::GC::release(path); } /** \brief A class to make an adjustment that uses Extension params */ diff --git a/src/extension/internal/image-resolution.cpp b/src/extension/internal/image-resolution.cpp new file mode 100644 index 000000000..18eb97f80 --- /dev/null +++ b/src/extension/internal/image-resolution.cpp @@ -0,0 +1,380 @@ +/* + * Authors: + * Daniel Wagenaar <daw@caltech.edu> + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#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 <setjmp.h> +#endif + +#ifdef WITH_IMAGE_MAGICK +#include <Magick++.h> +#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, 0, 0, 0); + if (!png_ptr) + return; + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_read_struct(&png_ptr, 0, 0); + return; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + fclose(fp); + return; + } + + png_init_io(png_ptr, fp); + png_read_info(png_ptr, info_ptr); + + png_uint_32 res_x, res_y; + int unit_type; + png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type); + + png_destroy_read_struct(&png_ptr, &info_ptr, 0); + fclose(fp); + + if (unit_type == PNG_RESOLUTION_METER) { + ok_ = true; + x_ = res_x * 2.54 / 100; + y_ = res_y * 2.54 / 100; + } +} +#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); +} + +#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; +} + +#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); + + 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); +} + +#else + +// Dummy implementation +void ImageResolution::readjfif(char const *) { +} + +#endif + +#ifdef WITH_IMAGE_MAGICK +void ImageResolution::readmagick(char const *fn) { + Magick::Image image; + try { + image.read(fn); + } catch (Magick::Error & err) { + g_warning("ImageMagick error: %s", err.what()); + return; + } catch (...) { + g_warning("ImageResolution::readmagick: Unknown error"); + return; + } + Magick::Geometry geo = image.density(); + std::string type = image.magick(); + + if (type == "PNG") { // PNG only supports pixelspercentimeter + x_ = Inkscape::Util::Quantity::convert((double)geo.width(), "in", "cm"); + y_ = Inkscape::Util::Quantity::convert((double)geo.height(), "in", "cm"); + } else { + x_ = (double)geo.width(); + y_ = (double)geo.height(); + } + + ok_ = true; +} + +#else + +// Dummy implementation +void ImageResolution::readmagick(char const *) { +} + +#endif /* WITH_IMAGE_MAGICK */ + +} +} +} diff --git a/src/extension/internal/image-resolution.h b/src/extension/internal/image-resolution.h new file mode 100644 index 000000000..6c4928a35 --- /dev/null +++ b/src/extension/internal/image-resolution.h @@ -0,0 +1,41 @@ +/* + * Authors: + * Daniel Wagenaar <daw@caltech.edu> + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL, 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/javafx-out.cpp b/src/extension/internal/javafx-out.cpp index 67c0fae66..19946022c 100644 --- a/src/extension/internal/javafx-out.cpp +++ b/src/extension/internal/javafx-out.cpp @@ -20,6 +20,8 @@ #ifdef HAVE_CONFIG_H # include <config.h> #endif + +#include <glibmm/miscutils.h> #include "javafx-out.h" #include <inkscape.h> #include <inkscape-version.h> @@ -394,12 +396,14 @@ bool JavaFXOutput::doStyle(SPStyle *style) } else if (fill.isPaintserver()){ if (fill.value.href && fill.value.href->getURI() ){ - String uri = fill.value.href->getURI()->toString(); + gchar *str = fill.value.href->getURI()->toString(); + String uri = (str ? str : ""); /* trim the anchor '#' from the front */ if (uri.size() > 0 && uri[0]=='#') { uri = uri.substr(1); } out(" fill: %s()\n", sanatize(uri).c_str()); + g_free(str); } } @@ -414,10 +418,8 @@ bool JavaFXOutput::doStyle(SPStyle *style) * SPIEnum stroke_linecap; * SPIEnum stroke_linejoin; * SPIFloat stroke_miterlimit; - * NRVpathDash stroke_dash; - * unsigned stroke_dasharray_set : 1; - * unsigned stroke_dasharray_inherit : 1; - * unsigned stroke_dashoffset_set : 1; + * SPIDashArray stroke_dasharray; + * SPILength stroke_dashoffset; * SPIScale24 stroke_opacity; */ if (style->stroke_opacity.value > 0) @@ -432,16 +434,16 @@ bool JavaFXOutput::doStyle(SPStyle *style) out(" strokeLineCap: %s\n", getStrokeLineCap(linecap).c_str()); out(" strokeLineJoin: %s\n", getStrokeLineJoin(linejoin).c_str()); out(" strokeMiterLimit: %s\n", DSTR(style->stroke_miterlimit.value)); - if (style->stroke_dasharray_set) { - if (style->stroke_dashoffset_set) { - out(" strokeDashOffset: %s\n", DSTR(style->stroke_dash.offset)); + if (style->stroke_dasharray.set) { + if (style->stroke_dashoffset.set) { + out(" strokeDashOffset: %s\n", DSTR(style->stroke_dashoffset.value)); } out(" strokeDashArray: [ "); - for(int i = 0; i < style->stroke_dash.n_dash; i++ ) { + for(unsigned i = 0; i < style->stroke_dasharray.values.size(); i++ ) { if (i > 0) { - out(", %.2lf", style->stroke_dash.dash[i]); + out(", %.2lf", style->stroke_dasharray.values[i]); }else { - out(" %.2lf", style->stroke_dash.dash[i]); + out(" %.2lf", style->stroke_dasharray.values[i]); } } out(" ]\n"); diff --git a/src/extension/internal/latex-pstricks.cpp b/src/extension/internal/latex-pstricks.cpp index c1eddf539..6aaa1bca4 100644 --- a/src/extension/internal/latex-pstricks.cpp +++ b/src/extension/internal/latex-pstricks.cpp @@ -21,7 +21,7 @@ #include <2geom/hvlinesegment.h> #include <errno.h> #include <signal.h> -#include <unit-constants.h> +#include "util/units.h" #include "helper/geom-curves.h" #include "extension/print.h" @@ -117,8 +117,8 @@ unsigned int PrintLatex::begin (Inkscape::Extension::Print *mod, SPDocument *doc } // width and height in pt - _width = doc->getWidth() * PT_PER_PX; - _height = doc->getHeight() * PT_PER_PX; + _width = doc->getWidth().value("pt"); + _height = doc->getHeight().value("pt"); if (res >= 0) { @@ -128,10 +128,10 @@ unsigned int PrintLatex::begin (Inkscape::Extension::Print *mod, SPDocument *doc 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() << "," << doc->getHeight() << ")\n"; + 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())); /// @fixme hardcoded doc2dt transform + 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()); } @@ -239,16 +239,13 @@ unsigned int PrintLatex::stroke(Inkscape::Extension::Print * /*mod*/, os<<",strokeopacity="<<stroke_opacity; } - if (style->stroke_dasharray_set && - style->stroke_dash.n_dash && - style->stroke_dash.dash) { - int i; + if (style->stroke_dasharray.set && !style->stroke_dasharray.values.empty()) { os << ",linestyle=dashed,dash="; - for (i = 0; i < style->stroke_dash.n_dash; i++) { + for (unsigned i = 0; i < style->stroke_dasharray.values.size(); i++) { if ((i)) { os << " "; } - os << style->stroke_dash.dash[i]; + os << style->stroke_dasharray.values[i]; } } diff --git a/src/extension/internal/latex-text-renderer.cpp b/src/extension/internal/latex-text-renderer.cpp index ebd73a033..398c9f061 100644 --- a/src/extension/internal/latex-text-renderer.cpp +++ b/src/extension/internal/latex-text-renderer.cpp @@ -39,7 +39,7 @@ #include "sp-rect.h" #include "text-editing.h" -#include <unit-constants.h> +#include "util/units.h" #include "extension/system.h" @@ -56,7 +56,7 @@ namespace Internal { */ bool latex_render_document_text_to_file( SPDocument *doc, gchar const *filename, - const gchar * const exportId, bool exportDrawing, bool exportCanvas, + const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px, bool pdflatex) { doc->ensureUpToDate(); @@ -84,7 +84,7 @@ latex_render_document_text_to_file( SPDocument *doc, gchar const *filename, bool ret = renderer->setTargetFile(filename); if (ret) { /* Render document */ - bool ret = renderer->setupDocument(doc, pageBoundingBox, base); + bool ret = renderer->setupDocument(doc, pageBoundingBox, bleedmargin_px, base); if (ret) { renderer->renderItem(base); } @@ -98,7 +98,9 @@ latex_render_document_text_to_file( SPDocument *doc, gchar const *filename, LaTeXTextRenderer::LaTeXTextRenderer(bool pdflatex) : _stream(NULL), _filename(NULL), - _pdflatex(pdflatex) + _pdflatex(pdflatex), + _omittext_state(EMPTY), + _omittext_page(1) { push_transform(Geom::identity()); } @@ -262,6 +264,11 @@ LaTeXTextRenderer::sp_use_render(SPItem *item) void LaTeXTextRenderer::sp_text_render(SPItem *item) { + // 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; + SPText *textobj = SP_TEXT (item); SPStyle *style = item->style; @@ -395,6 +402,11 @@ 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; + SPFlowtext *flowtext = SP_FLOWTEXT(item); SPStyle *style = item->style; @@ -405,7 +417,7 @@ Flowing in rectangle is possible, not in arb shape. } SPRect *frame = SP_RECT(frame_item); - Geom::Rect framebox = sp_rect_get_rect(frame) * transform(); + Geom::Rect framebox = frame->getRect() * transform(); // get position and alignment // Align on topleft corner. @@ -556,8 +568,13 @@ LaTeXTextRenderer::sp_item_invoke_render(SPItem *item) return sp_text_render(item); } else if (SP_IS_FLOWTEXT(item)) { return sp_flowtext_render(item); + } 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; } - // We are not interested in writing the other SPItem types to LaTeX } void @@ -568,8 +585,22 @@ LaTeXTextRenderer::renderItem(SPItem *item) pop_transform(); } +void +LaTeXTextRenderer::writeGraphicPage(void) { + 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, SPItem *base) +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 ! @@ -588,6 +619,7 @@ LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem * } 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()); @@ -601,7 +633,7 @@ LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem * } // flip y-axis - push_transform( Geom::Scale(1,-1) * Geom::Translate(0, doc->getHeight()) ); /// @fixme hardcoded desktop transform! + push_transform( Geom::Scale(1,-1) * Geom::Translate(0, doc->getHeight().value("px")) ); /// @fixme hardcoded desktop transform! // write the info to LaTeX Inkscape::SVGOStringStream os; @@ -610,7 +642,7 @@ LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem * // scaling of the image when including it in LaTeX os << " \\ifx\\svgwidth\\undefined%\n"; - os << " \\setlength{\\unitlength}{" << d.width() * PT_PER_PX << "bp}%\n"; // note: 'bp' is the Postscript pt unit in LaTeX, see LP bug #792384 + 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"; @@ -624,11 +656,12 @@ LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem * os << " \\makeatother%\n"; os << " \\begin{picture}(" << _width << "," << _height << ")%\n"; - // strip pathname, as it is probably desired. Having a specific path in the TeX file is not convenient. - os << " \\put(0,0){\\includegraphics[width=\\unitlength]{" << _filename << "}}%\n"; fprintf(_stream, "%s", os.str().c_str()); + if (!_pdflatex) + writeGraphicPage(); + return true; } diff --git a/src/extension/internal/latex-text-renderer.h b/src/extension/internal/latex-text-renderer.h index 66055a3bc..77d12bc61 100644 --- a/src/extension/internal/latex-text-renderer.h +++ b/src/extension/internal/latex-text-renderer.h @@ -22,14 +22,14 @@ #include <stack> class SPItem; -struct SPRoot; +class SPRoot; 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, + const gchar * const exportId, bool exportDrawing, bool exportCanvas, float bleedmargin_px, bool pdflatex); class LaTeXTextRenderer { @@ -41,17 +41,26 @@ public: /** Initializes the LaTeXTextRenderer according to the specified SPDocument. Important to set the boundingbox to the pdf boundingbox */ - bool setupDocument(SPDocument *doc, bool pageBoundingBox, SPItem *base); + 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 ouputting for pdfLaTeX*/ + LaTeXOmitTextPageState _omittext_state; + gulong _omittext_page; + void push_transform(Geom::Affine const &transform); Geom::Affine const & transform(); void pop_transform(); @@ -60,6 +69,8 @@ protected: void writePreamble(); void writePostamble(); + void writeGraphicPage(); + void sp_item_invoke_render(SPItem *item); void sp_root_render(SPRoot *item); void sp_group_render(SPItem *item); diff --git a/src/extension/internal/metafile-inout.cpp b/src/extension/internal/metafile-inout.cpp new file mode 100644 index 000000000..1d419a6a0 --- /dev/null +++ b/src/extension/internal/metafile-inout.cpp @@ -0,0 +1,211 @@ +/** @file + * @brief Metafile input - common routines + *//* + * Authors: + * David Mathog + * + * Copyright (C) 2013 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#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 "sp-gradient.h" +#include "sp-image.h" +#include "sp-linear-gradient.h" +#include "sp-pattern.h" +#include "sp-radial-gradient.h" +#include "style.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=NULL; // 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 = NULL; + png_infop info_ptr = NULL; + size_t x, y; + png_byte ** row_pointers = NULL; + /* 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, NULL, NULL, NULL); + if (png_ptr == NULL){ + accum->buffer=NULL; + return; + } + + info_ptr = png_create_info_struct (png_ptr); + if (info_ptr == NULL){ + png_destroy_write_struct (&png_ptr, &info_ptr); + accum->buffer=NULL; + return; + } + + /* Set up error handling. */ + + if (setjmp (png_jmpbuf (png_ptr))) { + png_destroy_write_struct (&png_ptr, &info_ptr); + accum->buffer=NULL; + 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, NULL); + + png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL); + + 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); + +} + + +/* 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(void){ + 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 000000000..968773a3a --- /dev/null +++ b/src/extension/internal/metafile-inout.h @@ -0,0 +1,93 @@ +/** @file + * @brief Metafile input - common functions + *//* + * Authors: + * David Mathog + * + * Copyright (C) 2013 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#define PNG_SKIP_SETJMP_CHECK // else any further png.h include blows up in the compiler +#include <png.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <map> +#include <stack> +#include <glibmm/ustring.h> +#include <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. */ +typedef struct { + uint8_t red; + uint8_t green; + uint8_t blue; + uint8_t opacity; +} pixel_t; + +/* A picture. */ + typedef struct { + pixel_t *pixels; + size_t width; + size_t height; +} bitmap_t; + +/* structure to store PNG image bytes */ +typedef struct { + char *buffer; + size_t size; +} MEMPNG, *PMEMPNG; + +class Metafile + : public Inkscape::Extension::Implementation::Implementation +{ +public: + Metafile() {} + ~Metafile(); + +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(void); + + +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 000000000..73d63f27d --- /dev/null +++ b/src/extension/internal/metafile-print.cpp @@ -0,0 +1,493 @@ +/** @file + * @brief Metafile printing - common routines + *//* + * Authors: + * Krzysztof KosiÅ„ski <tweenk.pl@gmail.com> + * + * Copyright (C) 2013 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#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 "sp-gradient.h" +#include "sp-image.h" +#include "sp-linear-gradient.h" +#include "sp-pattern.h" +#include "sp-radial-gradient.h" +#include "style.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +bool PrintMetafile::_ppt_fontfix_read = false; +PrintMetafile::FontfixMap PrintMetafile::_ppt_fixable_fonts; + +PrintMetafile::~PrintMetafile() +{ +#ifndef G_OS_WIN32 + // restore default signal handling for SIGPIPE + (void) signal(SIGPIPE, SIG_DFL); +#endif + return; +} + +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; +} + +bool PrintMetafile::_load_ppt_fontfix_data() //this is not called by any other source files +{ + static bool ppt_fontfix_available = false; + + if (_ppt_fontfix_read) return ppt_fontfix_available; + _ppt_fontfix_read = true; + + // add default entry + _ppt_fixable_fonts.insert(std::make_pair(Glib::ustring(""), FontfixParams())); + + std::string fontfix_path = Glib::build_filename(INKSCAPE_EXTENSIONDIR, "fontfix.conf"); + std::ifstream fontfix_file(fontfix_path.c_str(), std::ios::in); + + if (!fontfix_file.is_open()) { + g_warning("Unable to open PowerPoint fontfix file: %s\n" + "PowerPoint ungrouping compensation in WMF/EMF export will not be available.", + fontfix_path.c_str()); + return (ppt_fontfix_available = false); + } + + char *oldlocale = g_strdup(setlocale(LC_NUMERIC, NULL)); + setlocale(LC_NUMERIC, "C"); + + std::string instr; + while (std::getline(fontfix_file, instr)) { + if (instr[0] == '#') { + continue; + } + // not a comment, get the 4 values from the line + FontfixParams params; + char fontname[128]; + int elements = sscanf(instr.c_str(), "%lf %lf %lf %127[^\n]", + ¶ms.f1, ¶ms.f2, ¶ms.f3, &fontname[0]); + if (elements != 4) { + g_warning("Malformed line in %s: %s\n", fontfix_path.c_str(), instr.c_str()); + continue; + } + _ppt_fixable_fonts.insert(std::make_pair(Glib::ustring(fontname), params)); + } + fontfix_file.close(); // not strictly necessary + + setlocale(LC_NUMERIC, oldlocale); + g_free(oldlocale); + return (ppt_fontfix_available = true); +} + +// Finds font fix parameters for the given fontname. +void PrintMetafile::_lookup_ppt_fontfix(Glib::ustring const &fontname, FontfixParams ¶ms) +{ + if (!_ppt_fontfix_read) _load_ppt_fontfix_data(); + + FontfixMap::iterator f = _ppt_fixable_fonts.find(fontname); + if (f != _ppt_fixable_fonts.end()) { + params = f->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; + sp_color_get_rgb_floatv(&gr->vector.stops[0 ].color, rgbs); + sp_color_get_rgb_floatv(&gr->vector.stops[last].color, 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 = NULL; + *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 != NULL; pat_i = pat_i->ref ? pat_i->ref->getObject() : NULL) { + if (SP_IS_IMAGE(pat_i)) { + *epixbuf = ((SPImage *)pat_i)->pixbuf; + return; + } + char temp[32]; // large enough + temp[31] = '\0'; + strncpy(temp, pat_i->getAttribute("id"), 31); // Some names may be longer than [EW]MFhatch#_###### + hatch_classify(temp, hatchType, hatchColor, bkColor); + if (*hatchType != -1) { + return; + } + + // still looking? Look at this pattern's children, if there are any + SPObject *child = pat_i->firstChild(); + while (child && !(*epixbuf) && (*hatchType == -1)) { + brush_classify(child, depth, epixbuf, hatchType, hatchColor, bkColor); + child = child->getNext(); + } + } + } 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. + SPObject *child = parent->firstChild(); + while (child && !(*epixbuf) && (*hatchType == -1)) { + brush_classify(child, depth, epixbuf, hatchType, hatchColor, bkColor); + child = child->getNext(); + } + } +} + +//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]; + sprintf(text, " 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); + std::vector<Geom::Path> 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]; + sprintf(text, " 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); + std::vector<Geom::Path> 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]; + sprintf(text, " 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); + std::vector<Geom::Path> 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) +{ + std::vector<Geom::Path> 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 000000000..a21f9de58 --- /dev/null +++ b/src/extension/internal/metafile-print.h @@ -0,0 +1,134 @@ +/** @file + * @brief Metafile printing - common functions + *//* + * Authors: + * Krzysztof KosiÅ„ski <tweenk.pl@gmail.com> + * + * Copyright (C) 2013 Authors + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_PRINT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_PRINT_H + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <map> +#include <stack> +#include <glibmm/ustring.h> +#include <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) + FontfixParams() : f1(0), f2(0), f3(0) {} +}; + +class PrintMetafile + : public Inkscape::Extension::Implementation::Implementation +{ +public: + PrintMetafile() {} + ~PrintMetafile(); + + bool textToPath (Inkscape::Extension::Print * ext); + unsigned int bind(Inkscape::Extension::Print *module, Geom::Affine const &transform, float opacity); + unsigned int release(Inkscape::Extension::Print *module); + +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; + 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 bool _load_ppt_fontfix_data(); + 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 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; + +private: + typedef std::map<Glib::ustring, FontfixParams> FontfixMap; + static FontfixMap _ppt_fixable_fonts; + static bool _ppt_fontfix_read; +}; + +} // 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 index 8bb96920d..52fabcf3c 100644 --- a/src/extension/internal/odf.cpp +++ b/src/extension/internal/odf.cpp @@ -4,7 +4,7 @@ * 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 + * 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 continously upgraded for ever improving fidelity. Potential * developers should especially look in preprocess() and writeTree() to see how @@ -15,8 +15,10 @@ * Authors: * Bob Jamison * Abhishek Sharma + * Kris De Gussem * * Copyright (C) 2006, 2007 Bob Jamison + * Copyright (C) 2013 Kris De Gussem * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -45,7 +47,7 @@ #include <stdio.h> #include <time.h> #include <vector> - +#include <math.h> //# Inkscape includes #include "clear-n_.h" @@ -73,37 +75,29 @@ #include "sp-flowtext.h" #include "svg/svg.h" #include "text-editing.h" +#include "util/units.h" +#include "uri.h" -//# DOM-specific includes -#include "dom/dom.h" -#include "dom/util/ziptool.h" -#include "dom/io/domstream.h" -#include "dom/io/bufferstream.h" -#include "dom/io/stringstream.h" - - +#include "inkscape-version.h" #include "document.h" - #include "extension/extension.h" - - +#include "io/inkscapestream.h" +#include "io/bufferstream.h" +#include <util/ziptool.h> +#include <iomanip> namespace Inkscape { namespace Extension { namespace Internal { - - - //# Shorthand notation -typedef org::w3c::dom::DOMString DOMString; -typedef org::w3c::dom::XMLCh XMLCh; -typedef org::w3c::dom::io::OutputStreamWriter OutputStreamWriter; -typedef org::w3c::dom::io::BufferOutputStream BufferOutputStream; -typedef org::w3c::dom::io::StringOutputStream StringOutputStream; +typedef Inkscape::IO::BufferOutputStream BufferOutputStream; +typedef Inkscape::IO::OutputStreamWriter OutputStreamWriter; +typedef Inkscape::IO::StringOutputStream StringOutputStream; + //######################################################################## //# C L A S S SingularValueDecomposition @@ -211,9 +205,11 @@ public: SVDMatrix transpose() { SVDMatrix result(cols, rows); - for (unsigned int i=0 ; i<rows ; i++) - for (unsigned int j=0 ; j<cols ; j++) + 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; } @@ -239,9 +235,11 @@ private: rows = other.rows; cols = other.cols; size = other.size; + badval = other.badval; d = new double[size]; - for (unsigned int i=0 ; i<size ; i++) + for (unsigned int i=0 ; i<size ; i++){ d[i] = other.d[i]; + } } double badval; @@ -289,11 +287,13 @@ public: @return Structure to access U, S and V. */ - SingularValueDecomposition (const SVDMatrix &mat) + SingularValueDecomposition (const SVDMatrix &mat) : + A (mat), + U (), + s (NULL), + s_size (0), + V () { - A = mat; - s = NULL; - s_size = 0; calculate(); } @@ -801,7 +801,6 @@ void SingularValueDecomposition::calculate() } - /** * Return the left singular vectors * @return U @@ -899,7 +898,6 @@ static Glib::ustring getAttribute( Inkscape::XML::Node *node, char const *attrNa } - /** * Get the extension suffix from the end of a file name */ @@ -919,7 +917,6 @@ static Glib::ustring getExtension(const Glib::ustring &fname) return ext; } - static Glib::ustring formatTransform(Geom::Affine &tf) { Glib::ustring str; @@ -935,9 +932,6 @@ static Glib::ustring formatTransform(Geom::Affine &tf) } - - - /** * Get the general transform from SVG pixels to * ODF cm @@ -947,7 +941,7 @@ static Geom::Affine getODFTransform(const SPItem *item) //### Get SVG-to-ODF transform Geom::Affine tf (item->i2dt_affine()); //Flip Y into document coordinates - double doc_height = SP_ACTIVE_DOCUMENT->getHeight(); + double doc_height = SP_ACTIVE_DOCUMENT->getHeight().value("px"); Geom::Affine doc2dt_tf = Geom::Affine(Geom::Scale(1.0, -1.0)); /// @fixme hardcoded desktop transform doc2dt_tf = doc2dt_tf * Geom::Affine(Geom::Translate(0, doc_height)); tf = tf * doc2dt_tf; @@ -956,8 +950,6 @@ static Geom::Affine getODFTransform(const SPItem *item) } - - /** * Get the bounding box of an item, as mapped onto * an ODF document, in cm. @@ -965,7 +957,7 @@ static Geom::Affine getODFTransform(const SPItem *item) static Geom::OptRect getODFBoundingBox(const SPItem *item) { // TODO: geometric or visual? - Geom::OptRect bbox = ((SPItem *)item)->documentVisualBounds(); + Geom::OptRect bbox = item->documentVisualBounds(); if (bbox) { *bbox *= Geom::Affine(Geom::Scale(pxToCm)); } @@ -973,7 +965,6 @@ static Geom::OptRect getODFBoundingBox(const SPItem *item) } - /** * Get the transform for an item, correcting for * handedness reversal @@ -987,7 +978,6 @@ static Geom::Affine getODFItemTransform(const SPItem *item) } - /** * Get some fun facts from the transform */ @@ -1011,16 +1001,9 @@ static void analyzeTransform(Geom::Affine &tf, double s1 = svd.getS(1); xscale = s0; yscale = s1; - //g_message("## s0:%.3f s1:%.3f", s0, s1); - //g_message("## u:%.3f %.3f %.3f %.3f", U(0,0), U(0,1), U(1,0), U(1,1)); - //g_message("## v:%.3f %.3f %.3f %.3f", V(0,0), V(0,1), V(1,0), V(1,1)); - //g_message("## vt:%.3f %.3f %.3f %.3f", Vt(0,0), Vt(0,1), Vt(1,0), Vt(1,1)); - //g_message("## uvt:%.3f %.3f %.3f %.3f", UVt(0,0), UVt(0,1), UVt(1,0), UVt(1,1)); rotate = UVt(0,0); } - - static void gatherText(Inkscape::XML::Node *node, Glib::ustring &buf) { if (node->type() == Inkscape::XML::TEXT_NODE) @@ -1038,15 +1021,14 @@ static void gatherText(Inkscape::XML::Node *node, Glib::ustring &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) +void OdfOutput::preprocess(ZipFile &zf, Inkscape::XML::Node *node) { - Glib::ustring nodeName = node->name(); Glib::ustring id = getAttribute(node, "id"); @@ -1074,18 +1056,16 @@ OdfOutput::preprocess(ZipFile &zf, Inkscape::XML::Node *node) //Now consider items. SPObject *reprobj = SP_ACTIVE_DOCUMENT->getObjectByRepr(node); if (!reprobj) + { return; + } if (!SP_IS_ITEM(reprobj)) - { + { return; - } - SPItem *item = SP_ITEM(reprobj); - //### Get SVG-to-ODF transform - Geom::Affine tf = getODFTransform(item); + } if (nodeName == "image" || nodeName == "svg:image") { - //g_message("image"); Glib::ustring href = getAttribute(node, "xlink:href"); if (href.size() > 0) { @@ -1102,12 +1082,9 @@ OdfOutput::preprocess(ZipFile &zf, Inkscape::XML::Node *node) imageTable[oldName] = newName; Glib::ustring comment = "old name was: "; comment.append(oldName); - URI oldUri(oldName); - //g_message("oldpath:%s", oldUri.getNativePath().c_str()); + Inkscape::URI oldUri(oldName.c_str()); //# if relative to the documentURI, get proper path - URI resUri = documentUri.resolve(oldUri); - DOMString pathName = resUri.getNativePath(); - //g_message("native path:%s", pathName.c_str()); + std::string pathName = documentUri.getFullPath(oldUri.getFullPath("")); ZipEntry *ze = zf.addFile(pathName, comment); if (ze) { @@ -1127,7 +1104,6 @@ OdfOutput::preprocess(ZipFile &zf, Inkscape::XML::Node *node) } - /** * Writes the manifest. Currently it only changes according to the * file names of images packed into the zip file. @@ -1140,25 +1116,25 @@ bool OdfOutput::writeManifest(ZipFile &zf) time_t tim; time(&tim); - outs.printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - outs.printf("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" file: manifest.xml\n"); - outs.printf(" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> - outs.printf(" http://www.inkscape.org\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n"); - outs.printf(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n"); - outs.printf(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n"); - outs.printf(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"styles.xml\"/>\n"); - outs.printf(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n"); - outs.printf(" <!--List our images here-->\n"); + 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) { @@ -1176,7 +1152,7 @@ bool OdfOutput::writeManifest(ZipFile &zf) else if (ext == ".jpg") outs.printf("image/jpeg"); outs.printf("\" manifest:full-path=\""); - outs.printf(newName.c_str()); + outs.writeString(newName.c_str()); outs.printf("\"/>\n"); } outs.printf("</manifest:manifest>\n"); @@ -1204,65 +1180,79 @@ bool OdfOutput::writeMeta(ZipFile &zf) time(&tim); std::map<Glib::ustring, Glib::ustring>::iterator iter; - Glib::ustring creator = "unknown"; + 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 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.printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" file: meta.xml\n"); - outs.printf(" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> - outs.printf(" http://www.inkscape.org\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:document-meta\n"); - outs.printf("xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); - outs.printf("xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); - outs.printf("xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); - outs.printf("xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); - outs.printf("xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); - outs.printf("xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); - outs.printf("xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); - outs.printf("xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); - outs.printf("office:version=\"1.0\">\n"); - outs.printf("<office:meta>\n"); - outs.printf(" <meta:generator>Inkscape.org - 0.45</meta:generator>\n"); - outs.printf(" <meta:initial-creator>%#s</meta:initial-creator>\n", - creator.c_str()); - outs.printf(" <meta:creation-date>%#s</meta:creation-date>\n", date.c_str()); + 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.size() > 0 && value.size()>0) - { - outs.printf(" <%#s>%#s</%#s>\n", - name.c_str(), value.c_str(), name.c_str()); - } + if (!name.empty() && !value.empty()) + { + tmp = Glib::ustring::compose(" <%1>%2</%3>\n", name, value, name); + outs.writeUString(tmp); } - outs.printf(" <meta:editing-cycles>2</meta:editing-cycles>\n"); - outs.printf(" <meta:editing-duration>PT56S</meta:editing-duration>\n"); - outs.printf(" <meta:user-defined meta:name=\"Info 1\"/>\n"); - outs.printf(" <meta:user-defined meta:name=\"Info 2\"/>\n"); - outs.printf(" <meta:user-defined meta:name=\"Info 3\"/>\n"); - outs.printf(" <meta:user-defined meta:name=\"Info 4\"/>\n"); - outs.printf(" <meta:document-statistic meta:object-count=\"2\"/>\n"); - outs.printf("</office:meta>\n"); - outs.printf("</office:document-meta>\n"); - outs.printf("\n"); - outs.printf("\n"); - - + } + // 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 @@ -1274,208 +1264,6 @@ bool OdfOutput::writeMeta(ZipFile &zf) } - - -/** - * This is called just before writeTree(), since it will write style and - * gradient information above the <draw> tag in the content.xml file - */ -bool OdfOutput::writeStyle(ZipFile &zf) -{ - BufferOutputStream bouts; - OutputStreamWriter outs(bouts); - - /* - ========================================================== - Dump our style table. Styles should have a general layout - something like the following. Look in: - http://books.evc-cit.info/odbook/ch06.html#draw-style-file-section - for style and gradient information. - <style:style style:name="gr13" - style:family="graphic" style:parent-style-name="standard"> - <style:graphic-properties draw:stroke="solid" - svg:stroke-width="0.1cm" - svg:stroke-color="#ff0000" - draw:fill="solid" draw:fill-color="#e6e6ff"/> - </style:style> - ========================================================== - */ - outs.printf("<!-- ####### Styles from Inkscape document ####### -->\n"); - std::vector<StyleInfo>::iterator iter; - for (iter = styleTable.begin() ; iter != styleTable.end() ; ++iter) - { - outs.printf("<style:style style:name=\"%s\"", iter->name.c_str()); - StyleInfo s(*iter); - outs.printf(" style:family=\"graphic\" style:parent-style-name=\"standard\">\n"); - outs.printf(" <style:graphic-properties"); - outs.printf(" draw:fill=\"%s\" ", s.fill.c_str()); - if (s.fill != "none") - { - outs.printf(" draw:fill-color=\"%s\" ", s.fillColor.c_str()); - outs.printf(" draw:fill-opacity=\"%s\" ", s.fillOpacity.c_str()); - } - outs.printf(" draw:stroke=\"%s\" ", s.stroke.c_str()); - if (s.stroke != "none") - { - outs.printf(" svg:stroke-width=\"%s\" ", s.strokeWidth.c_str()); - outs.printf(" svg:stroke-color=\"%s\" ", s.strokeColor.c_str()); - outs.printf(" svg:stroke-opacity=\"%s\" ", s.strokeOpacity.c_str()); - } - outs.printf("/>\n"); - outs.printf("</style:style>\n"); - } - - //## Dump our gradient table - int gradientCount = 0; - outs.printf("\n"); - outs.printf("<!-- ####### Gradients from Inkscape document ####### -->\n"); - std::vector<GradientInfo>::iterator giter; - for (giter = gradientTable.begin() ; giter != gradientTable.end() ; ++giter) - { - GradientInfo gi(*giter); - 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 tops for a linear gradient"); - continue; - } - outs.printf("<svg:linearGradient "); - outs.printf("id=\"%#s_g\" ", gi.name.c_str()); - outs.printf("draw:name=\"%#s_g\"\n", gi.name.c_str()); - outs.printf(" draw:display-name=\"imported linear %u\"\n", - gradientCount); - outs.printf(" svg:x1=\"%05.3fcm\" svg:y1=\"%05.3fcm\"\n", - gi.x1, gi.y1); - outs.printf(" svg:x2=\"%05.3fcm\" svg:y2=\"%05.3fcm\"\n", - gi.x2, gi.y2); - outs.printf(" svg:gradientUnits=\"objectBoundingBox\">\n"); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[0].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[0].opacity * 100.0); - outs.printf(" svg:offset=\"0\"/>\n"); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[1].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[1].opacity * 100.0); - outs.printf(" svg:offset=\"1\"/>\n"); - outs.printf("</svg:linearGradient>\n"); - } - 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 tops for a radial gradient"); - continue; - } - outs.printf("<svg:radialGradient "); - outs.printf("id=\"%#s_g\" ", gi.name.c_str()); - outs.printf("draw:name=\"%#s_g\"\n", gi.name.c_str()); - outs.printf(" draw:display-name=\"imported radial %d\"\n", - gradientCount); - outs.printf(" svg:cx=\"%05.3f\" svg:cy=\"%05.3f\"\n", - gi.cx, gi.cy); - outs.printf(" svg:fx=\"%05.3f\" svg:fy=\"%05.3f\"\n", - gi.fx, gi.fy); - outs.printf(" svg:r=\"%05.3f\"\n", - gi.r); - outs.printf(" svg:gradientUnits=\"objectBoundingBox\">\n"); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[0].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[0].opacity * 100.0); - outs.printf(" svg:offset=\"0\"/>\n"); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[1].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[1].opacity * 100.0); - outs.printf(" svg:offset=\"1\"/>\n"); - outs.printf("</svg:radialGradient>\n"); - } - else - { - g_warning("unsupported gradient style '%s'", gi.style.c_str()); - } - outs.printf("<style:style style:name=\"%#s\" style:family=\"graphic\" ", - gi.name.c_str()); - outs.printf("style:parent-style-name=\"standard\">\n"); - outs.printf(" <style:graphic-properties draw:fill=\"gradient\" "); - outs.printf("draw:fill-gradient-name=\"%#s_g\"\n", - gi.name.c_str()); - outs.printf(" draw:textarea-horizontal-align=\"center\" "); - outs.printf("draw:textarea-vertical-align=\"middle\"/>\n"); - outs.printf("</style:style>\n\n"); - - gradientCount++; - } - - outs.printf("\n"); - outs.printf("</office:automatic-styles>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:master-styles>\n"); - outs.printf("<draw:layer-set>\n"); - outs.printf(" <draw:layer draw:name=\"layout\"/>\n"); - outs.printf(" <draw:layer draw:name=\"background\"/>\n"); - outs.printf(" <draw:layer draw:name=\"backgroundobjects\"/>\n"); - outs.printf(" <draw:layer draw:name=\"controls\"/>\n"); - outs.printf(" <draw:layer draw:name=\"measurelines\"/>\n"); - outs.printf("</draw:layer-set>\n"); - outs.printf("\n"); - outs.printf("<style:master-page style:name=\"Default\"\n"); - outs.printf(" style:page-master-name=\"PM1\" draw:style-name=\"dp1\"/>\n"); - outs.printf("</office:master-styles>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("</office:document-styles>\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" E N D O F F I L E\n"); - outs.printf(" Have a nice day - ishmal\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - - //Make our entry - ZipEntry *ze = zf.newEntry("styles.xml", "ODF style 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 */ @@ -1533,24 +1321,30 @@ writePath(Writer &outs, Geom::PathVector const &pathv, return nrPoints; } - - -bool OdfOutput::processStyle(Writer &outs, SPItem *item, - const Glib::ustring &id) +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 + // 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; - //g_message("## %s %lx", id.c_str(), (unsigned int)fillCol); snprintf(buf, 15, "#%02x%02x%02x", r, g, b); si.fillColor = buf; si.fill = "solid"; @@ -1558,11 +1352,19 @@ bool OdfOutput::processStyle(Writer &outs, SPItem *item, (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 + // STROKE if (style->stroke.isColor()) - { + { guint32 strokeCol = style->stroke.value.color.toRGBA32( 0 ); char buf[16]; int r = (strokeCol >> 24) & 0xff; @@ -1577,157 +1379,170 @@ bool OdfOutput::processStyle(Writer &outs, SPItem *item, (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; - //g_message("found duplicate style:%s", styleName.c_str()); styleLookupTable[id] = styleName; styleMatch = true; break; - } } + } - //## Dont need a new style + // Dont need a new style if (styleMatch) + { return false; + } - char buf[16]; - snprintf(buf, 15, "style%d", (int)styleTable.size()); - Glib::ustring styleName = buf; + Glib::ustring styleName = Glib::ustring::compose("style%1", styleTable.size()); si.name = styleName; styleTable.push_back(si); styleLookupTable[id] = styleName; - outs.printf("<style:style style:name=\"%s\"", si.name.c_str()); - outs.printf(" style:family=\"graphic\" style:parent-style-name=\"standard\">\n"); - outs.printf(" <style:graphic-properties"); - outs.printf(" draw:fill=\"%s\" ", si.fill.c_str()); - if (si.fill != "none") + 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") { - outs.printf(" draw:fill-color=\"%s\" ", si.fillColor.c_str()); - outs.printf(" draw:fill-opacity=\"%s\" ", si.fillOpacity.c_str()); + output += Glib::ustring::compose(" draw:fill-color=\"%1\"", si.fillColor); } - outs.printf(" draw:stroke=\"%s\" ", si.stroke.c_str()); - if (si.stroke != "none") + } + 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") { - outs.printf(" svg:stroke-width=\"%s\" ", si.strokeWidth.c_str()); - outs.printf(" svg:stroke-color=\"%s\" ", si.strokeColor.c_str()); - outs.printf(" svg:stroke-opacity=\"%s\" ", si.strokeOpacity.c_str()); + output += Glib::ustring::compose (" svg:stroke-width=\"%1\" svg:stroke-color=\"%2\" ", si.strokeWidth, si.strokeColor); } - outs.printf("/>\n"); - outs.printf("</style:style>\n"); + } + output += "/>\n</style:style>\n"; return true; } - - - -bool OdfOutput::processGradient(Writer &outs, SPItem *item, - const Glib::ustring &id, Geom::Affine &/*tf*/) +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 (!style->fill.isPaintserver()) - return false; - - //## Gradient. Look in writeStyle() below to see what info - // we need to read into GradientInfo. - if (!SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))) + if ((checkFillGradient? (!style->fill.isPaintserver()) : (!style->stroke.isPaintserver()))) + { return false; + } - SPGradient *gradient = SP_GRADIENT(SP_STYLE_FILL_SERVER(style)); + //## Gradient + SPGradient *gradient = SP_GRADIENT((checkFillGradient?(SP_STYLE_FILL_SERVER(style)) :(SP_STYLE_STROKE_SERVER(style)))); + if (gradient == NULL) + { + return false; + } GradientInfo gi; - SPGradient *grvec = gradient->getVector(FALSE); - for (SPStop *stop = grvec->getFirstStop() ; + for (SPStop *stop = grvec->getFirstStop(); stop ; stop = stop->getNextStop()) - { - unsigned long rgba = sp_stop_get_rgba32(stop); + { + unsigned long rgba = stop->get_rgba32(); unsigned long rgb = (rgba >> 8) & 0xffffff; - double opacity = ((double)(rgba & 0xff)) / 256.0; + 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); - /* - Geom::Point p1(linGrad->x1.value, linGrad->y1.value); - p1 = p1 * tf; - gi.x1 = p1[Geom::X]; - gi.y1 = p1[Geom::Y]; - Geom::Point p2(linGrad->x2.value, linGrad->y2.value); - p2 = p2 * tf; - gi.x2 = p2[Geom::X]; - gi.y2 = p2[Geom::Y]; - */ 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); - gi.cx = radGrad->cx.computed * 100.0;//ODG cx is percentages - gi.cy = radGrad->cy.computed * 100.0; - } + 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 - Glib::ustring gradientName = iter->name; - //g_message("found duplicate style:%s", gradientName.c_str()); + gradientName = iter->name; gradientLookupTable[id] = gradientName; gradientMatch = true; break; - } } + } if (gradientMatch) + { return true; + } - //## No match, let us write a new entry - char buf[16]; - snprintf(buf, 15, "gradient%d", (int)gradientTable.size()); - Glib::ustring gradientName = buf; + // No match, let us write a new entry + gradientName = gradientName2; gi.name = gradientName; gradientTable.push_back(gi); gradientLookupTable[id] = gradientName; - int gradientCount = gradientTable.size(); - + // int gradientCount = gradientTable.size(); + char buf[128]; if (gi.style == "linear") - { + { /* =================================================================== LINEAR gradient. We need something that looks like this: @@ -1740,36 +1555,25 @@ bool OdfOutput::processGradient(Writer &outs, SPItem *item, =================================================================== */ if (gi.stops.size() < 2) - { + { g_warning("Need at least 2 stops for a linear gradient"); - return false;; - } - outs.printf("<svg:linearGradient "); - outs.printf("id=\"%#s_g\" ", gi.name.c_str()); - outs.printf("draw:name=\"%#s_g\"\n", gi.name.c_str()); - outs.printf(" draw:display-name=\"imported linear %d\"\n", - gradientCount); - outs.printf(" svg:gradientUnits=\"objectBoundingBox\"\n"); - outs.printf(" svg:x1=\"%05.3fcm\" svg:y1=\"%05.3fcm\"\n", - gi.x1 * pxToCm, gi.y1 * pxToCm); - outs.printf(" svg:x2=\"%05.3fcm\" svg:y2=\"%05.3fcm\">\n", - gi.x2 * pxToCm, gi.y2 * pxToCm); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[0].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[0].opacity * 100.0); - outs.printf(" svg:offset=\"0\"/>\n"); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[1].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[1].opacity * 100.0); - outs.printf(" svg:offset=\"1\"/>\n"); - outs.printf("</svg:linearGradient>\n"); + 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. / 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: @@ -1783,62 +1587,31 @@ bool OdfOutput::processGradient(Writer &outs, SPItem *item, =================================================================== */ if (gi.stops.size() < 2) - { + { g_warning("Need at least 2 stops for a radial gradient"); return false; - } - outs.printf("<svg:radialGradient "); - outs.printf("id=\"%#s_g\" ", gi.name.c_str()); - outs.printf("draw:name=\"%#s_g\"\n", gi.name.c_str()); - outs.printf(" draw:display-name=\"imported radial %d\"\n", - gradientCount); - outs.printf(" svg:gradientUnits=\"objectBoundingBox\"\n"); - outs.printf(" svg:cx=\"%05.3f\" svg:cy=\"%05.3f\"\n", - gi.cx, gi.cy); - outs.printf(" svg:fx=\"%05.3f\" svg:fy=\"%05.3f\"\n", - gi.fx, gi.fy); - outs.printf(" svg:r=\"%05.3f\">\n", - gi.r); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[0].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[0].opacity * 100.0); - outs.printf(" svg:offset=\"0\"/>\n"); - outs.printf(" <svg:stop\n"); - outs.printf(" svg:stop-color=\"#%06lx\"\n", - gi.stops[1].rgb); - outs.printf(" svg:stop-opacity=\"%f%%\"\n", - gi.stops[1].opacity * 100.0); - outs.printf(" svg:offset=\"1\"/>\n"); - outs.printf("</svg:radialGradient>\n"); } + 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; - } - outs.printf("<style:style style:name=\"%#s\" style:family=\"graphic\" ", - gi.name.c_str()); - outs.printf("style:parent-style-name=\"standard\">\n"); - outs.printf(" <style:graphic-properties draw:fill=\"gradient\" "); - outs.printf("draw:fill-gradient-name=\"%#s_g\"\n", - gi.name.c_str()); - outs.printf(" draw:textarea-horizontal-align=\"center\" "); - outs.printf("draw:textarea-vertical-align=\"middle\"/>\n"); - outs.printf("</style:style>\n\n"); - + } return true; } - - /** * SECOND PASS. - * This is the main SPObject tree output to ODF. preprocess() - * must be called prior to this, as elements will often reference - * data parsed and tabled in preprocess(). + * This is the main SPObject tree output to ODF. */ bool OdfOutput::writeTree(Writer &couts, Writer &souts, Inkscape::XML::Node *node) @@ -1846,22 +1619,19 @@ bool OdfOutput::writeTree(Writer &couts, Writer &souts, //# 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"); - - //### Get SVG-to-ODF transform - Geom::Affine tf = getODFTransform(item); - - //### Get ODF bounding box params for item - Geom::OptRect bbox = getODFBoundingBox(item); + 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; } @@ -1881,63 +1651,73 @@ bool OdfOutput::writeTree(Writer &couts, Writer &souts, //# Do our stuff SPCurve *curve = NULL; - - 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; } + return true; + } else if (nodeName == "g" || nodeName == "svg:g") + { + if (!id.empty()) { - if (id.size() > 0) 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.size() > 0) + } + if (!id.empty()) + { couts.printf("</draw:g> <!-- id=\"%s\" -->\n", id.c_str()); + } else + { couts.printf("</draw:g>\n"); - return true; } + return true; + } - //###################################### - //# S T Y L E - //###################################### - processStyle(souts, item, id); - - //###################################### - //# G R A D I E N T - //###################################### - processGradient(souts, item, id, tf); - - - - - //###################################### - //# I T E M D A T A - //###################################### - //g_message("##### %s #####", nodeName.c_str()); + //# GRADIENT + Glib::ustring gradientNameFill; + Glib::ustring gradientNameStroke; + Glib::ustring outputFill; + Glib::ustring outputStroke; + Glib::ustring outputStyle; + + processGradient(item, id, tf, gradientNameFill, outputFill, 1); + processGradient(item, id, tf, gradientNameStroke, outputStroke, 0); + 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. Why? ;-)"); + { + g_warning("<image> is not an SPImage."); return false; - } + } SPImage *img = SP_IMAGE(item); double ix = img->x.value; @@ -1949,8 +1729,6 @@ bool OdfOutput::writeTree(Writer &couts, Writer &souts, ibbox = ibbox * tf; ix = ibbox.min()[Geom::X]; iy = ibbox.min()[Geom::Y]; - //iwidth = ibbox.max()[Geom::X] - ibbox.min()[Geom::X]; - //iheight = ibbox.max()[Geom::Y] - ibbox.min()[Geom::Y]; iwidth = xscale * iwidth; iheight = yscale * iheight; @@ -1961,99 +1739,89 @@ bool OdfOutput::writeTree(Writer &couts, Writer &souts, 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.size() > 0) + 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.size() > 0) - { + 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.printf("draw:transform=\"translate(%.3fcm, %.3fcm)\" ", ix, iy); + } - couts.printf(">\n"); + couts.writeString(">\n"); couts.printf(" <draw:image xlink:href=\"%s\" xlink:type=\"simple\"\n", newName.c_str()); - couts.printf(" xlink:show=\"embed\" xlink:actuate=\"onLoad\">\n"); - couts.printf(" <text:p/>\n"); - couts.printf(" </draw:image>\n"); - couts.printf("</draw:frame>\n"); + 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)) - { - //g_message("### %s is a shape", nodeName.c_str()); + { 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.printf("<draw:path "); - if (id.size()>0) + 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()); - } - - std::map<Glib::ustring, Glib::ustring>::iterator giter; - giter = gradientLookupTable.find(id); - if (giter != gradientLookupTable.end()) - { - Glib::ustring gradientName = giter->second; - couts.printf("draw:fill-gradient-name=\"%s\" ", - gradientName.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\"\n", + couts.printf("svg:viewBox=\"0.0 0.0 %.3f %.3f\"", bbox_width * 1000.0, bbox_height * 1000.0); - couts.printf(" svg:d=\""); + couts.printf(" svg:d=\""); int nrPoints = writePath(couts, curve->get_pathvector(), tf, bbox_x, bbox_y); - couts.printf("\""); + couts.writeString("\""); - couts.printf(">\n"); + couts.writeString(">\n"); couts.printf(" <!-- %d nodes -->\n", nrPoints); - couts.printf("</draw:path>\n\n"); - + couts.writeString("</draw:path>\n\n"); curve->unref(); - } + } return true; } - /** * Write the header for the content.xml file */ @@ -2062,59 +1830,56 @@ bool OdfOutput::writeStyleHeader(Writer &outs) time_t tim; time(&tim); - outs.printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" file: styles.xml\n"); - outs.printf(" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> - outs.printf(" http://www.inkscape.org\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:document-styles\n"); - outs.printf(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); - outs.printf(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n"); - outs.printf(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n"); - outs.printf(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n"); - outs.printf(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n"); - outs.printf(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n"); - outs.printf(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); - outs.printf(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); - outs.printf(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); - outs.printf(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n"); - outs.printf(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); - outs.printf(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n"); - outs.printf(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n"); - outs.printf(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n"); - outs.printf(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n"); - outs.printf(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n"); - outs.printf(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n"); - outs.printf(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); - outs.printf(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n"); - outs.printf(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n"); - outs.printf(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n"); - outs.printf(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n"); - outs.printf(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"); - outs.printf(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); - outs.printf(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); - outs.printf(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); - outs.printf(" office:version=\"1.0\">\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" S T Y L E S\n"); - outs.printf(" Style entries have been pulled from the svg style and\n"); - outs.printf(" representation attributes in the SVG tree. The tree elements\n"); - outs.printf(" then refer to them by name, in the ODF manner\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - outs.printf("<office:styles>\n"); - outs.printf("\n"); + 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; } @@ -2125,58 +1890,49 @@ bool OdfOutput::writeStyleHeader(Writer &outs) */ bool OdfOutput::writeStyleFooter(Writer &outs) { - outs.printf("\n"); - outs.printf("</office:styles>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:automatic-styles>\n"); - outs.printf("<!-- ####### 'Standard' styles ####### -->\n"); - outs.printf("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n"); - outs.printf("<style:style style:name=\"gr1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n"); - outs.printf(" <style:graphic-properties draw:stroke=\"none\" draw:fill=\"none\"\n"); - outs.printf(" draw:textarea-horizontal-align=\"center\"\n"); - outs.printf(" draw:textarea-vertical-align=\"middle\" draw:color-mode=\"standard\"\n"); - outs.printf(" draw:luminance=\"0%%\" draw:contrast=\"0%%\" draw:gamma=\"100%%\" draw:red=\"0%%\"\n"); - outs.printf(" draw:green=\"0%%\" draw:blue=\"0%%\" fo:clip=\"rect(0cm 0cm 0cm 0cm)\"\n"); - outs.printf(" draw:image-opacity=\"100%%\" style:mirror=\"none\"/>\n"); - outs.printf("</style:style>\n"); - outs.printf("<style:style style:name=\"P1\" style:family=\"paragraph\">\n"); - outs.printf(" <style:paragraph-properties fo:text-align=\"center\"/>\n"); - outs.printf("</style:style>\n"); - outs.printf("</office:automatic-styles>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:master-styles>\n"); - outs.printf("<draw:layer-set>\n"); - outs.printf(" <draw:layer draw:name=\"layout\"/>\n"); - outs.printf(" <draw:layer draw:name=\"background\"/>\n"); - outs.printf(" <draw:layer draw:name=\"backgroundobjects\"/>\n"); - outs.printf(" <draw:layer draw:name=\"controls\"/>\n"); - outs.printf(" <draw:layer draw:name=\"measurelines\"/>\n"); - outs.printf("</draw:layer-set>\n"); - outs.printf("\n"); - outs.printf("<style:master-page style:name=\"Default\"\n"); - outs.printf(" style:page-master-name=\"PM1\" draw:style-name=\"dp1\"/>\n"); - outs.printf("</office:master-styles>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("</office:document-styles>\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" E N D O F F I L E\n"); - outs.printf(" Have a nice day - ishmal\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); + 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 */ @@ -2185,69 +1941,61 @@ bool OdfOutput::writeContentHeader(Writer &outs) time_t tim; time(&tim); - outs.printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" file: content.xml\n"); - outs.printf(" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr> - outs.printf(" http://www.inkscape.org\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:document-content\n"); - outs.printf(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n"); - outs.printf(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n"); - outs.printf(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n"); - outs.printf(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n"); - outs.printf(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n"); - outs.printf(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n"); - outs.printf(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"); - outs.printf(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"); - outs.printf(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n"); - outs.printf(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n"); - outs.printf(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n"); - outs.printf(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n"); - outs.printf(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n"); - outs.printf(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n"); - outs.printf(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n"); - outs.printf(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n"); - outs.printf(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n"); - outs.printf(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n"); - outs.printf(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n"); - outs.printf(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n"); - outs.printf(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n"); - outs.printf(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n"); - outs.printf(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"); - outs.printf(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"); - outs.printf(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n"); - outs.printf(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n"); - outs.printf(" office:version=\"1.0\">\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:scripts/>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" D R A W I N G\n"); - outs.printf(" This section is the heart of SVG-ODF conversion. We are\n"); - outs.printf(" starting with simple conversions, and will slowly evolve\n"); - outs.printf(" into a 'smarter' translation as time progresses. Any help\n"); - outs.printf(" in improving .odg export is welcome.\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<office:body>\n"); - outs.printf("<office:drawing>\n"); - outs.printf("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\"\n"); - outs.printf(" draw:master-page-name=\"Default\">\n"); - outs.printf("\n"); - outs.printf("\n"); - + 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; } @@ -2257,37 +2005,18 @@ bool OdfOutput::writeContentHeader(Writer &outs) */ bool OdfOutput::writeContentFooter(Writer &outs) { - outs.printf("\n"); - outs.printf("\n"); - - outs.printf("</draw:page>\n"); - outs.printf("</office:drawing>\n"); - - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n"); - outs.printf("\n"); - outs.printf("\n"); - - outs.printf("</office:body>\n"); - outs.printf("</office:document-content>\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("\n"); - outs.printf("<!--\n"); - outs.printf("*************************************************************************\n"); - outs.printf(" E N D O F F I L E\n"); - outs.printf(" Have a nice day - ishmal\n"); - outs.printf("*************************************************************************\n"); - outs.printf("-->\n"); - outs.printf("\n"); - outs.printf("\n"); - + 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(). @@ -2299,39 +2028,48 @@ bool OdfOutput::writeContent(ZipFile &zf, Inkscape::XML::Node *node) 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 as the same time + //# to both files at the same time + char *oldlocale = g_strdup (setlocale (LC_NUMERIC, NULL)); + 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()); @@ -2344,8 +2082,7 @@ bool OdfOutput::writeContent(ZipFile &zf, Inkscape::XML::Node *node) /** * Resets class to its pristine condition, ready to use again */ -void -OdfOutput::reset() +void OdfOutput::reset() { metadata.clear(); styleTable.clear(); @@ -2353,23 +2090,17 @@ OdfOutput::reset() 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) +void OdfOutput::save(Inkscape::Extension::Output */*mod*/, SPDocument *doc, gchar const *filename) { reset(); - //g_message("native file:%s\n", filename); - documentUri = URI(filename); - /* fixme: It looks like we really are using documentUri as a URI, so we ought to call - * g_filename_to_uri for the URI constructor. */ + documentUri = Inkscape::URI(filename); ZipFile zf; preprocess(zf, doc->rroot); @@ -2404,8 +2135,7 @@ OdfOutput::save(Inkscape::Extension::Output */*mod*/, SPDocument *doc, gchar con * calls the extension system with the memory allocated XML that * describes the data. */ -void -OdfOutput::init() +void OdfOutput::init() { Inkscape::Extension::build_from_mem( "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" @@ -2424,8 +2154,7 @@ OdfOutput::init() /** * Make sure that we are in the database */ -bool -OdfOutput::check (Inkscape::Extension::Extension */*module*/) +bool OdfOutput::check (Inkscape::Extension::Extension */*module*/) { /* We don't need a Key if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV)) @@ -2435,30 +2164,6 @@ OdfOutput::check (Inkscape::Extension::Extension */*module*/) return TRUE; } - - -//######################################################################## -//# I N P U T -//######################################################################## - - - -//####################### -//# L A T E R !!! :-) -//####################### - - - - - - - - - - - - - } //namespace Internal } //namespace Extension } //namespace Inkscape diff --git a/src/extension/internal/odf.h b/src/extension/internal/odf.h index 6b8883134..f8712d5eb 100644 --- a/src/extension/internal/odf.h +++ b/src/extension/internal/odf.h @@ -23,23 +23,20 @@ #ifndef EXTENSION_INTERNAL_ODG_OUT_H #define EXTENSION_INTERNAL_ODG_OUT_H -#include <dom/dom.h> -#include <dom/io/stringstream.h> -#include <dom/uri.h> +#include <io/stringstream.h> +#include <util/ziptool.h> -#include <glibmm.h> #include "extension/implementation/implementation.h" - #include <xml/repr.h> - #include <string> #include <map> -#include <dom/util/ziptool.h> -#include <dom/io/domstream.h> +#include "uri.h" #include "sp-item.h" +#include <glibmm/ustring.h> + namespace Inkscape { namespace Extension @@ -47,9 +44,7 @@ namespace Extension namespace Internal { -typedef org::w3c::dom::URI URI; -typedef org::w3c::dom::io::Writer Writer; - +typedef Inkscape::IO::Writer Writer; class StyleInfo { @@ -281,7 +276,7 @@ public: private: - URI documentUri; + Inkscape::URI documentUri; void reset(); @@ -315,10 +310,10 @@ private: bool writeStyle(ZipFile &zf); - bool processStyle(Writer &outs, SPItem *item, const Glib::ustring &id); + bool processStyle(SPItem *item, const Glib::ustring &id, const Glib::ustring &gradientNameFill, const Glib::ustring &gradientNameStroke, Glib::ustring& output); - bool processGradient(Writer &outs, SPItem *item, - const Glib::ustring &id, Geom::Affine &tf); + bool processGradient(SPItem *item, + const Glib::ustring &id, Geom::Affine &tf, Glib::ustring& gradientName, Glib::ustring& output, bool checkFillGradient = 1); bool writeStyleHeader(Writer &outs); @@ -335,8 +330,6 @@ private: }; - - } //namespace Internal } //namespace Extension } //namespace Inkscape diff --git a/src/extension/internal/pdf-input-cairo.cpp b/src/extension/internal/pdf-input-cairo.cpp index 5cb4f96c3..c45367c08 100644 --- a/src/extension/internal/pdf-input-cairo.cpp +++ b/src/extension/internal/pdf-input-cairo.cpp @@ -23,6 +23,10 @@ #include "extension/input.h" #include "dialogs/dialog-events.h" #include "document.h" +#include "sp-root.h" +#include "util/units.h" + +#include <2geom/rect.h> #include "inkscape.h" @@ -95,11 +99,7 @@ PdfImportCairoDialog::PdfImportCairoDialog(PopplerDocument *doc) _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++ ) { -#if WITH_GTKMM_2_24 _cropTypeCombo->append(_(crop_setting_choices[i])); -#else - _cropTypeCombo->append_text(_(crop_setting_choices[i])); -#endif } _cropTypeCombo->set_active_text(_(crop_setting_choices[0])); _cropTypeCombo->set_sensitive(false); @@ -112,7 +112,7 @@ PdfImportCairoDialog::PdfImportCairoDialog(PopplerDocument *doc) #if WITH_GTKMM_3_0 _fallbackPrecisionSlider_adj = Gtk::Adjustment::create(2, 1, 256, 1, 10, 10); - _fallbackPrecisionSlider = Gtk::manage(new Gtk::HScale(_fallbackPrecisionSlider_adj)); + _fallbackPrecisionSlider = Gtk::manage(new Gtk::Scale(_fallbackPrecisionSlider_adj)); #else _fallbackPrecisionSlider_adj = Gtk::manage(new class Gtk::Adjustment(2, 1, 256, 1, 10, 10)); _fallbackPrecisionSlider = Gtk::manage(new class Gtk::HScale(*_fallbackPrecisionSlider_adj)); @@ -124,11 +124,7 @@ PdfImportCairoDialog::PdfImportCairoDialog(PopplerDocument *doc) // Text options _labelText = Gtk::manage(new class Gtk::Label(_("Text handling:"))); _textHandlingCombo = Gtk::manage(new class Gtk::ComboBoxText()); -#if WITH_GTKMM_2_24 _textHandlingCombo->append(_("Import text as text")); -#else - _textHandlingCombo->append_text(_("Import text as text")); -#endif _textHandlingCombo->set_active_text(_("Import text as text")); _localFontsCheck = Gtk::manage(new class Gtk::CheckButton(_("Replace PDF fonts by closest-named installed fonts"))); @@ -232,12 +228,20 @@ PdfImportCairoDialog::PdfImportCairoDialog(PopplerDocument *doc) vbox1->pack_start(*_importSettingsFrame, Gtk::PACK_EXPAND_PADDING, 0); hbox1->pack_start(*vbox1); hbox1->pack_start(*_previewArea, Gtk::PACK_EXPAND_WIDGET, 4); + +#if WITH_GTKMM_3_0 + get_content_area()->set_homogeneous(false); + get_content_area()->set_spacing(0); + get_content_area()->pack_start(*hbox1); +#else this->get_vbox()->set_homogeneous(false); this->get_vbox()->set_spacing(0); this->get_vbox()->pack_start(*hbox1); +#endif + this->set_title(_("PDF Import Settings")); this->set_modal(true); - sp_transientize((GtkWidget *)this->gobj()); //Make transient + 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); @@ -271,7 +275,11 @@ PdfImportCairoDialog::PdfImportCairoDialog(PopplerDocument *doc) hbox1->show(); // Connect signals +#if WITH_GTKMM_3_0 + _previewArea->signal_draw().connect(sigc::mem_fun(*this, &PdfImportCairoDialog::_onDraw)); +#else _previewArea->signal_expose_event().connect(sigc::mem_fun(*this, &PdfImportCairoDialog::_onExposePreview)); +#endif _pageNumberSpin_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PdfImportCairoDialog::_onPageNumberChanged)); _cropCheck->signal_toggled().connect(sigc::mem_fun(*this, &PdfImportCairoDialog::_onToggleCropping)); _fallbackPrecisionSlider_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PdfImportCairoDialog::_onPrecisionChanged)); @@ -420,7 +428,7 @@ static void copy_cairo_surface_to_pixbuf (cairo_surface_t *surface, cairo_height = gdk_pixbuf_get_height (pixbuf); for (y = 0; y < cairo_height; y++) { - src = (unsigned int *) (cairo_data + y * cairo_rowstride); + src = reinterpret_cast<unsigned int *>(cairo_data + y * cairo_rowstride); dst = pixbuf_data + y * pixbuf_rowstride; for (x = 0; x < cairo_width; x++) { @@ -438,8 +446,15 @@ static void copy_cairo_surface_to_pixbuf (cairo_surface_t *surface, /** * \brief Updates the preview area with the previously rendered thumbnail */ -bool PdfImportCairoDialog::_onExposePreview(GdkEventExpose */*event*/) { +#if !WITH_GTKMM_3_0 +bool PdfImportCairoDialog::_onExposePreview(GdkEventExpose * /*event*/) { + Cairo::RefPtr<Cairo::Context> cr = _previewArea->get_window()->create_cairo_context(); + return _onDraw(cr); +} +#endif + +bool PdfImportCairoDialog::_onDraw(const Cairo::RefPtr<Cairo::Context>& cr) { // Check if we have a thumbnail at all if (!_thumb_data) { return true; @@ -461,24 +476,14 @@ bool PdfImportCairoDialog::_onExposePreview(GdkEventExpose */*event*/) { // Set background to white if (_render_thumb) { thumb->fill(0xffffffff); - Glib::RefPtr<Gdk::Pixmap> back_pixmap = Gdk::Pixmap::create( - _previewArea->get_window(), _thumb_width, _thumb_height, -1); - if (!back_pixmap) { - return true; - } - - Cairo::RefPtr<Cairo::Context> cr = back_pixmap->create_cairo_context(); Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, 0); cr->paint(); - _previewArea->get_window()->set_back_pixmap(back_pixmap, false); - _previewArea->get_window()->clear(); } // Copy the thumbnail image from the Cairo surface if (_render_thumb) { copy_cairo_surface_to_pixbuf(_cairo_surface, _thumb_data, thumb->gobj()); } - Cairo::RefPtr<Cairo::Context> cr = _previewArea->get_window()->create_cairo_context(); Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, _render_thumb ? 0 : 20); cr->paint(); @@ -490,7 +495,7 @@ bool PdfImportCairoDialog::_onExposePreview(GdkEventExpose */*event*/) { */ void PdfImportCairoDialog::_setPreviewPage(int page) { - PopplerPage *_previewed_page = poppler_document_get_page(_poppler_doc, page); + PopplerPage *_previewed_page = poppler_document_get_page(_poppler_doc, page-1); // Try to get a thumbnail from the PDF if possible if (!_render_thumb) { @@ -619,6 +624,11 @@ PdfInputCairo::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri) { SPDocument * doc = SPDocument::createNewDocFromMem(output->c_str(), output->length(), 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->getDefaultUnit()), doc->getHeight().value(doc->getDefaultUnit()))); + } + delete output; g_object_unref(page); g_object_unref(document); @@ -629,8 +639,8 @@ PdfInputCairo::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri) { static cairo_status_t _write_ustring_cb(void *closure, const unsigned char *data, unsigned int length) { - Glib::ustring* stream = (Glib::ustring*)closure; - stream->append((const char*)data, length); + Glib::ustring* stream = static_cast<Glib::ustring*>(closure); + stream->append(reinterpret_cast<const char*>(data), length); return CAIRO_STATUS_SUCCESS; } @@ -642,13 +652,13 @@ void PdfInputCairo::init(void) { Inkscape::Extension::build_from_mem( "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n" - "<name>PDF Input</name>\n" + "<name>" N_("PDF Input") "</name>\n" "<id>org.inkscape.input.cairo-pdf</id>\n" "<input>\n" "<extension>.pdf</extension>\n" "<mimetype>application/pdf</mimetype>\n" - "<filetypename>Adobe PDF via poppler-cairo (*.pdf)</filetypename>\n" - "<filetypetooltip>PDF Document</filetypetooltip>\n" + "<filetypename>" N_("Adobe PDF via poppler-cairo (*.pdf)") "</filetypename>\n" + "<filetypetooltip>" N_("PDF Document") "</filetypetooltip>\n" "</input>\n" "</inkscape-extension>", new PdfInputCairo()); } // init diff --git a/src/extension/internal/pdf-input-cairo.h b/src/extension/internal/pdf-input-cairo.h index 15235ee19..b65d22f48 100644 --- a/src/extension/internal/pdf-input-cairo.h +++ b/src/extension/internal/pdf-input-cairo.h @@ -16,6 +16,10 @@ # include <config.h> #endif +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include <glibmm/threads.h> +#endif + #include <gtkmm/dialog.h> #include <gtkmm/button.h> #include <gtkmm/buttonbox.h> @@ -37,6 +41,14 @@ #include "../implementation/implementation.h" +namespace Gtk { +#if WITH_GTKMM_3_0 + class Scale; +#else + class HScale; +#endif +} + namespace Inkscape { namespace UI { @@ -63,7 +75,11 @@ private: void _setPreviewPage(int page); // Signal handlers +#if !WITH_GTKMM_3_0 bool _onExposePreview(GdkEventExpose *event); +#endif + + bool _onDraw(const Cairo::RefPtr<Cairo::Context>& cr); void _onPageNumberChanged(); void _onToggleCropping(); void _onPrecisionChanged(); @@ -81,10 +97,11 @@ private: class Inkscape::UI::Widget::Frame * _pageSettingsFrame; class Gtk::Label * _labelPrecision; class Gtk::Label * _labelPrecisionWarning; - class Gtk::HScale * _fallbackPrecisionSlider; #if WITH_GTKMM_3_0 + class Gtk::Scale * _fallbackPrecisionSlider; Glib::RefPtr<Gtk::Adjustment> _fallbackPrecisionSlider_adj; #else + class Gtk::HScale * _fallbackPrecisionSlider; class Gtk::Adjustment *_fallbackPrecisionSlider_adj; #endif class Gtk::Label * _labelPrecisionComment; diff --git a/src/extension/internal/pdfinput/pdf-input.cpp b/src/extension/internal/pdfinput/pdf-input.cpp index d4fc6a8a2..3155ac098 100644 --- a/src/extension/internal/pdfinput/pdf-input.cpp +++ b/src/extension/internal/pdfinput/pdf-input.cpp @@ -32,6 +32,7 @@ #endif #include <gtkmm/alignment.h> +#include <gtkmm/checkbutton.h> #include <gtkmm/comboboxtext.h> #include <gtkmm/drawingarea.h> #include <gtkmm/frame.h> @@ -45,6 +46,7 @@ #include "document-private.h" #include "document-undo.h" #include "inkscape.h" +#include "util/units.h" #include "dialogs/dialog-events.h" #include <gtk/gtk.h> @@ -111,11 +113,7 @@ PdfImportDialog::PdfImportDialog(PDFDoc *doc, const gchar */*uri*/) _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++ ) { -#if WITH_GTKMM_2_24 _cropTypeCombo->append(_(crop_setting_choices[i])); -#else - _cropTypeCombo->append_text(_(crop_setting_choices[i])); -#endif } _cropTypeCombo->set_active_text(_(crop_setting_choices[0])); _cropTypeCombo->set_sensitive(false); @@ -128,7 +126,7 @@ PdfImportDialog::PdfImportDialog(PDFDoc *doc, const gchar */*uri*/) #if WITH_GTKMM_3_0 _fallbackPrecisionSlider_adj = Gtk::Adjustment::create(2, 1, 256, 1, 10, 10); - _fallbackPrecisionSlider = Gtk::manage(new class Gtk::HScale(_fallbackPrecisionSlider_adj)); + _fallbackPrecisionSlider = Gtk::manage(new class Gtk::Scale(_fallbackPrecisionSlider_adj)); #else _fallbackPrecisionSlider_adj = Gtk::manage(new class Gtk::Adjustment(2, 1, 256, 1, 10, 10)); _fallbackPrecisionSlider = Gtk::manage(new class Gtk::HScale(*_fallbackPrecisionSlider_adj)); @@ -140,11 +138,7 @@ PdfImportDialog::PdfImportDialog(PDFDoc *doc, const gchar */*uri*/) // Text options _labelText = Gtk::manage(new class Gtk::Label(_("Text handling:"))); _textHandlingCombo = Gtk::manage(new class Gtk::ComboBoxText()); -#if WITH_GTKMM_2_24 _textHandlingCombo->append(_("Import text as text")); -#else - _textHandlingCombo->append_text(_("Import text as text")); -#endif _textHandlingCombo->set_active_text(_("Import text as text")); _localFontsCheck = Gtk::manage(new class Gtk::CheckButton(_("Replace PDF fonts by closest-named installed fonts"))); @@ -248,12 +242,20 @@ PdfImportDialog::PdfImportDialog(PDFDoc *doc, const gchar */*uri*/) vbox1->pack_start(*_importSettingsFrame, Gtk::PACK_EXPAND_PADDING, 0); hbox1->pack_start(*vbox1); hbox1->pack_start(*_previewArea, Gtk::PACK_EXPAND_WIDGET, 4); + +#if WITH_GTKMM_3_0 + get_content_area()->set_homogeneous(false); + get_content_area()->set_spacing(0); + get_content_area()->pack_start(*hbox1); +#else this->get_vbox()->set_homogeneous(false); this->get_vbox()->set_spacing(0); this->get_vbox()->pack_start(*hbox1); +#endif + this->set_title(_("PDF Import Settings")); this->set_modal(true); - sp_transientize((GtkWidget *)this->gobj()); //Make transient + 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); @@ -287,7 +289,12 @@ PdfImportDialog::PdfImportDialog(PDFDoc *doc, const gchar */*uri*/) hbox1->show(); // Connect signals +#if WITH_GTKMM_3_0 + _previewArea->signal_draw().connect(sigc::mem_fun(*this, &PdfImportDialog::_onDraw)); +#else _previewArea->signal_expose_event().connect(sigc::mem_fun(*this, &PdfImportDialog::_onExposePreview)); +#endif + _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)); @@ -449,7 +456,7 @@ static void copy_cairo_surface_to_pixbuf (cairo_surface_t *surface, cairo_height = gdk_pixbuf_get_height (pixbuf); for (y = 0; y < cairo_height; y++) { - src = (unsigned int *) (cairo_data + y * cairo_rowstride); + src = reinterpret_cast<unsigned int *>(cairo_data + y * cairo_rowstride); dst = pixbuf_data + y * pixbuf_rowstride; for (x = 0; x < cairo_width; x++) { @@ -469,8 +476,14 @@ static void copy_cairo_surface_to_pixbuf (cairo_surface_t *surface, /** * \brief Updates the preview area with the previously rendered thumbnail */ -bool PdfImportDialog::_onExposePreview(GdkEventExpose */*event*/) { +#if !WITH_GTKMM_3_0 +bool PdfImportDialog::_onExposePreview(GdkEventExpose * /*event*/) { + Cairo::RefPtr<Cairo::Context> cr = _previewArea->get_window()->create_cairo_context(); + return _onDraw(cr); +} +#endif +bool PdfImportDialog::_onDraw(const Cairo::RefPtr<Cairo::Context>& cr) { // Check if we have a thumbnail at all if (!_thumb_data) { return true; @@ -478,6 +491,7 @@ bool PdfImportDialog::_onExposePreview(GdkEventExpose */*event*/) { // 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); @@ -492,17 +506,8 @@ bool PdfImportDialog::_onExposePreview(GdkEventExpose */*event*/) { // Set background to white if (_render_thumb) { thumb->fill(0xffffffff); - Glib::RefPtr<Gdk::Pixmap> back_pixmap = Gdk::Pixmap::create( - _previewArea->get_window(), _thumb_width, _thumb_height, -1); - if (!back_pixmap) { - return true; - } - - Cairo::RefPtr<Cairo::Context> cr = back_pixmap->create_cairo_context(); Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, 0); cr->paint(); - _previewArea->get_window()->set_back_pixmap(back_pixmap, false); - _previewArea->get_window()->clear(); } #ifdef HAVE_POPPLER_CAIRO // Copy the thumbnail image from the Cairo surface @@ -511,7 +516,6 @@ bool PdfImportDialog::_onExposePreview(GdkEventExpose */*event*/) { } #endif - Cairo::RefPtr<Cairo::Context> cr = _previewArea->get_window()->create_cairo_context(); Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, _render_thumb ? 0 : 20); cr->paint(); return true; @@ -586,12 +590,19 @@ void PdfImportDialog::_setPreviewPage(int page) { //////////////////////////////////////////////////////////////////////////////// +bool +PdfInput::wasCancelled () { + return _cancelled; +} + /** * Parses the selected page of the given PDF document using PdfParser. */ SPDocument * PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { + _cancelled = false; + // Initialize the globalParams variable for poppler if (!globalParams) { globalParams = new GlobalParams(); @@ -602,7 +613,7 @@ PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { PDFDoc *pdf_doc = new PDFDoc(filename_goo, NULL, NULL, NULL); // TODO: Could ask for password //delete filename_goo; #else - wchar_t *wfilename = (wchar_t*)g_utf8_to_utf16 (uri, -1, NULL, NULL, NULL); + wchar_t *wfilename = reinterpret_cast<wchar_t*>(g_utf8_to_utf16 (uri, -1, NULL, NULL, NULL)); if (wfilename == NULL) { return NULL; @@ -646,6 +657,7 @@ PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { if (inkscape_use_gui()) { dlg = new PdfImportDialog(pdf_doc, uri); if (!dlg->showDialog()) { + _cancelled = true; delete dlg; delete pdf_doc; return NULL; @@ -736,6 +748,11 @@ PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) { delete pdf_doc; delete dlg; + // Set viewBox if it doesn't exist + if (!doc->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDefaultUnit()), doc->getHeight().value(doc->getDefaultUnit()))); + } + // Restore undo DocumentUndo::setUndoSensitive(doc, saved); diff --git a/src/extension/internal/pdfinput/pdf-input.h b/src/extension/internal/pdfinput/pdf-input.h index 3b79e6f29..f22a783ff 100644 --- a/src/extension/internal/pdfinput/pdf-input.h +++ b/src/extension/internal/pdfinput/pdf-input.h @@ -16,6 +16,10 @@ #ifdef HAVE_POPPLER +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include <glibmm/threads.h> +#endif + #include <gtkmm/dialog.h> #include "../../implementation/implementation.h" @@ -39,7 +43,11 @@ namespace Gtk { class DrawingArea; class Frame; class HBox; +#if WITH_GTKMM_3_0 + class Scale; +#else class HScale; +#endif class VBox; class Label; } @@ -73,7 +81,11 @@ private: void _setPreviewPage(int page); // Signal handlers +#if !WITH_GTKMM_3_0 bool _onExposePreview(GdkEventExpose *event); +#endif + + bool _onDraw(const Cairo::RefPtr<Cairo::Context>& cr); void _onPageNumberChanged(); void _onToggleCropping(); void _onPrecisionChanged(); @@ -91,10 +103,11 @@ private: class Inkscape::UI::Widget::Frame * _pageSettingsFrame; class Gtk::Label * _labelPrecision; class Gtk::Label * _labelPrecisionWarning; - class Gtk::HScale * _fallbackPrecisionSlider; #if WITH_GTKMM_3_0 + class Gtk::Scale * _fallbackPrecisionSlider; Glib::RefPtr<Gtk::Adjustment> _fallbackPrecisionSlider_adj; #else + class Gtk::HScale * _fallbackPrecisionSlider; class Gtk::Adjustment *_fallbackPrecisionSlider_adj; #endif class Gtk::Label * _labelPrecisionComment; @@ -131,7 +144,9 @@ public: SPDocument *open( Inkscape::Extension::Input *mod, const gchar *uri ); static void init( void ); - + virtual bool wasCancelled(); +private: + bool _cancelled; }; } // namespace Implementation diff --git a/src/extension/internal/pdfinput/pdf-parser.cpp b/src/extension/internal/pdfinput/pdf-parser.cpp index 5e8b005e2..b398486e6 100644 --- a/src/extension/internal/pdfinput/pdf-parser.cpp +++ b/src/extension/internal/pdfinput/pdf-parser.cpp @@ -34,7 +34,7 @@ extern "C" { #include "svg-builder.h" #include "Gfx.h" #include "pdf-parser.h" -#include "unit-constants.h" +#include "util/units.h" #include "goo/gmem.h" #include "goo/GooTimer.h" @@ -76,10 +76,6 @@ extern "C" { // Operator table //------------------------------------------------------------------------ -#ifdef WIN32 // this works around a bug in the VC7 compiler -# pragma optimize("",off) -#endif - PdfOperator PdfParser::opTab[] = { {"\"", 3, {tchkNum, tchkNum, tchkString}, &PdfParser::opMoveSetShowText}, @@ -249,10 +245,6 @@ PdfOperator PdfParser::opTab[] = { &PdfParser::opCurveTo2} }; -#ifdef WIN32 // this works around a bug in the VC7 compiler -# pragma optimize("",on) -#endif - #define numOps (sizeof(opTab) / sizeof(PdfOperator)) //------------------------------------------------------------------------ @@ -279,14 +271,14 @@ PdfParser::PdfParser(XRef *xrefA, Inkscape::Extension::Internal::SvgBuilder *bui ignoreUndef = 0; operatorHistory = NULL; builder = builderA; - builder->setDocumentSize(state->getPageWidth()*PX_PER_PT, - state->getPageHeight()*PX_PER_PT); + builder->setDocumentSize(Inkscape::Util::Quantity::convert(state->getPageWidth(), "pt", "px"), + Inkscape::Util::Quantity::convert(state->getPageHeight(), "pt", "px")); double *ctm = state->getCTM(); double scaledCTM[6]; for (int i = 0; i < 6; ++i) { baseMatrix[i] = ctm[i]; - scaledCTM[i] = PX_PER_PT * ctm[i]; + scaledCTM[i] = Inkscape::Util::Quantity::convert(ctm[i], "pt", "px"); } saveState(); builder->setTransform((double*)&scaledCTM); @@ -368,15 +360,23 @@ void PdfParser::parse(Object *obj, GBool topLevel) { for (int i = 0; i < obj->arrayGetLength(); ++i) { obj->arrayGet(i, &obj2); if (!obj2.isStream()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errInternal, -1, "Weird page contents"); +#else error(-1, const_cast<char*>("Weird page contents")); +#endif obj2.free(); return; } obj2.free(); } } else if (!obj->isStream()) { - error(-1, const_cast<char*>("Weird page contents")); - return; +#ifdef POPPLER_NEW_ERRORAPI + error(errInternal, -1, "Weird page contents"); +#else + error(-1, const_cast<char*>("Weird page contents")); +#endif + return; } parser = new Parser(xref, new Lexer(xref, obj), gFalse); go(topLevel); @@ -420,7 +420,11 @@ void PdfParser::go(GBool /*topLevel*/) // too many arguments - something is wrong } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Too many args in content stream"); +#else error(getPos(), const_cast<char*>("Too many args in content stream")); +#endif if (printCommands) { printf("throwing away arg: "); obj.print(stdout); @@ -437,7 +441,11 @@ void PdfParser::go(GBool /*topLevel*/) // args at end with no command if (numArgs > 0) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Leftover args in content stream"); +#else error(getPos(), const_cast<char*>("Leftover args in content stream")); +#endif if (printCommands) { printf("%d leftovers:", numArgs); for (int i = 0; i < numArgs; ++i) { @@ -503,7 +511,11 @@ void PdfParser::execOp(Object *cmd, Object args[], int numArgs) { name = cmd->getCmd(); if (!(op = findOp(name))) { if (ignoreUndef == 0) +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name); +#else error(getPos(), const_cast<char*>("Unknown operator '%s'"), name); +#endif return; } @@ -511,26 +523,42 @@ void PdfParser::execOp(Object *cmd, Object args[], int numArgs) { argPtr = args; if (op->numArgs >= 0) { if (numArgs < op->numArgs) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:d}' operator", numArgs, name); +#else error(getPos(), const_cast<char*>("Too few (%d) args to '%s' operator"), numArgs, name); +#endif return; } if (numArgs > op->numArgs) { #if 0 +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name); +#else error(getPos(), "Too many (%d) args to '%s' operator", numArgs, name); #endif +#endif argPtr += numArgs - op->numArgs; numArgs = op->numArgs; } } else { if (numArgs > -op->numArgs) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", +#else error(getPos(), const_cast<char*>("Too many (%d) args to '%s' operator"), +#endif numArgs, name); return; } } for (i = 0; i < numArgs; ++i) { if (!checkArg(&argPtr[i], op->tchk[i])) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})", +#else error(getPos(), const_cast<char*>("Arg #%d to '%s' operator is wrong type (%s)"), +#endif i, name, argPtr[i].getTypeName()); return; } @@ -543,14 +571,13 @@ void PdfParser::execOp(Object *cmd, Object args[], int numArgs) { (this->*op->func)(argPtr, numArgs); } -PdfOperator *PdfParser::findOp(char *name) { - int a, b, m, cmp; - - a = -1; - b = numOps; +PdfOperator* PdfParser::findOp(char *name) { + int a = -1; + int b = numOps; + int cmp = -1; // invariant: opTab[a] < name < opTab[b] while (b - a > 1) { - m = (a + b) / 2; + const int m = (a + b) / 2; cmp = strcmp(opTab[m].name, name); if (cmp < 0) a = m; @@ -700,7 +727,11 @@ void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) return; } if (!obj1.isDict()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type"), args[0].getName(); +#else error(getPos(), const_cast<char*>("ExtGState '%s' is wrong type"), args[0].getName()); +#endif obj1.free(); return; } @@ -716,7 +747,11 @@ void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) if (state->parseBlendMode(&obj2, &mode)) { state->setBlendMode(mode); } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState"); +#else error(getPos(), const_cast<char*>("Invalid blend mode in ExtGState")); +#endif } } obj2.free(); @@ -778,7 +813,11 @@ void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) state->setTransfer(funcs); } } else if (!obj2.isNull()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState"); +#else error(getPos(), const_cast<char*>("Invalid transfer function in ExtGState")); +#endif } obj2.free(); @@ -798,8 +837,11 @@ void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) funcs[0] = Function::parse(&obj3); if (funcs[0]->getInputSize() != 1 || funcs[0]->getOutputSize() != 1) { - error(getPos(), - const_cast<char*>("Invalid transfer function in soft mask in ExtGState")); +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState"); +#else + error(getPos(), const_cast<char*>("Invalid transfer function in soft mask in ExtGState")); +#endif delete funcs[0]; funcs[0] = NULL; } @@ -824,7 +866,7 @@ void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) GBool isolated = gFalse; GBool knockout = gFalse; if (!obj4.dictLookup(const_cast<char*>("CS"), &obj5)->isNull()) { -#ifdef POPPLER_NEW_COLOR_SPACE_API +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) blendingColorSpace = GfxColorSpace::parse(&obj5, NULL); #else blendingColorSpace = GfxColorSpace::parse(&obj5); @@ -855,15 +897,27 @@ void PdfParser::opSetExtGState(Object args[], int /*numArgs*/) delete funcs[0]; } } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); +#else error(getPos(), const_cast<char*>("Invalid soft mask in ExtGState - missing group")); +#endif } obj4.free(); } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group"); +#else error(getPos(), const_cast<char*>("Invalid soft mask in ExtGState - missing group")); +#endif } obj3.free(); } else if (!obj2.isNull()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState"); +#else error(getPos(), const_cast<char*>("Invalid soft mask in ExtGState")); +#endif } } obj2.free(); @@ -891,7 +945,11 @@ void PdfParser::doSoftMask(Object *str, GBool alpha, // check form type dict->lookup(const_cast<char*>("FormType"), &obj1); if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Unknown form type"); +#else error(getPos(), const_cast<char*>("Unknown form type")); +#endif } obj1.free(); @@ -899,7 +957,11 @@ void PdfParser::doSoftMask(Object *str, GBool alpha, dict->lookup(const_cast<char*>("BBox"), &obj1); if (!obj1.isArray()) { obj1.free(); +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Bad form bounding box"); +#else error(getPos(), const_cast<char*>("Bad form bounding box")); +#endif return; } for (i = 0; i < 4; ++i) { @@ -1038,7 +1100,7 @@ void PdfParser::opSetFillColorSpace(Object args[], int /*numArgs*/) res->lookupColorSpace(args[0].getName(), &obj); GfxColorSpace *colorSpace = 0; -#ifdef POPPLER_NEW_COLOR_SPACE_API +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) if (obj.isNull()) { colorSpace = GfxColorSpace::parse(&args[0], NULL); } else { @@ -1059,7 +1121,11 @@ void PdfParser::opSetFillColorSpace(Object args[], int /*numArgs*/) state->setFillColor(&color); builder->updateStyle(state); } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Bad color space (fill)"); +#else error(getPos(), const_cast<char*>("Bad color space (fill)")); +#endif } } @@ -1071,7 +1137,7 @@ void PdfParser::opSetStrokeColorSpace(Object args[], int /*numArgs*/) state->setStrokePattern(NULL); res->lookupColorSpace(args[0].getName(), &obj); -#ifdef POPPLER_NEW_COLOR_SPACE_API +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) if (obj.isNull()) { colorSpace = GfxColorSpace::parse(&args[0], NULL); } else { @@ -1092,7 +1158,11 @@ void PdfParser::opSetStrokeColorSpace(Object args[], int /*numArgs*/) state->setStrokeColor(&color); builder->updateStyle(state); } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Bad color space (stroke)"); +#else error(getPos(), const_cast<char*>("Bad color space (stroke)")); +#endif } } @@ -1101,7 +1171,11 @@ void PdfParser::opSetFillColor(Object args[], int numArgs) { int i; if (numArgs != state->getFillColorSpace()->getNComps()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command"); +#else error(getPos(), const_cast<char*>("Incorrect number of arguments in 'sc' command")); +#endif return; } state->setFillPattern(NULL); @@ -1117,7 +1191,11 @@ void PdfParser::opSetStrokeColor(Object args[], int numArgs) { int i; if (numArgs != state->getStrokeColorSpace()->getNComps()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command"); +#else error(getPos(), const_cast<char*>("Incorrect number of arguments in 'SC' command")); +#endif return; } state->setStrokePattern(NULL); @@ -1130,7 +1208,6 @@ void PdfParser::opSetStrokeColor(Object args[], int numArgs) { void PdfParser::opSetFillColorN(Object args[], int numArgs) { GfxColor color; - GfxPattern *pattern; int i; if (state->getFillColorSpace()->getMode() == csPattern) { @@ -1138,7 +1215,11 @@ void PdfParser::opSetFillColorN(Object args[], int numArgs) { if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace()) ->getUnder()->getNComps()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command"); +#else error(getPos(), const_cast<char*>("Incorrect number of arguments in 'scn' command")); +#endif return; } for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { @@ -1149,7 +1230,8 @@ void PdfParser::opSetFillColorN(Object args[], int numArgs) { state->setFillColor(&color); builder->updateStyle(state); } -#ifdef POPPLER_NEW_COLOR_SPACE_API + GfxPattern *pattern; +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) if (args[numArgs-1].isName() && (pattern = res->lookupPattern(args[numArgs-1].getName(), NULL))) { state->setFillPattern(pattern); @@ -1165,7 +1247,11 @@ void PdfParser::opSetFillColorN(Object args[], int numArgs) { } else { if (numArgs != state->getFillColorSpace()->getNComps()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command"); +#else error(getPos(), const_cast<char*>("Incorrect number of arguments in 'scn' command")); +#endif return; } state->setFillPattern(NULL); @@ -1181,7 +1267,6 @@ void PdfParser::opSetFillColorN(Object args[], int numArgs) { void PdfParser::opSetStrokeColorN(Object args[], int numArgs) { GfxColor color; - GfxPattern *pattern; int i; if (state->getStrokeColorSpace()->getMode() == csPattern) { @@ -1190,7 +1275,11 @@ void PdfParser::opSetStrokeColorN(Object args[], int numArgs) { ->getUnder() || numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace()) ->getUnder()->getNComps()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); +#else error(getPos(), const_cast<char*>("Incorrect number of arguments in 'SCN' command")); +#endif return; } for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) { @@ -1201,7 +1290,8 @@ void PdfParser::opSetStrokeColorN(Object args[], int numArgs) { state->setStrokeColor(&color); builder->updateStyle(state); } -#ifdef POPPLER_NEW_COLOR_SPACE_API + GfxPattern *pattern; +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) if (args[numArgs-1].isName() && (pattern = res->lookupPattern(args[numArgs-1].getName(), NULL))) { state->setStrokePattern(pattern); @@ -1217,7 +1307,11 @@ void PdfParser::opSetStrokeColorN(Object args[], int numArgs) { } else { if (numArgs != state->getStrokeColorSpace()->getNComps()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command"); +#else error(getPos(), const_cast<char*>("Incorrect number of arguments in 'SCN' command")); +#endif return; } state->setStrokePattern(NULL); @@ -1245,7 +1339,11 @@ void PdfParser::opMoveTo(Object args[], int /*numArgs*/) void PdfParser::opLineTo(Object args[], int /*numArgs*/) { if (!state->isCurPt()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No current point in lineto"); +#else error(getPos(), const_cast<char*>("No current point in lineto")); +#endif return; } state->lineTo(args[0].getNum(), args[1].getNum()); @@ -1255,7 +1353,11 @@ void PdfParser::opLineTo(Object args[], int /*numArgs*/) void PdfParser::opCurveTo(Object args[], int /*numArgs*/) { if (!state->isCurPt()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No current point in curveto"); +#else error(getPos(), const_cast<char*>("No current point in curveto")); +#endif return; } double x1 = args[0].getNum(); @@ -1271,7 +1373,11 @@ void PdfParser::opCurveTo(Object args[], int /*numArgs*/) void PdfParser::opCurveTo1(Object args[], int /*numArgs*/) { if (!state->isCurPt()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No current point in curveto1"); +#else error(getPos(), const_cast<char*>("No current point in curveto1")); +#endif return; } double x1 = state->getCurX(); @@ -1287,7 +1393,11 @@ void PdfParser::opCurveTo1(Object args[], int /*numArgs*/) void PdfParser::opCurveTo2(Object args[], int /*numArgs*/) { if (!state->isCurPt()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No current point in curveto2"); +#else error(getPos(), const_cast<char*>("No current point in curveto2")); +#endif return; } double x1 = args[0].getNum(); @@ -1316,7 +1426,11 @@ void PdfParser::opRectangle(Object args[], int /*numArgs*/) void PdfParser::opClosePath(Object /*args*/[], int /*numArgs*/) { if (!state->isCurPt()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No current point in closepath"); +#else error(getPos(), const_cast<char*>("No current point in closepath")); +#endif return; } state->closePath(); @@ -1482,7 +1596,11 @@ void PdfParser::doPatternFillFallback(GBool eoFill) { doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gFalse, eoFill); break; default: +#ifdef POPPLER_NEW_ERRORAPI + error(errUnimplemented, getPos(), "Unimplemented pattern type (%d) in fill", +#else error(getPos(), const_cast<char*>("Unimplemented pattern type (%d) in fill"), +#endif pattern->getType()); break; } @@ -1501,7 +1619,11 @@ void PdfParser::doPatternStrokeFallback() { doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gTrue, gFalse); break; default: +#ifdef POPPLER_NEW_ERRORAPI + error(errUnimplemented, getPos(), "Unimplemented pattern type ({0:d}) in stroke", +#else error(getPos(), const_cast<char*>("Unimplemented pattern type (%d) in stroke"), +#endif pattern->getType()); break; } @@ -1624,7 +1746,7 @@ void PdfParser::opShFill(Object args[], int /*numArgs*/) double *matrix = NULL; GBool savedState = gFalse; -#ifdef POPPLER_NEW_COLOR_SPACE_API +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) if (!(shading = res->lookupShading(args[0].getName(), NULL))) { return; } @@ -2244,7 +2366,11 @@ void PdfParser::opTextNextLine(Object /*args*/[], int /*numArgs*/) void PdfParser::opShowText(Object args[], int /*numArgs*/) { if (!state->getFont()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No font in show"); +#else error(getPos(), const_cast<char*>("No font in show")); +#endif return; } if (fontChanged) { @@ -2261,7 +2387,11 @@ void PdfParser::opMoveShowText(Object args[], int /*numArgs*/) double ty = 0; if (!state->getFont()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No font in move/show"); +#else error(getPos(), const_cast<char*>("No font in move/show")); +#endif return; } if (fontChanged) { @@ -2282,7 +2412,11 @@ void PdfParser::opMoveSetShowText(Object args[], int /*numArgs*/) double ty = 0; if (!state->getFont()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No font in move/set/show"); +#else error(getPos(), const_cast<char*>("No font in move/set/show")); +#endif return; } if (fontChanged) { @@ -2306,7 +2440,11 @@ void PdfParser::opShowSpaceText(Object args[], int /*numArgs*/) int wMode = 0; if (!state->getFont()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "No font in show/space"); +#else error(getPos(), const_cast<char*>("No font in show/space")); +#endif return; } if (fontChanged) { @@ -2331,7 +2469,11 @@ void PdfParser::opShowSpaceText(Object args[], int /*numArgs*/) } else if (obj.isString()) { doShowText(obj.getString()); } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Element of show/space array must be number or string"); +#else error(getPos(), const_cast<char*>("Element of show/space array must be number or string")); +#endif } obj.free(); } @@ -2429,7 +2571,11 @@ void PdfParser::doShowText(GooString *s) { if (charProc.isStream()) { //parse(&charProc, gFalse); // TODO: parse into SVG font } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry"); +#else error(getPos(), const_cast<char*>("Missing or bad Type3 CharProc entry")); +#endif } //out->endType3Char(state); if (resDict) { @@ -2506,7 +2652,11 @@ void PdfParser::opXObject(Object args[], int /*numArgs*/) return; } if (!obj1.isStream()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name); +#else error(getPos(), const_cast<char*>("XObject '%s' is wrong type"), name); +#endif obj1.free(); return; } @@ -2522,9 +2672,17 @@ void PdfParser::opXObject(Object args[], int /*numArgs*/) /* out->psXObject(obj1.getStream(), obj3.isStream() ? obj3.getStream() : (Stream *)NULL);*/ } else if (obj2.isName()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName()); +#else error(getPos(), const_cast<char*>("Unknown XObject subtype '%s'"), obj2.getName()); +#endif } else { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type"); +#else error(getPos(), const_cast<char*>("XObject subtype is missing or wrong type")); +#endif } obj2.free(); obj1.free(); @@ -2532,330 +2690,339 @@ void PdfParser::opXObject(Object args[], int /*numArgs*/) void PdfParser::doImage(Object * /*ref*/, Stream *str, GBool inlineImg) { - Dict *dict, *maskDict; - int width, height; - int bits; - StreamColorSpaceMode csMode; - GBool mask; - GBool invert; - GfxColorSpace *colorSpace, *maskColorSpace; - GfxImageColorMap *colorMap, *maskColorMap; - Object maskObj, smaskObj; - GBool haveColorKeyMask, haveExplicitMask, haveSoftMask; - int maskColors[2*gfxColorMaxComps]; - int maskWidth, maskHeight; - GBool maskInvert; - Stream *maskStr; - Object obj1, obj2; - int i; - - // get info from the stream - bits = 0; - csMode = streamCSNone; - str->getImageParams(&bits, &csMode); - - // get stream dict - dict = str->getDict(); - - // get size - dict->lookup(const_cast<char*>("Width"), &obj1); - if (obj1.isNull()) { - obj1.free(); - dict->lookup(const_cast<char*>("W"), &obj1); - } - if (obj1.isInt()) - width = obj1.getInt(); - else if (obj1.isReal()) - width = (int)obj1.getReal(); - else - goto err2; - obj1.free(); - dict->lookup(const_cast<char*>("Height"), &obj1); - if (obj1.isNull()) { - obj1.free(); - dict->lookup(const_cast<char*>("H"), &obj1); - } - if (obj1.isInt()) - height = obj1.getInt(); - else if (obj1.isReal()) - height = (int)obj1.getReal(); - else - goto err2; - obj1.free(); - - // image or mask? - dict->lookup(const_cast<char*>("ImageMask"), &obj1); - if (obj1.isNull()) { - obj1.free(); - dict->lookup(const_cast<char*>("IM"), &obj1); - } - mask = gFalse; - if (obj1.isBool()) - mask = obj1.getBool(); - else if (!obj1.isNull()) - goto err2; - obj1.free(); - - // bit depth - if (bits == 0) { - dict->lookup(const_cast<char*>("BitsPerComponent"), &obj1); + Dict *dict; + int width, height; + int bits; + StreamColorSpaceMode csMode; + GBool mask; + GBool invert; + Object maskObj, smaskObj; + GBool haveColorKeyMask, haveExplicitMask, haveSoftMask; + GBool maskInvert; + Object obj1, obj2; + + // get info from the stream + bits = 0; + csMode = streamCSNone; + str->getImageParams(&bits, &csMode); + + // get stream dict + dict = str->getDict(); + + // get size + dict->lookup(const_cast<char*>("Width"), &obj1); if (obj1.isNull()) { - obj1.free(); - dict->lookup(const_cast<char*>("BPC"), &obj1); + obj1.free(); + dict->lookup(const_cast<char*>("W"), &obj1); } - if (obj1.isInt()) { - bits = obj1.getInt(); - } else if (mask) { - bits = 1; - } else { - goto err2; + if (obj1.isInt()){ + width = obj1.getInt(); } - obj1.free(); - } - - // display a mask - if (mask) { - - // check for inverted mask - if (bits != 1) - goto err1; - invert = gFalse; - dict->lookup(const_cast<char*>("Decode"), &obj1); - if (obj1.isNull()) { - obj1.free(); - dict->lookup(const_cast<char*>("D"), &obj1); + else if (obj1.isReal()) { + width = (int)obj1.getReal(); } - if (obj1.isArray()) { - obj1.arrayGet(0, &obj2); - if (obj2.isInt() && obj2.getInt() == 1) - invert = gTrue; - obj2.free(); - } else if (!obj1.isNull()) { - goto err2; + else { + goto err2; } obj1.free(); - - // draw it - builder->addImageMask(state, str, width, height, invert); - - } else { - - // get color space and color map - dict->lookup(const_cast<char*>("ColorSpace"), &obj1); + dict->lookup(const_cast<char*>("Height"), &obj1); if (obj1.isNull()) { - obj1.free(); - dict->lookup(const_cast<char*>("CS"), &obj1); + obj1.free(); + dict->lookup(const_cast<char*>("H"), &obj1); } - if (obj1.isName()) { - res->lookupColorSpace(obj1.getName(), &obj2); - if (!obj2.isNull()) { - obj1.free(); - obj1 = obj2; - } else { - obj2.free(); - } + if (obj1.isInt()) { + height = obj1.getInt(); } - if (!obj1.isNull()) { -#ifdef POPPLER_NEW_COLOR_SPACE_API - colorSpace = GfxColorSpace::parse(&obj1, NULL); -#else - colorSpace = GfxColorSpace::parse(&obj1); -#endif - } else if (csMode == streamCSDeviceGray) { - colorSpace = new GfxDeviceGrayColorSpace(); - } else if (csMode == streamCSDeviceRGB) { - colorSpace = new GfxDeviceRGBColorSpace(); - } else if (csMode == streamCSDeviceCMYK) { - colorSpace = new GfxDeviceCMYKColorSpace(); - } else { - colorSpace = NULL; + else if (obj1.isReal()){ + height = static_cast<int>(obj1.getReal()); } - obj1.free(); - if (!colorSpace) { - goto err1; + else { + goto err2; } - dict->lookup(const_cast<char*>("Decode"), &obj1); + obj1.free(); + + // image or mask? + dict->lookup(const_cast<char*>("ImageMask"), &obj1); if (obj1.isNull()) { - obj1.free(); - dict->lookup(const_cast<char*>("D"), &obj1); + obj1.free(); + dict->lookup(const_cast<char*>("IM"), &obj1); + } + mask = gFalse; + if (obj1.isBool()) { + mask = obj1.getBool(); + } + else if (!obj1.isNull()) { + goto err2; } - colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); obj1.free(); - if (!colorMap->isOk()) { - delete colorMap; - goto err1; - } - - // get the mask - haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse; - maskStr = NULL; // make gcc happy - maskWidth = maskHeight = 0; // make gcc happy - maskInvert = gFalse; // make gcc happy - maskColorMap = NULL; // make gcc happy - dict->lookup(const_cast<char*>("Mask"), &maskObj); - dict->lookup(const_cast<char*>("SMask"), &smaskObj); - if (smaskObj.isStream()) { - // soft mask - if (inlineImg) { - goto err1; - } - maskStr = smaskObj.getStream(); - maskDict = smaskObj.streamGetDict(); - maskDict->lookup(const_cast<char*>("Width"), &obj1); + + // bit depth + if (bits == 0) { + dict->lookup(const_cast<char*>("BitsPerComponent"), &obj1); if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("W"), &obj1); + obj1.free(); + dict->lookup(const_cast<char*>("BPC"), &obj1); } - if (!obj1.isInt()) { - goto err2; + if (obj1.isInt()) { + bits = obj1.getInt(); + } else if (mask) { + bits = 1; + } else { + goto err2; } - maskWidth = obj1.getInt(); obj1.free(); - maskDict->lookup(const_cast<char*>("Height"), &obj1); - if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("H"), &obj1); - } - if (!obj1.isInt()) { - goto err2; + } + + // display a mask + if (mask) { + // check for inverted mask + if (bits != 1) { + goto err1; } - maskHeight = obj1.getInt(); - obj1.free(); - maskDict->lookup(const_cast<char*>("BitsPerComponent"), &obj1); + invert = gFalse; + dict->lookup(const_cast<char*>("Decode"), &obj1); if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("BPC"), &obj1); + obj1.free(); + dict->lookup(const_cast<char*>("D"), &obj1); } - if (!obj1.isInt()) { - goto err2; + if (obj1.isArray()) { + obj1.arrayGet(0, &obj2); + if (obj2.isInt() && obj2.getInt() == 1) { + invert = gTrue; + } + obj2.free(); + } else if (!obj1.isNull()) { + goto err2; } - int maskBits = obj1.getInt(); obj1.free(); - maskDict->lookup(const_cast<char*>("ColorSpace"), &obj1); + + // draw it + builder->addImageMask(state, str, width, height, invert); + + } else { + // get color space and color map + GfxColorSpace *colorSpace; + dict->lookup(const_cast<char*>("ColorSpace"), &obj1); if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("CS"), &obj1); + obj1.free(); + dict->lookup(const_cast<char*>("CS"), &obj1); } if (obj1.isName()) { - res->lookupColorSpace(obj1.getName(), &obj2); - if (!obj2.isNull()) { + res->lookupColorSpace(obj1.getName(), &obj2); + if (!obj2.isNull()) { obj1.free(); obj1 = obj2; - } else { + } else { obj2.free(); - } + } } -#ifdef POPPLER_NEW_COLOR_SPACE_API - maskColorSpace = GfxColorSpace::parse(&obj1, NULL); + if (!obj1.isNull()) { +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) + colorSpace = GfxColorSpace::parse(&obj1, NULL); #else - maskColorSpace = GfxColorSpace::parse(&obj1); + colorSpace = GfxColorSpace::parse(&obj1); #endif + } else if (csMode == streamCSDeviceGray) { + colorSpace = new GfxDeviceGrayColorSpace(); + } else if (csMode == streamCSDeviceRGB) { + colorSpace = new GfxDeviceRGBColorSpace(); + } else if (csMode == streamCSDeviceCMYK) { + colorSpace = new GfxDeviceCMYKColorSpace(); + } else { + colorSpace = NULL; + } obj1.free(); - if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) { + if (!colorSpace) { goto err1; } - maskDict->lookup(const_cast<char*>("Decode"), &obj1); + dict->lookup(const_cast<char*>("Decode"), &obj1); if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("D"), &obj1); + obj1.free(); + dict->lookup(const_cast<char*>("D"), &obj1); } - maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace); + GfxImageColorMap *colorMap = new GfxImageColorMap(bits, &obj1, colorSpace); obj1.free(); - if (!maskColorMap->isOk()) { - delete maskColorMap; + if (!colorMap->isOk()) { + delete colorMap; goto err1; } - //~ handle the Matte entry - haveSoftMask = gTrue; - } else if (maskObj.isArray()) { - // color key mask - for (i = 0; - i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps; - ++i) { - maskObj.arrayGet(i, &obj1); - maskColors[i] = obj1.getInt(); - obj1.free(); - } - haveColorKeyMask = gTrue; - } else if (maskObj.isStream()) { - // explicit mask - if (inlineImg) { - goto err1; - } - maskStr = maskObj.getStream(); - maskDict = maskObj.streamGetDict(); - maskDict->lookup(const_cast<char*>("Width"), &obj1); - if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("W"), &obj1); - } - if (!obj1.isInt()) { - goto err2; - } - maskWidth = obj1.getInt(); - obj1.free(); - maskDict->lookup(const_cast<char*>("Height"), &obj1); - if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("H"), &obj1); - } - if (!obj1.isInt()) { - goto err2; - } - maskHeight = obj1.getInt(); - obj1.free(); - maskDict->lookup(const_cast<char*>("ImageMask"), &obj1); - if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("IM"), &obj1); - } - if (!obj1.isBool() || !obj1.getBool()) { - goto err2; - } - obj1.free(); - maskInvert = gFalse; - maskDict->lookup(const_cast<char*>("Decode"), &obj1); - if (obj1.isNull()) { - obj1.free(); - maskDict->lookup(const_cast<char*>("D"), &obj1); - } - if (obj1.isArray()) { - obj1.arrayGet(0, &obj2); - if (obj2.isInt() && obj2.getInt() == 1) { - maskInvert = gTrue; - } - obj2.free(); - } else if (!obj1.isNull()) { - goto err2; - } - obj1.free(); - haveExplicitMask = gTrue; - } - - // draw it - if (haveSoftMask) { - builder->addSoftMaskedImage(state, str, width, height, colorMap, + + // get the mask + int maskColors[2*gfxColorMaxComps]; + haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse; + Stream *maskStr = NULL; + int maskWidth = 0; + int maskHeight = 0; + maskInvert = gFalse; + GfxImageColorMap *maskColorMap = NULL; + dict->lookup(const_cast<char*>("Mask"), &maskObj); + dict->lookup(const_cast<char*>("SMask"), &smaskObj); + Dict* maskDict; + if (smaskObj.isStream()) { + // soft mask + if (inlineImg) { + goto err1; + } + maskStr = smaskObj.getStream(); + maskDict = smaskObj.streamGetDict(); + maskDict->lookup(const_cast<char*>("Width"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("W"), &obj1); + } + if (!obj1.isInt()) { + goto err2; + } + maskWidth = obj1.getInt(); + obj1.free(); + maskDict->lookup(const_cast<char*>("Height"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("H"), &obj1); + } + if (!obj1.isInt()) { + goto err2; + } + maskHeight = obj1.getInt(); + obj1.free(); + maskDict->lookup(const_cast<char*>("BitsPerComponent"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("BPC"), &obj1); + } + if (!obj1.isInt()) { + goto err2; + } + int maskBits = obj1.getInt(); + obj1.free(); + maskDict->lookup(const_cast<char*>("ColorSpace"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("CS"), &obj1); + } + if (obj1.isName()) { + res->lookupColorSpace(obj1.getName(), &obj2); + if (!obj2.isNull()) { + obj1.free(); + obj1 = obj2; + } else { + obj2.free(); + } + } +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) + GfxColorSpace *maskColorSpace = GfxColorSpace::parse(&obj1, NULL); +#else + GfxColorSpace *maskColorSpace = GfxColorSpace::parse(&obj1); +#endif + obj1.free(); + if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) { + goto err1; + } + maskDict->lookup(const_cast<char*>("Decode"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("D"), &obj1); + } + maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace); + obj1.free(); + 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) { + maskObj.arrayGet(i, &obj1); + maskColors[i] = obj1.getInt(); + obj1.free(); + } + haveColorKeyMask = gTrue; + } else if (maskObj.isStream()) { + // explicit mask + if (inlineImg) { + goto err1; + } + maskStr = maskObj.getStream(); + maskDict = maskObj.streamGetDict(); + maskDict->lookup(const_cast<char*>("Width"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("W"), &obj1); + } + if (!obj1.isInt()) { + goto err2; + } + maskWidth = obj1.getInt(); + obj1.free(); + maskDict->lookup(const_cast<char*>("Height"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("H"), &obj1); + } + if (!obj1.isInt()) { + goto err2; + } + maskHeight = obj1.getInt(); + obj1.free(); + maskDict->lookup(const_cast<char*>("ImageMask"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("IM"), &obj1); + } + if (!obj1.isBool() || !obj1.getBool()) { + goto err2; + } + obj1.free(); + maskInvert = gFalse; + maskDict->lookup(const_cast<char*>("Decode"), &obj1); + if (obj1.isNull()) { + obj1.free(); + maskDict->lookup(const_cast<char*>("D"), &obj1); + } + if (obj1.isArray()) { + obj1.arrayGet(0, &obj2); + if (obj2.isInt() && obj2.getInt() == 1) { + maskInvert = gTrue; + } + obj2.free(); + } else if (!obj1.isNull()) { + goto err2; + } + obj1.free(); + haveExplicitMask = gTrue; + } + + // draw it + if (haveSoftMask) { + builder->addSoftMaskedImage(state, str, width, height, colorMap, maskStr, maskWidth, maskHeight, maskColorMap); - delete maskColorMap; - } else if (haveExplicitMask) { - builder->addMaskedImage(state, str, width, height, colorMap, + delete maskColorMap; + } else if (haveExplicitMask) { + builder->addMaskedImage(state, str, width, height, colorMap, maskStr, maskWidth, maskHeight, maskInvert); - } else { - builder->addImage(state, str, width, height, colorMap, - haveColorKeyMask ? maskColors : (int *)NULL); + } else { + builder->addImage(state, str, width, height, colorMap, + haveColorKeyMask ? maskColors : static_cast<int *>(NULL)); + } + delete colorMap; + + maskObj.free(); + smaskObj.free(); } - delete colorMap; - - maskObj.free(); - smaskObj.free(); - } - return; + return; err2: - obj1.free(); + obj1.free(); err1: - error(getPos(), const_cast<char*>("Bad image parameters")); +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Bad image parameters"); +#else + error(getPos(), const_cast<char*>("Bad image parameters")); +#endif } void PdfParser::doForm(Object *str) { @@ -2880,7 +3047,11 @@ void PdfParser::doForm(Object *str) { // check form type dict->lookup(const_cast<char*>("FormType"), &obj1); if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Unknown form type"); +#else error(getPos(), const_cast<char*>("Unknown form type")); +#endif } obj1.free(); @@ -2888,7 +3059,11 @@ void PdfParser::doForm(Object *str) { dict->lookup(const_cast<char*>("BBox"), &bboxObj); if (!bboxObj.isArray()) { bboxObj.free(); +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Bad form bounding box"); +#else error(getPos(), const_cast<char*>("Bad form bounding box")); +#endif return; } for (i = 0; i < 4; ++i) { @@ -2924,7 +3099,7 @@ void PdfParser::doForm(Object *str) { if (obj1.dictLookup(const_cast<char*>("S"), &obj2)->isName(const_cast<char*>("Transparency"))) { transpGroup = gTrue; if (!obj1.dictLookup(const_cast<char*>("CS"), &obj3)->isNull()) { -#ifdef POPPLER_NEW_COLOR_SPACE_API +#if defined(POPPLER_NEW_COLOR_SPACE_API) || defined(POPPLER_NEW_ERRORAPI) blendingColorSpace = GfxColorSpace::parse(&obj3, NULL); #else blendingColorSpace = GfxColorSpace::parse(&obj3); @@ -3085,7 +3260,11 @@ Stream *PdfParser::buildImageStream() { parser->getObj(&obj); while (!obj.isCmd(const_cast<char*>("ID")) && !obj.isEOF()) { if (!obj.isName()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object"); +#else error(getPos(), const_cast<char*>("Inline image dictionary key must be a name object")); +#endif obj.free(); } else { key = copyString(obj.getName()); @@ -3100,7 +3279,11 @@ Stream *PdfParser::buildImageStream() { parser->getObj(&obj); } if (obj.isEOF()) { +#ifdef POPPLER_NEW_ERRORAPI + error(errSyntaxError, getPos(), "End of file in inline image"); +#else error(getPos(), const_cast<char*>("End of file in inline image")); +#endif obj.free(); dict.free(); return NULL; @@ -3116,12 +3299,20 @@ Stream *PdfParser::buildImageStream() { void PdfParser::opImageData(Object /*args*/[], int /*numArgs*/) { +#ifdef POPPLER_NEW_ERRORAPI + error(errInternal, getPos(), "Internal: got 'ID' operator"); +#else error(getPos(), const_cast<char*>("Internal: got 'ID' operator")); +#endif } void PdfParser::opEndImage(Object /*args*/[], int /*numArgs*/) { +#ifdef POPPLER_NEW_ERRORAPI + error(errInternal, getPos(), "Internal: got 'EI' operator"); +#else error(getPos(), const_cast<char*>("Internal: got 'EI' operator")); +#endif } //------------------------------------------------------------------------ @@ -3198,9 +3389,22 @@ void PdfParser::opMarkPoint(Object args[], int numArgs) { //------------------------------------------------------------------------ void PdfParser::saveState() { + bool is_radial = false; + + GfxPattern *pattern = state->getFillPattern(); + if (pattern != NULL) + 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(); -// state = state->save(); // nasty hack to prevent GfxRadialShading from getting corrupted during copy operation - state->save(); // see LP Bug 919176 comment 8 + 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(); } diff --git a/src/extension/internal/pdfinput/svg-builder.cpp b/src/extension/internal/pdfinput/svg-builder.cpp index 2f36e19a4..71e6dc6ae 100644 --- a/src/extension/internal/pdfinput/svg-builder.cpp +++ b/src/extension/internal/pdfinput/svg-builder.cpp @@ -33,7 +33,7 @@ #include "svg/css-ostringstream.h" #include "svg/svg-color.h" #include "color.h" -#include "unit-constants.h" +#include "util/units.h" #include "io/stringstream.h" #include "io/base64stream.h" #include "display/nr-filter-utils.h" @@ -56,15 +56,12 @@ namespace Internal { #define TRACE(_args) IFTRACE(g_print _args) -static double ttm[6] = {1, 0, 0, 1, 0, 0}; // temporary transform matrix -static bool ttm_is_set = false; // flag to forbid setting ttm - /** * \struct SvgTransparencyGroup * \brief Holds information about a PDF transparency group */ struct SvgTransparencyGroup { - double bbox[6]; + double bbox[6]; // TODO should this be 4? Inkscape::XML::Node *container; bool isolated; @@ -94,6 +91,9 @@ SvgBuilder::SvgBuilder(SPDocument *document, gchar *docname, XRef *xref) _preferences = _xml_doc->createElement("svgbuilder:prefs"); _preferences->setAttribute("embedImages", "1"); _preferences->setAttribute("localFonts", "1"); + + _ttm[0] = 1; _ttm[1] = 0; _ttm[2] = 0; _ttm[3] = 1; _ttm[4] = 0; _ttm[5] = 0; + _ttm_is_set = false; } SvgBuilder::SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root) { @@ -216,9 +216,9 @@ Inkscape::XML::Node *SvgBuilder::pushGroup() { } } if (_container->parent()->attribute("inkscape:groupmode") != NULL) { - 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; + _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; } @@ -266,14 +266,12 @@ static void svgSetTransform(Inkscape::XML::Node *node, double c0, double c1, * \brief Generates a SVG path string from poppler's data structure */ static gchar *svgInterpretPath(GfxPath *path) { - GfxSubpath *subpath; Inkscape::SVG::PathString pathString; - int i, j; - for ( i = 0 ; i < path->getNumSubpaths() ; ++i ) { - subpath = path->getSubpath(i); + for (int i = 0 ; i < path->getNumSubpaths() ; ++i ) { + GfxSubpath *subpath = path->getSubpath(i); if (subpath->getNumPoints() > 0) { pathString.moveTo(subpath->getX(0), subpath->getY(0)); - j = 1; + int j = 1; while (j < subpath->getNumPoints()) { if (subpath->getCurve(j)) { pathString.curveTo(subpath->getX(j), subpath->getY(j), @@ -300,14 +298,6 @@ static gchar *svgInterpretPath(GfxPath *path) { * Uses the given SPCSSAttr for storing the style properties */ void SvgBuilder::_setStrokeStyle(SPCSSAttr *css, GfxState *state) { - - // Check line width - if ( state->getLineWidth() <= 0.0 ) { - // Ignore stroke - sp_repr_css_set_property(css, "stroke", "none"); - return; - } - // Stroke color/pattern if ( state->getStrokeColorSpace()->getMode() == csPattern ) { gchar *urltext = _createPattern(state->getStrokePattern(), state, true); @@ -328,7 +318,14 @@ void SvgBuilder::_setStrokeStyle(SPCSSAttr *css, GfxState *state) { // Line width Inkscape::CSSOStringStream os_width; - os_width << state->getLineWidth(); + 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 @@ -572,14 +569,14 @@ bool SvgBuilder::getTransform(double *transform) { 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") == NULL) && !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; + if ((_container->attribute("inkscape:groupmode") == NULL) && !_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 @@ -629,21 +626,21 @@ gchar *SvgBuilder::_createPattern(GfxPattern *pattern, GfxState *state, bool is_ if ( pattern->getType() == 2 ) { // Shading pattern GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern); double *ptm; - double ittm[6]; // invert ttm 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]; + det = _ttm[0] * _ttm[3] - _ttm[1] * _ttm[2]; if (det) { - 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; + 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]; @@ -676,7 +673,25 @@ gchar *SvgBuilder::_createTilingPattern(GfxTilingPattern *tiling_pattern, Inkscape::XML::Node *pattern_node = _xml_doc->createElement("svg:pattern"); // Set pattern transform matrix double *p2u = tiling_pattern->getMatrix(); - Geom::Affine pat_matrix(p2u[0], p2u[1], p2u[2], p2u[3], p2u[4], p2u[5]); + 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); @@ -777,7 +792,7 @@ gchar *SvgBuilder::_createGradient(GfxShading *shading, double *matrix, bool for 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, _height * PT_PER_PX); + 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); @@ -1004,9 +1019,7 @@ void SvgBuilder::updateFont(GfxState *state) { _font_style = sp_repr_css_attr_new(); GfxFont *font = state->getFont(); // Store original name - if (font->getOrigName()) { - _font_specification = font->getOrigName()->getCString(); - } else if (font->getName()) { + if (font->getName()) { _font_specification = font->getName()->getCString(); } else { _font_specification = (char*) "Arial"; @@ -1210,7 +1223,7 @@ void SvgBuilder::updateTextMatrix(GfxState *state) { */ void SvgBuilder::_flushText() { // Ignore empty strings - if ( _glyphs.size() < 1 ) { + if ( _glyphs.empty()) { _glyphs.clear(); return; } @@ -1302,7 +1315,8 @@ void SvgBuilder::_flushText() { 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 - sp_repr_css_change(tspan_node, glyph.style, "style"); + // 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); } @@ -1642,6 +1656,10 @@ Inkscape::XML::Node *SvgBuilder::_createImage(Stream *str, int width, int height 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); + + // 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); @@ -1818,7 +1836,9 @@ void SvgBuilder::pushTransparencyGroup(GfxState * /*state*/, double *bbox, // Setup new transparency group SvgTransparencyGroup *transpGroup = new SvgTransparencyGroup; - memcpy(&transpGroup->bbox, bbox, sizeof(bbox)); + for (size_t i = 0; i < 4; i++) { + transpGroup->bbox[i] = bbox[i]; + } transpGroup->isolated = isolated; transpGroup->knockout = knockout; transpGroup->for_softmask = for_softmask; diff --git a/src/extension/internal/pdfinput/svg-builder.h b/src/extension/internal/pdfinput/svg-builder.h index a550565d6..f1ce02cf0 100644 --- a/src/extension/internal/pdfinput/svg-builder.h +++ b/src/extension/internal/pdfinput/svg-builder.h @@ -19,7 +19,7 @@ class SPDocument; namespace Inkscape { namespace XML { - class Document; + struct Document; class Node; } } @@ -31,10 +31,10 @@ namespace Inkscape { #include "CharTypes.h" class GooString; class Function; -struct GfxState; -class GfxColor; +class GfxState; +struct GfxColor; class GfxColorSpace; -class GfxRGB; +struct GfxRGB; class GfxPath; class GfxPattern; class GfxTilingPattern; @@ -223,6 +223,8 @@ private: 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; }; diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp index 9b1098afd..8b272af60 100644 --- a/src/extension/internal/svg.cpp +++ b/src/extension/internal/svg.cpp @@ -44,7 +44,7 @@ using Inkscape::XML::Node; -void pruneExtendedAttributes( Inkscape::XML::Node *repr ) +static void pruneExtendedAttributes( Inkscape::XML::Node *repr ) { if (repr) { if ( repr->type() == Inkscape::XML::ELEMENT_NODE ) { diff --git a/src/extension/internal/text_reassemble.c b/src/extension/internal/text_reassemble.c new file mode 100644 index 000000000..c62c83120 --- /dev/null +++ b/src/extension/internal/text_reassemble.c @@ -0,0 +1,2926 @@ +/** + @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. Ouput file is overwritten each time. + + +File: text_reassemble.c +Version: 0.0.13 +Date: 24-MAR-2014 +Author: David Mathog, Biology Division, Caltech +email: mathog@caltech.edu +Copyright: 2014 David Mathog and California Institute of Technology (Caltech) +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "text_reassemble.h" +#include <libuemf/uemf_utf.h> /* For a couple of text functions. Exact copy from libUEMF. */ +#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=%lf: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; + if(!fti)return(2); + if(fti->used >= fti->space){ + fti->space += ALLOCINFO_CHUNK; + if((fti->fonts = (FNT_SPECS *) realloc(fti->fonts, fti->space * sizeof(FNT_SPECS) ))){ + 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: %lf\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 %8lf fsize %8lf \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; + if(!fsp)return(2); + if(fsp->used >= fsp->space){ + fsp->space += ALLOCINFO_CHUNK; + if((fsp->alts = (ALT_SPECS *) realloc(fsp->alts, fsp->space * sizeof(ALT_SPECS) ))){ + 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; + if(!csp)return(2); + if(csp->used >= csp->space){ + csp->space += ALLOCINFO_CHUNK; + if((csp->members = (int *) realloc(csp->members, csp->space * sizeof(int) ))){ + 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; + if(cxi->used >= cxi->space){ + cxi->space += ALLOCINFO_CHUNK; + if((cxi->cx = (CX_SPECS *) realloc(cxi->cx, cxi->space * sizeof(CX_SPECS) ))){ + 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: %lf , %lf\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) (%lf,%lf),(%lf,%lf)\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.3lf,%8.3lf),(%8.3lf,%8.3lf) xy (%8.3lf,%8.3lf) kern (%8.3lf,%8.3lf) 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; + if(tpi->used >= tpi->space){ + tpi->space += ALLOCINFO_CHUNK; + if((tpi->chunks = (TCHUNK_SPECS *) realloc(tpi->chunks, tpi->space * sizeof(TCHUNK_SPECS) ))){ + 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; + if(!bri)return(2); + if(bri->used >= bri->space){ + bri->space += ALLOCINFO_CHUNK; + if(!(bri->rects = (BRECT_SPECS *) realloc(bri->rects, bri->space * sizeof(BRECT_SPECS) ))){ 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:(%lf,%lf),(%lf,%lf) src:(%lf,%lf),(%lf,%lf)\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:(%lf,%lf),(%lf,%lf) src:(%lf,%lf),(%lf,%lf)\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:(%lf,%lf),(%lf,%lf) src:(%lf,%lf),(%lf,%lf)\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:(%lf,%lf),(%lf,%lf) src:(%lf,%lf),(%lf,%lf)\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:(%lf,%lf),(%lf,%lf) src:(%lf,%lf),(%lf,%lf)\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; + if(!src)return(-1); + slen = strlen(src); + if(tri->outused + (int) slen + 1 >= tri->outspace){ + tri->outspace += TEREMAX(ALLOCOUT_CHUNK,slen+1); + if(!(tri->out = realloc(tri->out, tri->outspace )))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:%lf\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; + + if(tri->load_flags & FT_LOAD_NO_SCALE) xe *= tsp->fs/32.0; + + /* 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 *= tsp->fs/32.0; + dsc *= tsp->fs/32.0; + fasc *= tsp->fs/32.0; + fdsc *= tsp->fs/32.0; + } + + + /* 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; + +/* +#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(%lf,%lf,%lf,%lf,%lf,%lf)\"\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=\"%lf\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%lf\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%lf\" y=\"%lf\"\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=\"%lf\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%lf\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%lf\" y=\"%lf\"\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=\"%lf\" y=\"%lf\"\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:%lfpx;",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(%lf,%lf,%lf,%lf,%lf,%lf)\"\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=\"%lf\"\n", 1.25*(bri->rects[cline_sp->rt_cidx].xur - bri->rects[cline_sp->rt_cidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%lf\"\n",1.25*(bri->rects[cline_sp->rt_cidx].yll - bri->rects[cline_sp->rt_cidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%lf\" y=\"%lf\"\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=\"%lf\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%lf\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%lf\" y=\"%lf\"\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=\"%lf\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll)); + TRPRINT(tri, obuf); + sprintf(obuf,"height=\"%lf\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur)); + TRPRINT(tri, obuf); + sprintf(obuf,"x=\"%lf\" y=\"%lf\"\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:%lfpx;",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:%lf%%;",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(%lf,%lf,%lf,%lf,%lf,%lf)\"\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=\"%lf\" y=\"%lf\"\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=\"%lf\" y=\"%lf\"\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=\"%lf\" dy=\"%lf\" ",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:%lfpx;",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 */ +} + +/** + \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 000000000..6c1acafe5 --- /dev/null +++ b/src/extension/internal/text_reassemble.h @@ -0,0 +1,387 @@ +/** + @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> +#include <stdio.h> +#include <math.h> +#include <stdint.h> +#include <ctype.h> +#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 diretion 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 indicies + 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 000000000..6fc79237b --- /dev/null +++ b/src/extension/internal/vsd-input.cpp @@ -0,0 +1,339 @@ +/* + * 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 bellow. + * + * Authors: + * Fridrich Strba (fridrich.strba@bluewin.ch) + * + * Copyright (C) 2012 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + * + */ + +#include <stdio.h> +#include "config.h" + +#include "vsd-input.h" + +#ifdef WITH_LIBVISIO + +#include <string> +#include <cstring> + +#include <libvisio/libvisio.h> +#include <libwpd-stream/libwpd-stream.h> + +#include <gtkmm/alignment.h> +#include <gtkmm/comboboxtext.h> +#include <gtkmm/drawingarea.h> +#include <gtkmm/frame.h> +#include <gtkmm/scale.h> + +#include "extension/system.h" +#include "extension/input.h" +#include "document.h" + +#include "document-private.h" +#include "document-undo.h" +#include "inkscape.h" +#include "util/units.h" + +#include "dialogs/dialog-events.h" +#include <gtk/gtk.h> +#include "ui/widget/spinbutton.h" +#include "ui/widget/frame.h" +#include <glibmm/i18n.h> + +#include <gdkmm/general.h> + +#include "svg-view.h" +#include "svg-view-widget.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + + +class VsdImportDialog : public Gtk::Dialog { +public: + VsdImportDialog(const std::vector<WPXString> &vec); + virtual ~VsdImportDialog(); + + bool showDialog(); + unsigned getSelectedPage(); + void getImportSettings(Inkscape::XML::Node *prefs); + +private: + void _setPreviewPage(unsigned page); + + // Signal handlers +#if !WITH_GTKMM_3_0 + bool _onExposePreview(GdkEventExpose *event); +#endif + + void _onPageNumberChanged(); + + class Gtk::Button * cancelbutton; + class Gtk::Button * okbutton; + class Gtk::Label * _labelSelect; + class Inkscape::UI::Widget::SpinButton * _pageNumberSpin; + class Gtk::Label * _labelTotalPages; + class Gtk::VBox * vbox1; + class Gtk::VBox * vbox2; + class Gtk::Widget * _previewArea; + + const std::vector<WPXString> &_vec; // Document to be imported + unsigned _current_page; // Current selected page + int _preview_width, _preview_height; // Size of the preview area +}; + +VsdImportDialog::VsdImportDialog(const std::vector<WPXString> &vec) + : _vec(vec), _current_page(1) +{ + int num_pages = _vec.size(); + if ( num_pages <= 1 ) + return; + cancelbutton = Gtk::manage(new class Gtk::Button(Gtk::StockID("gtk-cancel"))); + okbutton = Gtk::manage(new class Gtk::Button(Gtk::StockID("gtk-ok"))); + _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:"))); + + // Page number +#if WITH_GTKMM_3_0 + Glib::RefPtr<Gtk::Adjustment> _pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _vec.size(), 1, 10, 0); + _pageNumberSpin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(_pageNumberSpin_adj, 1, 1)); +#else + Gtk::Adjustment *_pageNumberSpin_adj = Gtk::manage( + new class Gtk::Adjustment(1, 1, _vec.size(), 1, 10, 0)); + _pageNumberSpin = Gtk::manage(new class Inkscape::UI::Widget::SpinButton(*_pageNumberSpin_adj, 1, 1)); +#endif + _labelTotalPages = Gtk::manage(new class Gtk::Label()); + gchar *label_text = g_strdup_printf(_("out of %i"), num_pages); + _labelTotalPages->set_label(label_text); + g_free(label_text); + + vbox1 = Gtk::manage(new class Gtk::VBox(false, 4)); + SPDocument *doc = SPDocument::createNewDocFromMem(_vec[0].cstr(), strlen(_vec[0].cstr()), 0); + _previewArea = Glib::wrap(sp_svg_view_widget_new(doc)); + + vbox2 = Gtk::manage(new class Gtk::VBox(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); + this->get_action_area()->property_layout_style().set_value(Gtk::BUTTONBOX_END); + _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_line_wrap(false); + _labelTotalPages->set_use_markup(false); + _labelTotalPages->set_selectable(false); + vbox2->pack_start(*_previewArea, Gtk::PACK_SHRINK, 0); + this->get_vbox()->set_homogeneous(false); + this->get_vbox()->set_spacing(0); + this->get_vbox()->pack_start(*vbox2); + 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); + this->get_action_area()->add(*_labelSelect); + this->add_action_widget(*_pageNumberSpin, -7); + this->get_action_area()->add(*_labelTotalPages); + this->add_action_widget(*cancelbutton, -6); + this->add_action_widget(*okbutton, -5); + cancelbutton->show(); + okbutton->show(); + _labelSelect->show(); + _pageNumberSpin->show(); + _labelTotalPages->show(); + vbox1->show(); + _previewArea->show(); + vbox2->show(); + + // Connect signals + _pageNumberSpin_adj->signal_value_changed().connect(sigc::mem_fun(*this, &VsdImportDialog::_onPageNumberChanged)); +} + +VsdImportDialog::~VsdImportDialog() {} + +bool VsdImportDialog::showDialog() +{ + show(); + gint b = run(); + hide(); + if ( b == Gtk::RESPONSE_OK ) { + 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(_current_page); +} + +/** + * \brief Renders the given page's thumbnail + */ +void VsdImportDialog::_setPreviewPage(unsigned page) +{ + SPDocument *doc = SPDocument::createNewDocFromMem(_vec[page-1].cstr(), strlen(_vec[page-1].cstr()), 0); + Gtk::Widget * tmpPreviewArea = Glib::wrap(sp_svg_view_widget_new(doc)); + std::swap(_previewArea, tmpPreviewArea); + if (tmpPreviewArea) { + _previewArea->set_size_request( tmpPreviewArea->get_width(), tmpPreviewArea->get_height() ); + delete tmpPreviewArea; + } + vbox2->pack_start(*_previewArea, Gtk::PACK_SHRINK, 0); + _previewArea->show_now(); +} + +SPDocument *VsdInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri) +{ + WPXFileStream input(uri); + + if (!libvisio::VisioDocument::isSupported(&input)) { + return NULL; + } + + libvisio::VSDStringVector output; + if (!libvisio::VisioDocument::generateSVG(&input, output)) { + return NULL; + } + + if (output.empty()) { + return NULL; + } + + std::vector<WPXString> tmpSVGOutput; + for (unsigned i=0; i<output.size(); ++i) { + WPXString 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 = 0; + if (inkscape_use_gui()) { + dlg = new VsdImportDialog(tmpSVGOutput); + if (!dlg->showDialog()) { + delete dlg; + return NULL; + } + } + + // 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->getRoot()->viewBox_set) { + doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDefaultUnit()), doc->getHeight().value(doc->getDefaultUnit()))); + } + + return doc; +} + +#include "clear-n_.h" + +void VsdInput::init(void) +{ + /* 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 000000000..3414e0ec9 --- /dev/null +++ b/src/extension/internal/vsd-input.h @@ -0,0 +1,58 @@ +/* + * 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, read the file 'COPYING' for more information + */ + +#ifndef __EXTENSION_INTERNAL_VSDOUTPUT_H__ +#define __EXTENSION_INTERNAL_VSDOUTPUT_H__ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#ifdef WITH_LIBVISIO + +#if GLIBMM_DISABLE_DEPRECATED && HAVE_GLIBMM_THREADS_H +#include <glibmm/threads.h> +#endif + +#include <gtkmm/dialog.h> + +#include "../implementation/implementation.h" + +namespace Inkscape { +namespace Extension { +namespace Internal { + +class VsdInput : public Inkscape::Extension::Implementation::Implementation { + VsdInput () { }; +public: + SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ); + static void init( void ); + +}; + +} } } /* 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 000000000..ef95dbe45 --- /dev/null +++ b/src/extension/internal/wmf-inout.cpp @@ -0,0 +1,3135 @@ +/** @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, 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 + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +//#include <png.h> //This must precede text_reassemble.h or it blows up in pngconf.h when compiling +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <libuemf/symbol_convert.h> + +#include "sp-root.h" +#include "sp-path.h" +#include "style.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 "util/units.h" +#include "clear-n_.h" +#include "document.h" +#include "shape-editor.h" +#include "sp-namedview.h" +#include "document-undo.h" +#include "inkscape.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 U_RECT16 rc_old; +static bool clipset = false; +static uint32_t BLTmode=0; + +Wmf::Wmf (void) // The null constructor +{ + return; +} + + +Wmf::~Wmf (void) //The destructor +{ + return; +} + + +bool +Wmf::check (Inkscape::Extension::Extension * /*module*/) +{ + if (NULL == 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; + unsigned int ret; + + 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 */ + ret = mod->begin(doc); + if (ret) { + g_free(oldoutput); + throw Inkscape::Extension::Output::save_failed(); + } + mod->base->invoke_print(&context); + ret = mod->finish(); + /* Release arena */ + mod->base->invoke_hide(mod->dkey); + mod->base = NULL; + mod->root = NULL; // 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 == NULL) + 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); + + print_document_to_file(doc, filename); + + 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){ + std::stringstream 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); +} + +/* (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; + } + + /* 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); + + *(d->defs) += "\n"; + switch(hatchType){ + case U_HS_HORIZONTAL: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 6 0\" style=\"fill:none;stroke:#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\" />\n"; + break; + case U_HS_VERTICAL: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 0 6\" style=\"fill:none;stroke:#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\" />\n"; + break; + case U_HS_FDIAGONAL: + *(d->defs) += " <line id=\"sub"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\"/>\n"; + break; + case U_HS_BDIAGONAL: + *(d->defs) += " <line id=\"sub"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\"/>\n"; + break; + case U_HS_CROSS: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 6 0 M 0 0 0 6\" style=\"fill:none;stroke:#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\" />\n"; + break; + case U_HS_DIAGCROSS: + *(d->defs) += " <line id=\"subfd"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->defs) += "\"/>\n"; + *(d->defs) += " <line id=\"subbd"; + *(d->defs) += hpathname; + *(d->defs) += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#"; + *(d->defs) += tmpcolor; + *(d->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: + *(d->defs) += " <path id=\""; + *(d->defs) += hpathname; + *(d->defs) += "\" d=\"M 0 0 6 0 6 6 0 6 z\" style=\"fill:#"; + *(d->defs) += tmpcolor; + *(d->defs) += ";stroke:none"; + *(d->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,"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); + *(d->defs) += "\n"; + *(d->defs) += " <pattern id=\""; + *(d->defs) += hatchname; + *(d->defs) += "\" xlink:href=\"#WMFhbasepattern\">\n"; + *(d->defs) += refpath; + *(d->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); + + *(d->defs) += "\n"; + *(d->defs) += " <rect id=\""; + *(d->defs) += hbkname; + *(d->defs) += "\" x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"#"; + *(d->defs) += bkcolor; + *(d->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); + *(d->defs) += "\n"; + *(d->defs) += " <pattern id=\""; + *(d->defs) += hatchname; + *(d->defs) += "\" xlink:href=\"#WMFhbasepattern\">\n"; + *(d->defs) += " <use xlink:href=\"#"; + *(d->defs) += hbkname; + *(d->defs) += "\" />\n"; + *(d->defs) += refpath; + *(d->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 = NULL; + + char *rgba_px = NULL; // RGBA pixels + const char *px = NULL; // DIB pixels + const U_RGBQUAD *ct = NULL; // DIB color table + int32_t width, height, colortype, numCt, 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=NULL; + 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); + 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 + + *(d->defs) += "\n"; + *(d->defs) += " <image id=\""; + *(d->defs) += imagename; + *(d->defs) += "\"\n "; + *(d->defs) += xywh; + *(d->defs) += "\n"; + if(dibparams == U_BI_JPEG){ *(d->defs) += " xlink:href=\"data:image/jpeg;base64,"; } + else { *(d->defs) += " xlink:href=\"data:image/png;base64,"; } + *(d->defs) += base64String; + *(d->defs) += "\"\n"; + *(d->defs) += " preserveAspectRatio=\"none\"\n"; + *(d->defs) += " />\n"; + + + *(d->defs) += "\n"; + *(d->defs) += " <pattern id=\""; + *(d->defs) += imagename; + *(d->defs) += "_ref\"\n "; + *(d->defs) += xywh; + *(d->defs) += "\n patternUnits=\"userSpaceOnUse\""; + *(d->defs) += " >\n"; + *(d->defs) += " <use id=\""; + *(d->defs) += imagename; + *(d->defs) += "_ign\" "; + *(d->defs) += " xlink:href=\"#"; + *(d->defs) += imagename; + *(d->defs) += "\" />\n"; + *(d->defs) += " "; + *(d->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 = NULL; + + char *rgba_px = NULL; // RGBA pixels + const U_RGBQUAD *ct = NULL; // 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=NULL; + 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); + 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 + + *(d->defs) += "\n"; + *(d->defs) += " <image id=\""; + *(d->defs) += imagename; + *(d->defs) += "\"\n "; + *(d->defs) += xywh; + *(d->defs) += "\n"; + *(d->defs) += " xlink:href=\"data:image/png;base64,"; + *(d->defs) += base64String; + *(d->defs) += "\"\n"; + *(d->defs) += " preserveAspectRatio=\"none\"\n"; + *(d->defs) += " />\n"; + + + *(d->defs) += "\n"; + *(d->defs) += " <pattern id=\""; + *(d->defs) += imagename; + *(d->defs) += "_ref\"\n "; + *(d->defs) += xywh; + *(d->defs) += "\n patternUnits=\"userSpaceOnUse\""; + *(d->defs) += " >\n"; + *(d->defs) += " <use id=\""; + *(d->defs) += imagename; + *(d->defs) += "_ign\" "; + *(d->defs) += " xlink:href=\"#"; + *(d->defs) += imagename; + *(d->defs) += "\" />\n"; + *(d->defs) += " </pattern>\n"; + } + g_free(base64String); //wait until this point to free because it might be a duplicate image + return(idx-1); +} + +void +Wmf::output_style(PWMF_CALLBACK_DATA d) +{ +// SVGOStringStream tmp_id; + SVGOStringStream tmp_style; + char tmp[1024] = {0}; + + float fill_rgb[3]; + sp_color_get_rgb_floatv( &(d->dc[d->level].style.fill.value.color), fill_rgb ); + float stroke_rgb[3]; + sp_color_get_rgb_floatv(&(d->dc[d->level].style.stroke.value.color), 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 == 0 ? "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 == 0 ? "butt" : + d->dc[d->level].style.stroke_linecap.computed == 1 ? "round" : + d->dc[d->level].style.stroke_linecap.computed == 2 ? "square" : + "unknown" + ) << ";"; + + tmp_style << "stroke-linejoin:" << + ( + d->dc[d->level].style.stroke_linejoin.computed == 0 ? "miter" : + d->dc[d->level].style.stroke_linejoin.computed == 1 ? "round" : + d->dc[d->level].style.stroke_linejoin.computed == 2 ? "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]; + } + tmp_style << ";"; + tmp_style << "stroke-dashoffset:0;"; + } else { + tmp_style << "stroke-dasharray:none;"; + } + tmp_style << "stroke-opacity:1;"; + } + tmp_style << "\" "; + if (clipset) + tmp_style << "\n\tclip-path=\"url(#clipWmfPath" << d->id << ")\" "; + clipset = false; + + *(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){ + std::stringstream 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 = NULL; + 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); + if (!d->dc[d->level].style.stroke_dasharray.values.empty() && (d->level==0 || (d->level>0 && d->dc[d->level].style.stroke_dasharray.values!=d->dc[d->level-1].style.stroke_dasharray.values))) + d->dc[d->level].style.stroke_dasharray.values.clear(); + if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 3 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + } + if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + } + if (penstyle==U_PS_DASHDOTDOT) { + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + d->dc[d->level].style.stroke_dasharray.values.push_back( 1 ); + } + + d->dc[d->level].style.stroke_dasharray.set = 1; + break; + } + + case U_PS_SOLID: + default: + { + d->dc[d->level].style.stroke_dasharray.set = 0; + break; + } + } + + switch (up.Style & U_PS_ENDCAP_MASK) { + case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = 1; break; } + case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = 2; break; } + case U_PS_ENDCAP_FLAT: + default: { d->dc[d->level].style.stroke_linecap.computed = 0; break; } + } + + switch (up.Style & U_PS_JOIN_MASK) { + case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = 2; break; } + case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = 0; break; } + case U_PS_JOIN_ROUND: + default: { d->dc[d->level].style.stroke_linejoin.computed = 1; 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; // Pointer to Bitmap16 header (px follows) + const char *dib; // 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 if(Bm16h){ + 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 = NULL; + 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 : + U_FW_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 != NULL){ 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 = 0; + d->dc[d->level].style.stroke_linecap.computed = 2; // U_PS_ENDCAP_SQUARE + d->dc[d->level].style.stroke_linejoin.computed = 0; // 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 = 0; + d->dc[d->level].style.text_decoration_line.line_through = 0; + 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 +// reord 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 = NULL; + 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"; + tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n "; + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = NULL; + + char *rgba_px = NULL; // RGBA pixels + char *sub_px = NULL; // RGBA pixels, subarray + const char *px = NULL; // DIB pixels + const U_RGBQUAD *ct = NULL; // color table + int32_t width, height, colortype, numCt, 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=NULL; + 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"; + tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n "; + + MEMPNG mempng; // PNG in memory comes back in this + mempng.buffer = NULL; + + char *rgba_px = NULL; // RGBA pixels + char *sub_px = NULL; // RGBA pixels, subarray + const U_RGBQUAD *ct = NULL; // 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=NULL; + 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; + 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 + + /* initialize the tsp for text reassembly */ + 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.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 != NULL ) + { + for( int i=0; i < d->n_obj; ++i ) + d->wmf_obj[i].record = NULL; + } //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; + } + } + } + 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 + + SVGOStringStream tmp_path; + SVGOStringStream tmp_str; + + /* Check that the current record size is OK, abort if not. + Pointer math might wrap, so check both sides of the range. + Some of the records will reset this with the same value,others will not + return a value at this time. */ + nSize = U_WMRRECSAFE_get(contents, blimit); + if(!nSize)break; + + iType = *(uint8_t *)(contents + offsetof(U_METARECORD, iType ) ); + +// Uncomment the following to track down toxic records +// std::cout << "record type: " << (int) iType << " name " << U_wmr_names(iType) << " length: " << nSize << " offset: " << off <<std::endl; + + wmr_mask = U_wmr_properties(iType); + if(wmr_mask == U_WMR_INVALID){ throw "Inkscape fatal programming error at U_wmr_properties"; } + +/* 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); + 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].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) + << " 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; +*/ + + 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 + ( (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; + *(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 ? 0 : utmp16 == U_WINDING ? 1 : 0); + 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"; break; + case U_WMR_INTERSECTCLIPRECT: + { + dbg_str << "<!-- U_WMR_INTERSECTCLIPRECT -->\n"; + + nSize = U_WMRINTERSECTCLIPRECT_get(contents, &rc); + clipset = true; + if ((rc.left == rc_old.left) && (rc.top == rc_old.top) && (rc.right == rc_old.right) && (rc.bottom == rc_old.bottom)) + break; + rc_old = rc; + + double dx = pix_to_x_point( d, rc.left, rc.top ); + double dy = pix_to_y_point( d, rc.left, rc.top ); + double dw = pix_to_abs_size( d, rc.right - rc.left + 1); + double dh = pix_to_abs_size( d, rc.bottom - rc.top + 1); + + SVGOStringStream tmp_rectangle; + tmp_rectangle << "\n<clipPath\n\tclipPathUnits=\"userSpaceOnUse\" "; + tmp_rectangle << "\nid=\"clipWmfPath" << ++(d->id) << "\" >"; + tmp_rectangle << "\n<rect "; + tmp_rectangle << "\n x=\"" << dx << "\" "; + tmp_rectangle << "\n y=\"" << dy << "\" "; + tmp_rectangle << "\n width=\"" << dw << "\" "; + tmp_rectangle << "\n height=\"" << dh << "\" />"; + tmp_rectangle << "\n</clipPath>"; + + *(d->outdef) += tmp_rectangle.str().c_str(); + *(d->path) = ""; + 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, fabs(rc.right - rc.left )/2.0 ); + double ry = pix_to_abs_size( d, fabs(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_WMR_OFFSETCLIPRGN/POLYLINE -->\n"; 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 = 1; break; } + case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = 2; break; } + case U_PS_ENDCAP_FLAT: + default: { d->dc[d->level].style.stroke_linecap.computed = 0; 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 = 2; break; } + case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = 0; break; } + case U_PS_JOIN_ROUND: + default: { d->dc[d->level].style.stroke_linejoin.computed = 1; 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.values!=d->dc[old_level-1].style.stroke_dasharray.values))){ + 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 = NULL; + } + 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 = NULL; + + dup_wt = U_Latin1ToUtf32le(text, cChars, NULL); + 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, NULL); + 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=NULL; + } + + 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); + 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 + } + + 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 +// When testing, uncomment the following to place a comment for each processed WMR record in the SVG +// *(d->outsvg) += dbg_str.str().c_str(); + *(d->path) += tmp_path.str().c_str(); + if(!nSize){ OK=0; std::cout << "nSize == 0, oops!!!" << std::endl; } // There was some problem with this record, it is not safe to continue + + } //end of while +// When testing, uncomment the following to show the final SVG derived from the WMF +// 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 1; +} + +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); + } +} + +SPDocument * +Wmf::open( Inkscape::Extension::Input * /*mod*/, const gchar *uri ) +{ + + WMF_CALLBACK_DATA d; + + memset(&d, 0, sizeof(WMF_CALLBACK_DATA)); + + for(int i = 0; i < WMF_MAX_DC+1; i++){ // be sure all values and pointers are empty to start with + memset(&(d.dc[i]),0,sizeof(WMF_DEVICE_CONTEXT)); + } + // set default drawing objects, these are active if no object has been selected + d.dc[0].active_pen = -1; // -1 when the default is used instead of a selected object + d.dc[0].active_brush = -1; + d.dc[0].active_font = -1; + // 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].font_name = strdup("Arial"); + 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 = 0; + d.dc[0].style.text_decoration_line.line_through = 0; + d.dc[0].style.baseline_shift.value = 0; + d.dc[0].textColor = U_RGB(0, 0, 0); // default foreground color (black) + d.dc[0].bkColor = U_RGB(255, 255, 255); // default background color (white) + d.dc[0].bkMode = U_TRANSPARENT; + d.dc[0].dirty = 0; + // Default pen, WMF files that do not specify a pen are unlikely to look very good! + d.dc[0].style.stroke_dasharray.set = 0; + d.dc[0].style.stroke_linecap.computed = 2; // U_PS_ENDCAP_SQUARE; + d.dc[0].style.stroke_linejoin.computed = 0; // U_PS_JOIN_MITER; + d.dc[0].stroke_set = true; + d.dc[0].style.stroke_width.value = 1.0; // will be reset to something reasonable once WMF draying size is known + d.dc[0].style.stroke.value.color.set( 0, 0, 0 ); + // Default brush = none, WMF files that do not specify a brush are unlikely to look very good! + d.dc[0].fill_set = false; + + if (uri == NULL) { + return NULL; + } + + d.outsvg = new Glib::ustring(""); + d.path = new Glib::ustring(""); + d.outdef = new Glib::ustring(""); + d.defs = new Glib::ustring(""); + d.mask = 0; + d.drawtype = 0; + d.arcdir = U_AD_COUNTERCLOCKWISE; + d.dwRop2 = U_R2_COPYPEN; + d.dwRop3 = 0; + d.E2IdirY = 1.0; + d.D2PscaleX = 1.0; + d.D2PscaleY = 1.0; + d.hatches.size = 0; + d.hatches.count = 0; + d.hatches.strings = NULL; + d.images.size = 0; + d.images.count = 0; + d.images.strings = NULL; + + // 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(NULL); + + // set up the text reassembly system + if(!(d.tri = trinfo_init(NULL)))return(NULL); + (void) trinfo_load_ft_opts(d.tri, 1, + FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, + FT_KERNING_UNSCALED); + + (void) myMetaFileProc(contents,length, &d); + free(contents); + +// std::cout << "SVG Output: " << std::endl << *(d.outsvg) << std::endl; + + SPDocument *doc = SPDocument::createNewDocFromMem(d.outsvg->c_str(), strlen(d.outsvg->c_str()), TRUE); + + delete d.outsvg; + delete d.path; + delete d.outdef; + delete d.defs; + free_wmf_strings(d.hatches); + free_wmf_strings(d.images); + + 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<=d.level;i++){ + if(d.dc[i].font_name)free(d.dc[i].font_name); + } + + d.tri = trinfo_release_except_FC(d.tri); + + // Set viewBox if it doesn't exist + 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, 0)->getRepr(); + Inkscape::SVGOStringStream os; + Inkscape::Util::Unit const* doc_unit = doc->getWidth().unit; + os << doc_unit->abbr; + repr->setAttribute("inkscape:document-units", os.str().c_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); + 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"); + } + doc->getRoot()->scaleChildItemsRec(Geom::Scale(scale), Geom::Point(0, dh)); + ShapeEditor::blockSetItem(false); + + Inkscape::DocumentUndo::setUndoSensitive(doc, saved); + } + + return doc; +} + + +void +Wmf::init (void) +{ + /* 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=\"boolean\">true</param>\n" + "<param name=\"TnrToSymbol\" gui-text=\"" N_("Map Unicode to Symbol font") "\" type=\"boolean\">true</param>\n" + "<param name=\"TnrToWingdings\" gui-text=\"" N_("Map Unicode to Wingdings") "\" type=\"boolean\">true</param>\n" + "<param name=\"TnrToZapfDingbats\" gui-text=\"" N_("Map Unicode to Zapf Dingbats") "\" type=\"boolean\">true</param>\n" + "<param name=\"UsePUA\" gui-text=\"" N_("Use MS Unicode PUA (0xF020-0xF0FF) for converted characters") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTCharPos\" gui-text=\"" N_("Compensate for PPT font bug") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTDashLine\" gui-text=\"" N_("Convert dashed/dotted lines to single lines") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTGrad2Polys\" gui-text=\"" N_("Convert gradients to colored polygon series") "\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTPatternAsHatch\" gui-text=\"" N_("Map all fill patterns to standard WMF hatches") "\" type=\"boolean\">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 000000000..3d23ca749 --- /dev/null +++ b/src/extension/internal/wmf-inout.h @@ -0,0 +1,180 @@ +/** @file + * @brief Windows Metafile Input/Output + */ +/* Authors: + * Ulf Erikson <ulferikson@users.sf.net> + * + * Copyright (C) 2006-2008 Authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ +#ifndef SEEN_EXTENSION_INTERNAL_WMF_H +#define SEEN_EXTENSION_INTERNAL_WMF_H + +#include <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 + +typedef struct { + int type; + int level; + char *record; +} WMF_OBJECT, *PWMF_OBJECT; + +typedef struct { + int size; // number of slots allocated in strings + int count; // number of slots used in strings + char **strings; // place to store strings +} WMF_STRINGS, *PWMF_STRINGS; + +typedef struct wmf_device_context { + struct SPStyle style; + char *font_name; + 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; +} WMF_DEVICE_CONTEXT, *PWMF_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 +typedef struct { + 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. + 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; +} WMF_CALLBACK_DATA, *PWMF_CALLBACK_DATA; + +class Wmf : public Metafile +{ + +public: + Wmf(); // Empty constructor + + virtual ~Wmf();//Destructor + + bool check(Inkscape::Extension::Extension *module); //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); + + virtual SPDocument *open( Inkscape::Extension::Input *mod, + const gchar *uri ); + + static void init(void);//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 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 000000000..5a552ad83 --- /dev/null +++ b/src/extension/internal/wmf-print.cpp @@ -0,0 +1,1578 @@ +/** @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, 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 + */ + + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + + +#include "2geom/sbasis-to-bezier.h" +#include "2geom/svg-elliptical-arc.h" + +#include "2geom/path.h" +#include "2geom/pathvector.h" +#include "2geom/rect.h" +#include "2geom/bezier-curve.h" +#include "2geom/hvlinesegment.h" +#include "helper/geom.h" +#include "helper/geom-curves.h" +#include "sp-item.h" + +#include "style.h" +#include "inkscape-version.h" +#include "sp-root.h" + +#include "util/units.h" + +#include "extension/system.h" +#include "extension/print.h" +#include "document.h" +#include "path-prefix.h" +#include "sp-pattern.h" +#include "sp-image.h" +#include "sp-gradient.h" +#include "sp-radial-gradient.h" +#include "sp-linear-gradient.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 "wmf-print.h" + +#include <string.h> +#include <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 = NULL; +static WMFHANDLES *wht = NULL; + +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 = NULL; + *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 = sp_repr_lookup_name(doc->rroot, "sodipodi:namedview"); + 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 + if (!rec) { + g_error("Fatal programming error in PrintWmf::begin at WMRSETMAPMODE"); + } + (void) wmf_header_append((U_METARECORD *)rec, wt, 1); + free(ps); + + rec = U_WMRSETWINDOWEXT_set(point16_set(dwPxX, dwPxY)); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::begin at WMRSETWINDOWEXT"); + } + + rec = U_WMRSETWINDOWORG_set(point16_set(0, 0)); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::begin at WMRSETWINDOWORG"); + } + + rec = U_WMRSETMAPMODE_set(U_MM_ANISOTROPIC); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::begin at WMRSETMAPMODE"); + } + + /* 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_error("Fatal programming error in PrintWmf::begin at U_WMRSETBKMODE"); + } + + hpolyfillmode = U_WINDING; + rec = U_WMRSETPOLYFILLMODE_set(U_WINDING); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::begin at U_WMRSETPOLYFILLMODE"); + } + + // 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_error("Fatal programming error in PrintWmf::begin at U_WMRSETTEXTALIGN_set"); + } + + 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_error("Fatal programming error in PrintWmf::begin at U_WMRSETTEXTCOLOR_set"); + } + + rec = U_WMRSETROP2_set(U_R2_COPYPEN); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::begin at U_WMRSETROP2"); + } + + hmiterlimit = 5; + rec = wmiterlimit_set(5); + if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) { + g_error("Fatal programming error in PrintWmf::begin at wmiterlimit_set"); + } + + + // 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_error("Fatal programming error in PrintWmf::begin at wcreatepenindirect_set"); + } + + // 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_error("Fatal programming error in PrintWmf::begin at wcreatepenindirect_set"); + } + 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_error("Fatal programming error in PrintWmf::begin at wcreatebrushindirect_set"); + } + 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 + wmf_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; + float opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value); + if (opacity <= 0.0) { + opacity = 0.0; // basically the same as no fill + } + + sp_color_get_rgb_floatv(&style->fill.value.color, 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 = pattern_width(pat); + double dheight = pattern_height(pat); + 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 = NULL; + SPRadialGradient *rg = NULL; + + 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); + // Not sure why the next swap is needed because the preceding does it, and the code is identical + // to that in stretchdibits_set, which does not need this. + 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 = NULL; + 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. + sp_color_get_rgb_floatv(&style->stroke.value.color, 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. + penstyle = U_PS_DASH;// userstyle not supported apparently, for now map all Inkscape dot/dash to just dash + } + } + + } + + 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 = NULL; + // 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, NULL)) { + /* + 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(); + sp_color_get_rgb_floatv(&tg->vector.stops[0].color, rgb); + opa = tg->vector.stops[0].opacity; + c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa); + sp_color_get_rgb_floatv(&tg->vector.stops[nstops - 1].color, 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); + + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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; + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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); + + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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; + sp_color_get_rgb_floatv(&tg->vector.stops[istop].color, 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 (Geom::PathVector::const_iterator pit = pathv.begin(); pit != pathv.end(); ++pit) { + 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 = NULL; + 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 (unsigned int i = 0; i < pathv.size(); i++) { + tmp_pathpw.concat(pathv[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++]; + 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++]; // 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 = NULL; + + for (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + moves++; + nodes++; + + for (Geom::Path::const_iterator cit = pit->begin(); cit != pit->end_open(); ++cit) { + nodes++; + + if (is_straight_curve(*cit)) { + lines++; + } else if (&*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 (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + 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->points(); + //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 tranforms, use the one from m_tr_stack */ + SPStyle const * /*style*/) /** provides indirect link to image object */ +{ + double x1, y1, dw, dh; + char *rec = NULL; + 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 = NULL; + 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 (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + 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 (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + 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 interator + + 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 (Geom::PathVector::const_iterator pit = pv.begin(); pit != pv.end(); ++pit) { + 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 interator + + 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) { + return 0; + } + + char *rec = NULL; + 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; + 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, NULL); + 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, NULL); + free(unicode_text); + + //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->text->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 (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->text->font_family.value, 0, NULL); + } else { + facename = U_Utf8ToLatin1(FontName(newfont), 0, NULL); + } + + // 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]; + sp_color_get_rgb_floatv(&style->fill.value.color, 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(void) +{ + _load_ppt_fontfix_data(); + + /* 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 name=\"destination\" type=\"string\"></param>\n" + "<param name=\"textToPath\" type=\"boolean\">true</param>\n" + "<param name=\"pageBoundingBox\" type=\"boolean\">true</param>\n" + "<param name=\"FixPPTCharPos\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTDashLine\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTGrad2Polys\" type=\"boolean\">false</param>\n" + "<param name=\"FixPPTPatternAsHatch\" type=\"boolean\">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/emf-win32-print.h b/src/extension/internal/wmf-print.h index e7bd08477..1e5d4c323 100644 --- a/src/extension/internal/emf-win32-print.h +++ b/src/extension/internal/wmf-print.h @@ -1,5 +1,5 @@ /** @file - * @brief Enhanced Metafile printing - implementation + * @brief Windows Metafile printing - implementation */ /* Author: * Ulf Erikson <ulferikson@users.sf.net> @@ -8,59 +8,34 @@ * * Released under GNU GPL, read the file 'COPYING' for more information */ -#ifndef __INKSCAPE_EXTENSION_INTERNAL_PRINT_EMF_WIN32_H__ -#define __INKSCAPE_EXTENSION_INTERNAL_PRINT_EMF_WIN32_H__ - -#ifdef WIN32 +#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_WMF_PRINT_H +#define SEEN_INKSCAPE_EXTENSION_INTERNAL_WMF_PRINT_H #ifdef HAVE_CONFIG_H # include "config.h" #endif -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include "extension/implementation/implementation.h" -//#include "extension/extension.h" - -#include <2geom/pathvector.h> +#include <libuemf/uwmf.h> +#include "extension/internal/metafile-print.h" -#include <stack> +#include "sp-gradient.h" +#include "splivarot.h" // pieces for union on shapes +#include "display/canvas-bpath.h" // for SPWindRule namespace Inkscape { namespace Extension { namespace Internal { -class PrintEmfWin32 : public Inkscape::Extension::Implementation::Implementation +class PrintWmf : public PrintMetafile { - double _width; - double _height; - HDC hdc; - RECT rc; - - HBRUSH hbrush, hbrushOld; - HPEN hpen, hpenOld; - - std::stack<Geom::Affine> m_tr_stack; - Geom::PathVector fill_pathv; - Geom::Affine fill_transform; - bool stroke_and_fill; - bool fill_only; - bool simple_shape; + uint32_t hbrush, hpen, hpenOld, 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); - unsigned int image(Inkscape::Extension::Print * /* module */, /** not used */ - unsigned char *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_ignore, /** WRONG affine transform, use the one from m_tr_stack */ - SPStyle const *style); /** provides indirect link to image object */ public: - PrintEmfWin32 (void); - virtual ~PrintEmfWin32 (void); + PrintWmf(); /* Print functions */ virtual unsigned int setup (Inkscape::Extension::Print * module); @@ -69,8 +44,6 @@ public: virtual unsigned int finish (Inkscape::Extension::Print * module); /* Rendering methods */ - virtual unsigned int bind(Inkscape::Extension::Print *module, Geom::Affine const &transform, float opacity); - virtual unsigned int release(Inkscape::Extension::Print *module); virtual unsigned int fill (Inkscape::Extension::Print *module, Geom::PathVector const &pathv, Geom::Affine const &ctm, SPStyle const *style, @@ -81,32 +54,33 @@ public: Geom::Affine const &ctm, SPStyle const *style, Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox); + virtual 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); virtual unsigned int comment(Inkscape::Extension::Print *module, const char * comment); virtual unsigned int text(Inkscape::Extension::Print *module, char const *text, Geom::Point const &p, SPStyle const *style); - bool textToPath (Inkscape::Extension::Print * ext); static void init (void); protected: - int create_brush(SPStyle const *style); + 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); void destroy_brush(); - - void create_pen(SPStyle const *style, const Geom::Affine &transform); - + int create_pen(SPStyle const *style, const Geom::Affine &transform); void destroy_pen(); - - void flush_fill(); - }; } /* namespace Internal */ } /* namespace Extension */ } /* namespace Inkscape */ -#endif /* WIN32 */ -#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_EMF_WIN32_H__ */ +#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_WMF_H__ */ /* Local Variables: diff --git a/src/extension/internal/wpg-input.cpp b/src/extension/internal/wpg-input.cpp index cb1677547..14ff3ec77 100644 --- a/src/extension/internal/wpg-input.cpp +++ b/src/extension/internal/wpg-input.cpp @@ -48,6 +48,8 @@ #include "extension/system.h" #include "extension/input.h" #include "document.h" +#include "sp-root.h" +#include "util/units.h" #include <cstring> // Take a guess and fallback to 0.1.x if no configure has run @@ -109,6 +111,12 @@ SPDocument *WpgInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * u //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->getDefaultUnit()), doc->getHeight().value(doc->getDefaultUnit()))); + } + delete input; return doc; } |
