summaryrefslogtreecommitdiffstats
path: root/src/extension
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:24:48 +0000
commitcca66b9ec4e494c1d919bff0f71a820d8afab1fa (patch)
tree146f39ded1c938019e1ed42d30923c2ac9e86789 /src/extension
parentInitial commit. (diff)
downloadinkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.tar.xz
inkscape-cca66b9ec4e494c1d919bff0f71a820d8afab1fa.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/extension')
-rw-r--r--src/extension/CMakeLists.txt259
-rw-r--r--src/extension/db.cpp327
-rw-r--r--src/extension/db.h95
-rw-r--r--src/extension/dependency.cpp362
-rw-r--r--src/extension/dependency.h98
-rw-r--r--src/extension/effect.cpp339
-rw-r--r--src/extension/effect.h91
-rw-r--r--src/extension/execution-env.cpp251
-rw-r--r--src/extension/execution-env.h119
-rw-r--r--src/extension/extension.cpp1111
-rw-r--r--src/extension/extension.h307
-rw-r--r--src/extension/find_extension_by_mime.h39
-rw-r--r--src/extension/implementation/implementation.cpp70
-rw-r--r--src/extension/implementation/implementation.h208
-rw-r--r--src/extension/implementation/script.cpp1022
-rw-r--r--src/extension/implementation/script.h138
-rw-r--r--src/extension/implementation/xslt.cpp253
-rw-r--r--src/extension/implementation/xslt.h66
-rw-r--r--src/extension/init.cpp328
-rw-r--r--src/extension/init.h38
-rw-r--r--src/extension/input.cpp254
-rw-r--r--src/extension/input.h69
-rw-r--r--src/extension/internal/bitmap/adaptiveThreshold.cpp60
-rw-r--r--src/extension/internal/bitmap/adaptiveThreshold.h32
-rw-r--r--src/extension/internal/bitmap/addNoise.cpp71
-rw-r--r--src/extension/internal/bitmap/addNoise.h30
-rw-r--r--src/extension/internal/bitmap/blur.cpp58
-rw-r--r--src/extension/internal/bitmap/blur.h31
-rw-r--r--src/extension/internal/bitmap/channel.cpp77
-rw-r--r--src/extension/internal/bitmap/channel.h32
-rw-r--r--src/extension/internal/bitmap/charcoal.cpp58
-rw-r--r--src/extension/internal/bitmap/charcoal.h31
-rw-r--r--src/extension/internal/bitmap/colorize.cpp69
-rw-r--r--src/extension/internal/bitmap/colorize.h34
-rw-r--r--src/extension/internal/bitmap/contrast.cpp59
-rw-r--r--src/extension/internal/bitmap/contrast.h30
-rw-r--r--src/extension/internal/bitmap/crop.cpp89
-rw-r--r--src/extension/internal/bitmap/crop.h35
-rw-r--r--src/extension/internal/bitmap/cycleColormap.cpp56
-rw-r--r--src/extension/internal/bitmap/cycleColormap.h29
-rw-r--r--src/extension/internal/bitmap/despeckle.cpp54
-rw-r--r--src/extension/internal/bitmap/despeckle.h27
-rw-r--r--src/extension/internal/bitmap/edge.cpp56
-rw-r--r--src/extension/internal/bitmap/edge.h29
-rw-r--r--src/extension/internal/bitmap/emboss.cpp58
-rw-r--r--src/extension/internal/bitmap/emboss.h31
-rw-r--r--src/extension/internal/bitmap/enhance.cpp53
-rw-r--r--src/extension/internal/bitmap/enhance.h28
-rw-r--r--src/extension/internal/bitmap/equalize.cpp53
-rw-r--r--src/extension/internal/bitmap/equalize.h28
-rw-r--r--src/extension/internal/bitmap/gaussianBlur.cpp58
-rw-r--r--src/extension/internal/bitmap/gaussianBlur.h31
-rw-r--r--src/extension/internal/bitmap/imagemagick.cpp255
-rw-r--r--src/extension/internal/bitmap/imagemagick.h49
-rw-r--r--src/extension/internal/bitmap/implode.cpp56
-rw-r--r--src/extension/internal/bitmap/implode.h30
-rw-r--r--src/extension/internal/bitmap/level.cpp62
-rw-r--r--src/extension/internal/bitmap/level.h32
-rw-r--r--src/extension/internal/bitmap/levelChannel.cpp84
-rw-r--r--src/extension/internal/bitmap/levelChannel.h33
-rw-r--r--src/extension/internal/bitmap/medianFilter.cpp56
-rw-r--r--src/extension/internal/bitmap/medianFilter.h30
-rw-r--r--src/extension/internal/bitmap/modulate.cpp61
-rw-r--r--src/extension/internal/bitmap/modulate.h32
-rw-r--r--src/extension/internal/bitmap/negate.cpp54
-rw-r--r--src/extension/internal/bitmap/negate.h28
-rw-r--r--src/extension/internal/bitmap/normalize.cpp54
-rw-r--r--src/extension/internal/bitmap/normalize.h28
-rw-r--r--src/extension/internal/bitmap/oilPaint.cpp56
-rw-r--r--src/extension/internal/bitmap/oilPaint.h30
-rw-r--r--src/extension/internal/bitmap/opacity.cpp57
-rw-r--r--src/extension/internal/bitmap/opacity.h30
-rw-r--r--src/extension/internal/bitmap/raise.cpp61
-rw-r--r--src/extension/internal/bitmap/raise.h32
-rw-r--r--src/extension/internal/bitmap/reduceNoise.cpp59
-rw-r--r--src/extension/internal/bitmap/reduceNoise.h30
-rw-r--r--src/extension/internal/bitmap/sample.cpp59
-rw-r--r--src/extension/internal/bitmap/sample.h31
-rw-r--r--src/extension/internal/bitmap/shade.cpp61
-rw-r--r--src/extension/internal/bitmap/shade.h32
-rw-r--r--src/extension/internal/bitmap/sharpen.cpp58
-rw-r--r--src/extension/internal/bitmap/sharpen.h31
-rw-r--r--src/extension/internal/bitmap/solarize.cpp58
-rw-r--r--src/extension/internal/bitmap/solarize.h30
-rw-r--r--src/extension/internal/bitmap/spread.cpp56
-rw-r--r--src/extension/internal/bitmap/spread.h30
-rw-r--r--src/extension/internal/bitmap/swirl.cpp56
-rw-r--r--src/extension/internal/bitmap/swirl.h30
-rw-r--r--src/extension/internal/bitmap/threshold.cpp57
-rw-r--r--src/extension/internal/bitmap/threshold.h30
-rw-r--r--src/extension/internal/bitmap/unsharpmask.cpp63
-rw-r--r--src/extension/internal/bitmap/unsharpmask.h33
-rw-r--r--src/extension/internal/bitmap/wave.cpp58
-rw-r--r--src/extension/internal/bitmap/wave.h31
-rw-r--r--src/extension/internal/bluredge.cpp166
-rw-r--r--src/extension/internal/bluredge.h47
-rw-r--r--src/extension/internal/cairo-ps-out.cpp415
-rw-r--r--src/extension/internal/cairo-ps-out.h63
-rw-r--r--src/extension/internal/cairo-render-context.cpp2040
-rw-r--r--src/extension/internal/cairo-render-context.h269
-rw-r--r--src/extension/internal/cairo-renderer-pdf-out.cpp335
-rw-r--r--src/extension/internal/cairo-renderer-pdf-out.h47
-rw-r--r--src/extension/internal/cairo-renderer.cpp1012
-rw-r--r--src/extension/internal/cairo-renderer.h92
-rw-r--r--src/extension/internal/cdr-input.cpp382
-rw-r--r--src/extension/internal/cdr-input.h55
-rw-r--r--src/extension/internal/clear-n_.h33
-rw-r--r--src/extension/internal/emf-inout.cpp3688
-rw-r--r--src/extension/internal/emf-inout.h249
-rw-r--r--src/extension/internal/emf-print.cpp2210
-rw-r--r--src/extension/internal/emf-print.h97
-rw-r--r--src/extension/internal/filter/BUILD_YOUR_OWN2
-rw-r--r--src/extension/internal/filter/bevels.h289
-rw-r--r--src/extension/internal/filter/blurs.h440
-rw-r--r--src/extension/internal/filter/bumps.h494
-rw-r--r--src/extension/internal/filter/color.h1963
-rw-r--r--src/extension/internal/filter/distort.h258
-rw-r--r--src/extension/internal/filter/filter-all.cpp128
-rw-r--r--src/extension/internal/filter/filter-file.cpp139
-rw-r--r--src/extension/internal/filter/filter.cpp236
-rw-r--r--src/extension/internal/filter/filter.h62
-rw-r--r--src/extension/internal/filter/image.h118
-rw-r--r--src/extension/internal/filter/morphology.h334
-rw-r--r--src/extension/internal/filter/overlays.h150
-rw-r--r--src/extension/internal/filter/paint.h1061
-rw-r--r--src/extension/internal/filter/protrusions.h104
-rw-r--r--src/extension/internal/filter/shadows.h194
-rw-r--r--src/extension/internal/filter/textures.h163
-rw-r--r--src/extension/internal/filter/transparency.h420
-rw-r--r--src/extension/internal/gdkpixbuf-input.cpp257
-rw-r--r--src/extension/internal/gdkpixbuf-input.h40
-rw-r--r--src/extension/internal/gimpgrad.cpp294
-rw-r--r--src/extension/internal/gimpgrad.h50
-rw-r--r--src/extension/internal/grid.cpp228
-rw-r--r--src/extension/internal/grid.h47
-rw-r--r--src/extension/internal/image-resolution.cpp447
-rw-r--r--src/extension/internal/image-resolution.h42
-rw-r--r--src/extension/internal/latex-pstricks-out.cpp118
-rw-r--r--src/extension/internal/latex-pstricks-out.h50
-rw-r--r--src/extension/internal/latex-pstricks.cpp340
-rw-r--r--src/extension/internal/latex-pstricks.h80
-rw-r--r--src/extension/internal/latex-text-renderer.cpp755
-rw-r--r--src/extension/internal/latex-text-renderer.h98
-rw-r--r--src/extension/internal/metafile-inout.cpp294
-rw-r--r--src/extension/internal/metafile-inout.h93
-rw-r--r--src/extension/internal/metafile-print.cpp478
-rw-r--r--src/extension/internal/metafile-print.h129
-rw-r--r--src/extension/internal/odf.cpp2126
-rw-r--r--src/extension/internal/odf.h329
-rw-r--r--src/extension/internal/pdfinput/pdf-input.cpp1003
-rw-r--r--src/extension/internal/pdfinput/pdf-input.h170
-rw-r--r--src/extension/internal/pdfinput/pdf-parser.cpp3368
-rw-r--r--src/extension/internal/pdfinput/pdf-parser.h356
-rw-r--r--src/extension/internal/pdfinput/poppler-transition-api.h95
-rw-r--r--src/extension/internal/pdfinput/svg-builder.cpp1983
-rw-r--r--src/extension/internal/pdfinput/svg-builder.h257
-rw-r--r--src/extension/internal/png-output.cpp106
-rw-r--r--src/extension/internal/png-output.h48
-rw-r--r--src/extension/internal/polyfill/README.md19
-rw-r--r--src/extension/internal/polyfill/hatch.js401
-rw-r--r--src/extension/internal/polyfill/hatch_compressed.include4
-rw-r--r--src/extension/internal/polyfill/hatch_tests/hatch.svg63
-rw-r--r--src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg133
-rw-r--r--src/extension/internal/polyfill/hatch_tests/hatch_test.svg11730
-rw-r--r--src/extension/internal/polyfill/mesh.js1192
-rw-r--r--src/extension/internal/polyfill/mesh_compressed.include4
-rw-r--r--src/extension/internal/pov-out.cpp744
-rw-r--r--src/extension/internal/pov-out.h190
-rw-r--r--src/extension/internal/svg.cpp1056
-rw-r--r--src/extension/internal/svg.h71
-rw-r--r--src/extension/internal/svgz.cpp100
-rw-r--r--src/extension/internal/svgz.h42
-rw-r--r--src/extension/internal/text_reassemble.c2973
-rw-r--r--src/extension/internal/text_reassemble.h397
-rw-r--r--src/extension/internal/vsd-input.cpp380
-rw-r--r--src/extension/internal/vsd-input.h55
-rw-r--r--src/extension/internal/wmf-inout.cpp3262
-rw-r--r--src/extension/internal/wmf-inout.h237
-rw-r--r--src/extension/internal/wmf-print.cpp1602
-rw-r--r--src/extension/internal/wmf-print.h86
-rw-r--r--src/extension/internal/wpg-input.cpp157
-rw-r--r--src/extension/internal/wpg-input.h53
-rw-r--r--src/extension/loader.cpp140
-rw-r--r--src/extension/loader.h77
-rw-r--r--src/extension/output.cpp303
-rw-r--r--src/extension/output.h80
-rw-r--r--src/extension/patheffect.cpp91
-rw-r--r--src/extension/patheffect.h46
-rw-r--r--src/extension/plugins/CMakeLists.txt2
-rw-r--r--src/extension/plugins/grid2/CMakeLists.txt9
-rw-r--r--src/extension/plugins/grid2/grid.cpp213
-rw-r--r--src/extension/plugins/grid2/grid.h59
-rw-r--r--src/extension/plugins/grid2/libgrid2.inx21
-rw-r--r--src/extension/prefdialog/parameter-bool.cpp141
-rw-r--r--src/extension/prefdialog/parameter-bool.h77
-rw-r--r--src/extension/prefdialog/parameter-color.cpp146
-rw-r--r--src/extension/prefdialog/parameter-color.h77
-rw-r--r--src/extension/prefdialog/parameter-float.cpp191
-rw-r--r--src/extension/prefdialog/parameter-float.h79
-rw-r--r--src/extension/prefdialog/parameter-int.cpp189
-rw-r--r--src/extension/prefdialog/parameter-int.h74
-rw-r--r--src/extension/prefdialog/parameter-notebook.cpp285
-rw-r--r--src/extension/prefdialog/parameter-notebook.h86
-rw-r--r--src/extension/prefdialog/parameter-optiongroup.cpp351
-rw-r--r--src/extension/prefdialog/parameter-optiongroup.h103
-rw-r--r--src/extension/prefdialog/parameter-path.cpp268
-rw-r--r--src/extension/prefdialog/parameter-path.h75
-rw-r--r--src/extension/prefdialog/parameter-string.cpp228
-rw-r--r--src/extension/prefdialog/parameter-string.h66
-rw-r--r--src/extension/prefdialog/parameter.cpp306
-rw-r--r--src/extension/prefdialog/parameter.h162
-rw-r--r--src/extension/prefdialog/prefdialog.cpp237
-rw-r--r--src/extension/prefdialog/prefdialog.h91
-rw-r--r--src/extension/prefdialog/widget-box.cpp115
-rw-r--r--src/extension/prefdialog/widget-box.h60
-rw-r--r--src/extension/prefdialog/widget-image.cpp87
-rw-r--r--src/extension/prefdialog/widget-image.h61
-rw-r--r--src/extension/prefdialog/widget-label.cpp121
-rw-r--r--src/extension/prefdialog/widget-label.h65
-rw-r--r--src/extension/prefdialog/widget-separator.cpp43
-rw-r--r--src/extension/prefdialog/widget-separator.h53
-rw-r--r--src/extension/prefdialog/widget-spacer.cpp62
-rw-r--r--src/extension/prefdialog/widget-spacer.h60
-rw-r--r--src/extension/prefdialog/widget.cpp178
-rw-r--r--src/extension/prefdialog/widget.h149
-rw-r--r--src/extension/print.cpp124
-rw-r--r--src/extension/print.h94
-rw-r--r--src/extension/system.cpp710
-rw-r--r--src/extension/system.h104
-rw-r--r--src/extension/timer.cpp211
-rw-r--r--src/extension/timer.h75
231 files changed, 72366 insertions, 0 deletions
diff --git a/src/extension/CMakeLists.txt b/src/extension/CMakeLists.txt
new file mode 100644
index 0000000..867d235
--- /dev/null
+++ b/src/extension/CMakeLists.txt
@@ -0,0 +1,259 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+set(extension_SRC
+ db.cpp
+ dependency.cpp
+ effect.cpp
+ execution-env.cpp
+ extension.cpp
+ init.cpp
+ input.cpp
+ output.cpp
+ patheffect.cpp
+ print.cpp
+ system.cpp
+ timer.cpp
+ loader.cpp
+
+ implementation/implementation.cpp
+ implementation/xslt.cpp
+ implementation/script.cpp
+
+ internal/bluredge.cpp
+ internal/cairo-ps-out.cpp
+ internal/cairo-render-context.cpp
+ internal/cairo-renderer.cpp
+ internal/cairo-renderer-pdf-out.cpp
+ internal/emf-inout.cpp
+ internal/emf-print.cpp
+ internal/gdkpixbuf-input.cpp
+ internal/gimpgrad.cpp
+ internal/grid.cpp
+ internal/image-resolution.cpp
+ internal/latex-pstricks.cpp
+ internal/latex-pstricks-out.cpp
+ internal/metafile-inout.cpp
+ internal/metafile-print.cpp
+ internal/odf.cpp
+ internal/latex-text-renderer.cpp
+ internal/png-output.cpp
+ internal/pov-out.cpp
+ internal/svg.cpp
+ internal/svgz.cpp
+ internal/text_reassemble.c
+ internal/wmf-inout.cpp
+ internal/wmf-print.cpp
+
+ internal/filter/filter-all.cpp
+ internal/filter/filter-file.cpp
+ internal/filter/filter.cpp
+
+ internal/pdfinput/pdf-input.cpp
+ internal/pdfinput/pdf-parser.cpp
+ internal/pdfinput/svg-builder.cpp
+
+ prefdialog/prefdialog.cpp
+ prefdialog/parameter.cpp
+ prefdialog/parameter-bool.cpp
+ prefdialog/parameter-color.cpp
+ prefdialog/parameter-float.cpp
+ prefdialog/parameter-int.cpp
+ prefdialog/parameter-notebook.cpp
+ prefdialog/parameter-optiongroup.cpp
+ prefdialog/parameter-path.cpp
+ prefdialog/parameter-string.cpp
+ prefdialog/widget.cpp
+ prefdialog/widget-box.cpp
+ prefdialog/widget-image.cpp
+ prefdialog/widget-label.cpp
+ prefdialog/widget-separator.cpp
+ prefdialog/widget-spacer.cpp
+
+ # ------
+ # Header
+ db.h
+ dependency.h
+ effect.h
+ execution-env.h
+ extension.h
+ init.h
+ input.h
+ output.h
+ patheffect.h
+ print.h
+ system.h
+ timer.h
+ loader.h
+
+ implementation/implementation.h
+ implementation/script.h
+ implementation/xslt.h
+
+ internal/bluredge.h
+ internal/cairo-ps-out.h
+ internal/cairo-render-context.h
+ internal/cairo-renderer-pdf-out.h
+ internal/cairo-renderer.h
+ internal/clear-n_.h
+ internal/emf-inout.h
+ internal/emf-print.h
+ internal/filter/bevels.h
+ internal/filter/blurs.h
+ internal/filter/bumps.h
+ internal/filter/color.h
+ internal/filter/distort.h
+ internal/filter/filter.h
+ internal/filter/image.h
+ internal/filter/morphology.h
+ internal/filter/overlays.h
+ internal/filter/paint.h
+ internal/filter/protrusions.h
+ internal/filter/shadows.h
+ internal/filter/textures.h
+ internal/filter/transparency.h
+ internal/gdkpixbuf-input.h
+ internal/gimpgrad.h
+ internal/grid.h
+ internal/image-resolution.h
+ internal/latex-pstricks-out.h
+ internal/latex-pstricks.h
+ internal/latex-text-renderer.h
+ internal/metafile-inout.h
+ internal/metafile-print.h
+ internal/odf.h
+ internal/pdfinput/pdf-input.h
+ internal/pdfinput/pdf-parser.h
+ internal/pdfinput/svg-builder.h
+ internal/png-output.h
+ internal/pov-out.h
+ internal/svg.h
+ internal/svgz.h
+ internal/text_reassemble.h
+ internal/wmf-inout.h
+ internal/wmf-print.h
+
+ prefdialog/prefdialog.h
+ prefdialog/parameter.h
+ prefdialog/parameter-bool.h
+ prefdialog/parameter-color.h
+ prefdialog/parameter-float.h
+ prefdialog/parameter-int.h
+ prefdialog/parameter-notebook.h
+ prefdialog/parameter-optiongroup.h
+ prefdialog/parameter-path.h
+ prefdialog/parameter-string.h
+ prefdialog/widget.h
+ prefdialog/widget-box.h
+ prefdialog/widget-image.h
+ prefdialog/widget-label.h
+ prefdialog/widget-separator.h
+ prefdialog/widget-spacer.h
+)
+
+if(WIN32)
+ list(APPEND extension_SRC
+ )
+endif()
+
+if(WITH_LIBCDR)
+ list(APPEND extension_SRC
+ internal/cdr-input.cpp
+ internal/cdr-input.h
+ )
+endif()
+
+if(WITH_LIBVISIO)
+ list(APPEND extension_SRC
+ internal/vsd-input.cpp
+ internal/vsd-input.h
+ )
+endif()
+
+if(WITH_LIBWPG)
+ list(APPEND extension_SRC
+ internal/wpg-input.cpp
+ internal/wpg-input.h
+ )
+endif()
+
+if(WITH_MAGICK)
+ list(APPEND extension_SRC
+ internal/bitmap/adaptiveThreshold.cpp
+ internal/bitmap/adaptiveThreshold.h
+ internal/bitmap/addNoise.cpp
+ internal/bitmap/addNoise.h
+ internal/bitmap/blur.cpp
+ internal/bitmap/blur.h
+ internal/bitmap/channel.cpp
+ internal/bitmap/channel.h
+ internal/bitmap/charcoal.cpp
+ internal/bitmap/charcoal.h
+ internal/bitmap/colorize.cpp
+ internal/bitmap/colorize.h
+ internal/bitmap/contrast.cpp
+ internal/bitmap/contrast.h
+ internal/bitmap/crop.cpp
+ internal/bitmap/crop.h
+ internal/bitmap/cycleColormap.cpp
+ internal/bitmap/cycleColormap.h
+ internal/bitmap/despeckle.cpp
+ internal/bitmap/despeckle.h
+ internal/bitmap/edge.cpp
+ internal/bitmap/edge.h
+ internal/bitmap/emboss.cpp
+ internal/bitmap/emboss.h
+ internal/bitmap/enhance.cpp
+ internal/bitmap/enhance.h
+ internal/bitmap/equalize.cpp
+ internal/bitmap/equalize.h
+ internal/bitmap/gaussianBlur.cpp
+ internal/bitmap/gaussianBlur.h
+ internal/bitmap/imagemagick.cpp
+ internal/bitmap/imagemagick.h
+ internal/bitmap/implode.cpp
+ internal/bitmap/implode.h
+ internal/bitmap/level.cpp
+ internal/bitmap/level.h
+ internal/bitmap/levelChannel.cpp
+ internal/bitmap/levelChannel.h
+ internal/bitmap/medianFilter.cpp
+ internal/bitmap/medianFilter.h
+ internal/bitmap/modulate.cpp
+ internal/bitmap/modulate.h
+ internal/bitmap/negate.cpp
+ internal/bitmap/negate.h
+ internal/bitmap/normalize.cpp
+ internal/bitmap/normalize.h
+ internal/bitmap/oilPaint.cpp
+ internal/bitmap/oilPaint.h
+ internal/bitmap/opacity.cpp
+ internal/bitmap/opacity.h
+ internal/bitmap/raise.cpp
+ internal/bitmap/raise.h
+ internal/bitmap/reduceNoise.cpp
+ internal/bitmap/reduceNoise.h
+ internal/bitmap/sample.cpp
+ internal/bitmap/sample.h
+ internal/bitmap/shade.cpp
+ internal/bitmap/shade.h
+ internal/bitmap/sharpen.cpp
+ internal/bitmap/sharpen.h
+ internal/bitmap/solarize.cpp
+ internal/bitmap/solarize.h
+ internal/bitmap/spread.cpp
+ internal/bitmap/spread.h
+ internal/bitmap/swirl.cpp
+ internal/bitmap/swirl.h
+ internal/bitmap/threshold.cpp
+ internal/bitmap/threshold.h
+ internal/bitmap/unsharpmask.cpp
+ internal/bitmap/unsharpmask.h
+ internal/bitmap/wave.cpp
+ internal/bitmap/wave.h
+ )
+endif()
+
+# add_inkscape_lib(extension_LIB "${extension_SRC}")
+add_inkscape_source("${extension_SRC}")
+
+add_subdirectory( plugins )
diff --git a/src/extension/db.cpp b/src/extension/db.cpp
new file mode 100644
index 0000000..1fb8763
--- /dev/null
+++ b/src/extension/db.cpp
@@ -0,0 +1,327 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Functions to keep a listing of all modules in the system. Has its
+ * own file mostly for abstraction reasons, but is pretty simple
+ * otherwise.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "db.h"
+
+#include "implementation/script.h"
+#include "input.h"
+#include "output.h"
+#include "effect.h"
+
+/* Globals */
+
+/* Namespaces */
+
+namespace Inkscape {
+namespace Extension {
+
+/** This is the actual database object. There is only one of these */
+DB db;
+
+/* Types */
+
+DB::DB (void) = default;
+
+
+struct ModuleInputCmp {
+ bool operator()(Input* module1, Input* module2) const {
+
+ // Ensure SVG files are at top
+ int n1 = 0;
+ int n2 = 0;
+ // 12345678901234567890123456789012
+ if (strncmp(module1->get_id(),"org.inkscape.input.svg", 22) == 0) n1 = 1;
+ if (strncmp(module2->get_id(),"org.inkscape.input.svg", 22) == 0) n2 = 1;
+ if (strncmp(module1->get_id(),"org.inkscape.input.svgz", 23) == 0) n1 = 2;
+ if (strncmp(module2->get_id(),"org.inkscape.input.svgz", 23) == 0) n2 = 2;
+
+ if (n1 != 0 && n2 != 0) return (n1 < n2);
+ if (n1 != 0) return true;
+ if (n2 != 0) return false;
+
+ // GDK filetypenames begin with lower case letters and thus are sorted at the end.
+ // Special case "sK1" which starts with a lower case letter to keep it out of GDK region.
+ if (strncmp(module1->get_id(),"org.inkscape.input.sk1", 22) == 0) {
+ return ( strcmp("SK1", module2->get_filetypename()) <= 0 );
+ }
+ if (strncmp(module2->get_id(),"org.inkscape.input.sk1", 22) == 0) {
+ return ( strcmp(module1->get_filetypename(), "SK1" ) <= 0 );
+ }
+
+ return ( strcmp(module1->get_filetypename(), module2->get_filetypename()) <= 0 );
+ }
+};
+
+
+struct ModuleOutputCmp {
+ bool operator()(Output* module1, Output* module2) const {
+
+ // Ensure SVG files are at top
+ int n1 = 0;
+ int n2 = 0;
+ // 12345678901234567890123456789012
+ if (strncmp(module1->get_id(),"org.inkscape.output.png.inkscape", 32) == 0) n1 = 1;
+ if (strncmp(module2->get_id(),"org.inkscape.output.png.inkscape", 32) == 0) n2 = 1;
+ if (strncmp(module1->get_id(),"org.inkscape.output.svg.inkscape", 32) == 0) n1 = 1;
+ if (strncmp(module2->get_id(),"org.inkscape.output.svg.inkscape", 32) == 0) n2 = 1;
+ if (strncmp(module1->get_id(),"org.inkscape.output.svg.plain", 29) == 0) n1 = 2;
+ if (strncmp(module2->get_id(),"org.inkscape.output.svg.plain", 29) == 0) n2 = 2;
+ if (strncmp(module1->get_id(),"org.inkscape.output.svgz.inkscape", 33) == 0) n1 = 3;
+ if (strncmp(module2->get_id(),"org.inkscape.output.svgz.inkscape", 33) == 0) n2 = 3;
+ if (strncmp(module1->get_id(),"org.inkscape.output.svgz.plain", 30) == 0) n1 = 4;
+ if (strncmp(module2->get_id(),"org.inkscape.output.svgz.plain", 30) == 0) n2 = 4;
+ if (strncmp(module1->get_id(),"org.inkscape.output.scour", 25) == 0) n1 = 5;
+ if (strncmp(module2->get_id(),"org.inkscape.output.scour", 25) == 0) n2 = 5;
+ if (strncmp(module1->get_id(),"org.inkscape.output.ZIP", 23) == 0) n1 = 6;
+ if (strncmp(module2->get_id(),"org.inkscape.output.ZIP", 23) == 0) n2 = 6;
+ if (strncmp(module1->get_id(),"org.inkscape.output.LAYERS", 26) == 0) n1 = 7;
+ if (strncmp(module2->get_id(),"org.inkscape.output.LAYERS", 26) == 0) n2 = 7;
+
+ if (n1 != 0 && n2 != 0) return (n1 < n2);
+ if (n1 != 0) return true;
+ if (n2 != 0) return false;
+
+ // Special case "sK1" which starts with a lower case letter.
+ if (strncmp(module1->get_id(),"org.inkscape.output.sk1", 23) == 0) {
+ return ( strcmp("SK1", module2->get_filetypename()) <= 0 );
+ }
+ if (strncmp(module2->get_id(),"org.inkscape.output.sk1", 23) == 0) {
+ return ( strcmp(module1->get_filetypename(), "SK1" ) <= 0 );
+ }
+ // special case: two extensions for the same file type. I only one of them is a script, prefer the other one
+ if (Glib::ustring(module1->get_extension()).lowercase() == Glib::ustring(module2->get_extension()).lowercase()) {
+ bool module1_is_script = dynamic_cast<Inkscape::Extension::Implementation::Script *>(module1->get_imp());
+ bool module2_is_script = dynamic_cast<Inkscape::Extension::Implementation::Script *>(module2->get_imp());
+ if (module1_is_script != module2_is_script) {
+ return module1_is_script ? false : true;
+ }
+ }
+ return ( strcmp(module1->get_filetypename(), module2->get_filetypename()) <= 0 );
+ }
+};
+
+
+/**
+ \brief Add a module to the module database
+ \param module The module to be registered.
+*/
+void
+DB::register_ext (Extension *module)
+{
+ g_return_if_fail(module != nullptr);
+ g_return_if_fail(module->get_id() != nullptr);
+
+ // only add to list if it's a never-before-seen module
+ bool add_to_list =
+ ( moduledict.find(module->get_id()) == moduledict.end());
+
+ //printf("Registering: '%s' '%s' add:%d\n", module->get_id(), module->get_name(), add_to_list);
+ moduledict[module->get_id()] = module;
+
+ if (add_to_list) {
+ modulelist.push_back( module );
+ }
+}
+
+/**
+ \brief This function removes a module from the database
+ \param module The module to be removed.
+*/
+void
+DB::unregister_ext (Extension * module)
+{
+ g_return_if_fail(module != nullptr);
+ g_return_if_fail(module->get_id() != nullptr);
+
+ // printf("Extension DB: removing %s\n", module->get_id());
+ moduledict.erase(moduledict.find(module->get_id()));
+ // only remove if it's not there any more
+ if ( moduledict.find(module->get_id()) != moduledict.end())
+ modulelist.remove(module);
+}
+
+/**
+ \return A reference to the Inkscape::Extension::Extension specified by the input key.
+ \brief This function looks up a Inkscape::Extension::Extension by using its unique
+ id. It then returns a reference to that module.
+ \param key The unique ID of the module
+
+ Retrieves a module by name; if non-NULL, it refs the returned
+ module; the caller is responsible for releasing that reference
+ when it is no longer needed.
+*/
+Extension *
+DB::get (const gchar *key) const
+{
+ if (key == nullptr) return nullptr;
+
+ auto it = moduledict.find(key);
+ if (it == moduledict.end())
+ return nullptr;
+
+ Extension *mod = it->second;
+ assert(mod);
+
+ if ( !mod || mod->deactivated() )
+ return nullptr;
+
+ return mod;
+}
+
+/**
+ \return none
+ \brief A function to execute another function with every entry
+ in the database as a parameter.
+ \param in_func The function to execute for every module
+ \param in_data A data pointer that is also passed to in_func
+
+ Enumerates the modules currently in the database, calling a given
+ callback for each one.
+*/
+void
+DB::foreach (void (*in_func)(Extension * in_plug, gpointer in_data), gpointer in_data)
+{
+ std::list <Extension *>::iterator cur;
+
+ for (cur = modulelist.begin(); cur != modulelist.end(); ++cur) {
+ // printf("foreach: %s\n", (*cur)->get_id());
+ in_func((*cur), in_data);
+ }
+}
+
+/**
+ \return none
+ \brief The function to look at each module and see if it is
+ an input module, then add it to the list.
+ \param in_plug Module to be examined
+ \param data The list to be attached to
+
+ The first thing that is checked is if this module is an input
+ module. If it is, then it is added to the list which is passed
+ in through \c data.
+*/
+void
+DB::input_internal (Extension * in_plug, gpointer data)
+{
+ if (dynamic_cast<Input *>(in_plug)) {
+ InputList * ilist;
+ Input * imod;
+
+ imod = dynamic_cast<Input *>(in_plug);
+ ilist = reinterpret_cast<InputList *>(data);
+
+ ilist->push_back(imod);
+ // printf("Added to input list: %s\n", imod->get_id());
+ }
+}
+
+/**
+ \return none
+ \brief The function to look at each module and see if it is
+ an output module, then add it to the list.
+ \param in_plug Module to be examined
+ \param data The list to be attached to
+
+ The first thing that is checked is if this module is an output
+ module. If it is, then it is added to the list which is passed
+ in through \c data.
+*/
+void
+DB::output_internal (Extension * in_plug, gpointer data)
+{
+ if (dynamic_cast<Output *>(in_plug)) {
+ OutputList * olist;
+ Output * omod;
+
+ omod = dynamic_cast<Output *>(in_plug);
+ olist = reinterpret_cast<OutputList *>(data);
+
+ olist->push_back(omod);
+ // printf("Added to output list: %s\n", omod->get_id());
+ }
+
+ return;
+}
+
+/**
+ \return none
+ \brief The function to look at each module and see if it is
+ an effect module, then add it to the list.
+ \param in_plug Module to be examined
+ \param data The list to be attached to
+
+ The first thing that is checked is if this module is an effect
+ module. If it is, then it is added to the list which is passed
+ in through \c data.
+*/
+void
+DB::effect_internal (Extension * in_plug, gpointer data)
+{
+ if (dynamic_cast<Effect *>(in_plug)) {
+ EffectList * elist;
+ Effect * emod;
+
+ emod = dynamic_cast<Effect *>(in_plug);
+ elist = reinterpret_cast<EffectList *>(data);
+
+ elist->push_back(emod);
+ // printf("Added to effect list: %s\n", emod->get_id());
+ }
+
+ return;
+}
+
+/**
+ \brief Creates a list of all the Input extensions
+ \param ou_list The list that is used to put all the extensions in
+
+ Calls the database \c foreach function with \c input_internal.
+*/
+DB::InputList &
+DB::get_input_list (DB::InputList &ou_list)
+{
+ foreach(input_internal, (gpointer)&ou_list);
+ ou_list.sort( ModuleInputCmp() );
+ return ou_list;
+}
+
+/**
+ \brief Creates a list of all the Output extensions
+ \param ou_list The list that is used to put all the extensions in
+
+ Calls the database \c foreach function with \c output_internal.
+*/
+DB::OutputList &
+DB::get_output_list (DB::OutputList &ou_list)
+{
+ foreach(output_internal, (gpointer)&ou_list);
+ ou_list.sort( ModuleOutputCmp() );
+ return ou_list;
+}
+
+/**
+ \brief Creates a list of all the Effect extensions
+ \param ou_list The list that is used to put all the extensions in
+
+ Calls the database \c foreach function with \c effect_internal.
+*/
+DB::EffectList &
+DB::get_effect_list (DB::EffectList &ou_list)
+{
+ foreach(effect_internal, (gpointer)&ou_list);
+ return ou_list;
+}
+
+} } /* namespace Extension, Inkscape */
diff --git a/src/extension/db.h b/src/extension/db.h
new file mode 100644
index 0000000..d585180
--- /dev/null
+++ b/src/extension/db.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Functions to keep a listing of all modules in the system. Has its
+ * own file mostly for abstraction reasons, but is pretty simple
+ * otherwise.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_MODULES_DB_H
+#define SEEN_MODULES_DB_H
+
+#include <map>
+#include <list>
+#include <cstring>
+
+#include <glib.h>
+
+
+namespace Inkscape {
+namespace Extension {
+
+class Input;
+class Output;
+class Effect;
+class Extension;
+
+class DB {
+private:
+ /** A string comparison function to be used in the moduledict
+ to find the different extensions in the hash map. */
+ struct ltstr {
+ bool operator()(const char* s1, const char* s2) const {
+ if ( (s1 == nullptr) && (s2 != nullptr) ) {
+ return true;
+ } else if (s1 == nullptr || s2 == nullptr) {
+ return false;
+ } else {
+ return strcmp(s1, s2) < 0;
+ }
+ }
+ };
+ /** This is the actual database. It has all of the modules in it,
+ indexed by their ids. It's a hash table for faster lookups */
+ std::map <const char *, Extension *, ltstr> moduledict;
+ /** Maintain an ordered list of modules for generating the extension
+ lists via "foreach" */
+ std::list <Extension *> modulelist;
+
+ static void foreach_internal (gpointer in_key, gpointer in_value, gpointer in_data);
+
+public:
+ DB ();
+ Extension * get (const gchar *key) const;
+ void register_ext (Extension *module);
+ void unregister_ext (Extension *module);
+ void foreach (void (*in_func)(Extension * in_plug, gpointer in_data), gpointer in_data);
+
+private:
+ static void input_internal (Extension * in_plug, gpointer data);
+ static void output_internal (Extension * in_plug, gpointer data);
+ static void effect_internal (Extension * in_plug, gpointer data);
+
+public:
+ typedef std::list<Output *> OutputList;
+ typedef std::list<Input *> InputList;
+ typedef std::list<Effect *> EffectList;
+
+ InputList &get_input_list (InputList &ou_list);
+ OutputList &get_output_list (OutputList &ou_list);
+ EffectList &get_effect_list (EffectList &ou_list);
+}; /* class DB */
+
+extern DB db;
+
+} } /* namespace Extension, Inkscape */
+
+#endif // SEEN_MODULES_DB_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/dependency.cpp b/src/extension/dependency.cpp
new file mode 100644
index 0000000..09928c6
--- /dev/null
+++ b/src/extension/dependency.cpp
@@ -0,0 +1,362 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Author:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006 Johan Engelen, johan@shouraizou.nl
+ * Copyright (C) 2004 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+#include "dependency.h"
+#include "db.h"
+#include "extension.h"
+#include "io/resource.h"
+
+namespace Inkscape {
+namespace Extension {
+
+// These strings are for XML attribute comparisons and should not be translated;
+// make sure to keep in sync with enum defined in dependency.h
+gchar const * Dependency::_type_str[] = {
+ "executable",
+ "file",
+ "extension",
+};
+
+// These strings are for XML attribute comparisons and should not be translated
+// make sure to keep in sync with enum defined in dependency.h
+gchar const * Dependency::_location_str[] = {
+ "path",
+ "extensions",
+ "inx",
+ "absolute",
+};
+
+/**
+ \brief Create a dependency using an XML definition
+ \param in_repr XML definition of the dependency
+ \param extension Reference to the extension requesting this dependency
+ \param default_type Default file type of the dependency (unless overridden by XML definition's "type" attribute)
+
+ This function mostly looks for the 'location' and 'type' attributes
+ and turns them into the enums of the same name. This makes things
+ a little bit easier to use later. Also, a pointer to the core
+ content is pulled out -- also to make things easier.
+*/
+Dependency::Dependency (Inkscape::XML::Node * in_repr, const Extension *extension, type_t default_type)
+ : _repr(in_repr)
+ , _extension(extension)
+ , _type(default_type)
+{
+ Inkscape::GC::anchor(_repr);
+
+ if (const gchar * location = _repr->attribute("location")) {
+ for (int i = 0; i < LOCATION_CNT && location != nullptr; i++) {
+ if (!strcmp(location, _location_str[i])) {
+ _location = (location_t)i;
+ break;
+ }
+ }
+ } else if (const gchar * location = _repr->attribute("reldir")) { // backwards-compatibility
+ for (int i = 0; i < LOCATION_CNT && location != nullptr; i++) {
+ if (!strcmp(location, _location_str[i])) {
+ _location = (location_t)i;
+ break;
+ }
+ }
+ }
+
+ const gchar * type = _repr->attribute("type");
+ for (int i = 0; i < TYPE_CNT && type != nullptr; i++) {
+ if (!strcmp(type, _type_str[i])) {
+ _type = (type_t)i;
+ break;
+ }
+ }
+
+ _string = _repr->firstChild()->content();
+
+ _description = _repr->attribute("description");
+ if (_description == nullptr)
+ _description = _repr->attribute("_description");
+
+ return;
+}
+
+/**
+ \brief This dependency is not longer needed
+
+ Unreference the XML structure.
+*/
+Dependency::~Dependency ()
+{
+ Inkscape::GC::release(_repr);
+}
+
+/**
+ \brief Check if the dependency passes.
+ \return Whether or not the dependency passes.
+
+ This function depends largely on all of the enums. The first level
+ that is evaluated is the \c _type.
+
+ If the type is \c TYPE_EXTENSION then the id for the extension is
+ looked up in the database. If the extension is found, and it is
+ not deactivated, the dependency passes.
+
+ If the type is \c TYPE_EXECUTABLE or \c TYPE_FILE things are getting
+ even more interesting because now the \c _location variable is also
+ taken into account. First, the difference between the two is that
+ the file test for \c TYPE_EXECUTABLE also tests to make sure the
+ file is executable, besides checking that it exists.
+
+ If the \c _location is \c LOCATION_EXTENSIONS then the \c INKSCAPE_EXTENSIONDIR
+ is put on the front of the string with \c build_filename. Then the
+ appropriate filetest is run.
+
+ If the \c _location is \c LOCATION_ABSOLUTE then the file test is
+ run directly on the string.
+
+ If the \c _location is \c LOCATION_PATH or not specified then the
+ path is used to find the file. Each entry in the path is stepped
+ through, attached to the string, and then tested. If the file is
+ found then a TRUE is returned. If we get all the way through the
+ path then a FALSE is returned, the command could not be found.
+*/
+bool Dependency::check ()
+{
+ if (_string == nullptr) {
+ return false;
+ }
+
+ _absolute_location = "";
+
+ switch (_type) {
+ case TYPE_EXTENSION: {
+ Extension * myext = db.get(_string);
+ if (myext == nullptr) return false;
+ if (myext->deactivated()) return false;
+ break;
+ }
+ case TYPE_EXECUTABLE:
+ case TYPE_FILE: {
+ Glib::FileTest filetest = Glib::FILE_TEST_EXISTS;
+
+ std::string location(_string);
+
+ // get potential file extension for later usage
+ std::string extension;
+ size_t index = location.find_last_of(".");
+ if (index != std::string::npos) {
+ extension = location.substr(index);
+ }
+
+ // check interpreted scripts as "file" for backwards-compatibility, even if "executable" was requested
+ static const std::vector<std::string> interpreted = {".py", ".pl", ".rb"};
+ if (!extension.empty() &&
+ std::find(interpreted.begin(), interpreted.end(), extension) != interpreted.end())
+ {
+ _type = TYPE_FILE;
+ }
+
+#ifndef _WIN32
+ // There's no executable bit on Windows, so this is unreliable
+ // glib would search for "executable types" instead, which are only {".exe", ".cmd", ".bat", ".com"},
+ // and would therefore miss files without extension and other script files (like .py files)
+ if (_type == TYPE_EXECUTABLE) {
+ filetest = Glib::FILE_TEST_IS_EXECUTABLE;
+ }
+#endif
+
+ switch (_location) {
+ // backwards-compatibility: location="extensions" will be deprecated as of Inkscape 1.1,
+ // use location="inx" instead
+ case LOCATION_EXTENSIONS: {
+ // get_filename will warn if the resource isn't found, while returning an empty string.
+ std::string temploc =
+ Inkscape::IO::Resource::get_filename_string(Inkscape::IO::Resource::EXTENSIONS, location.c_str());
+ if (!temploc.empty()) {
+ location = temploc;
+ _absolute_location = temploc;
+ break;
+ }
+ /* Look for deprecated locations next */
+ auto deprloc = g_build_filename("inkex", "deprecated-simple", location.c_str(), nullptr);
+ std::string tempdepr =
+ Inkscape::IO::Resource::get_filename_string(Inkscape::IO::Resource::EXTENSIONS, deprloc, false, true);
+ g_free(deprloc);
+ if (!tempdepr.empty()) {
+ location = tempdepr;
+ _absolute_location = tempdepr;
+ break;
+ }
+ // PASS THROUGH!!! - also check inx location for backwards-compatibility,
+ // notably to make extension manager work
+ // (installs into subfolders of "extensions" directory)
+ }
+ case LOCATION_INX: {
+ std::string base_directory = _extension->get_base_directory();
+ if (base_directory.empty()) {
+ g_warning("Dependency '%s' requests location relative to .inx file, "
+ "which is unknown for extension '%s'", _string, _extension->get_id());
+ }
+ std::string absolute_location = Glib::build_filename(base_directory, location);
+ if (!Glib::file_test(absolute_location, filetest)) {
+ return false;
+ }
+ _absolute_location = absolute_location;
+ break;
+ }
+ case LOCATION_ABSOLUTE: {
+ // TODO: should we check if the directory actually is absolute and/or sanitize the filename somehow?
+ if (!Glib::file_test(location, filetest)) {
+ return false;
+ }
+ _absolute_location = location;
+ break;
+ }
+ /* The default case is to look in the path */
+ case LOCATION_PATH:
+ default: {
+ // TODO: we can likely use g_find_program_in_path (or its glibmm equivalent) for executable types
+
+ gchar * path = g_strdup(g_getenv("PATH"));
+
+ if (path == nullptr) {
+ /* There is no `PATH' in the environment.
+ The default search path is the current directory */
+ path = g_strdup(G_SEARCHPATH_SEPARATOR_S);
+ }
+
+ gchar * orig_path = path;
+
+ for (; path != nullptr;) {
+ gchar * local_path; // to have the path after detection of the separator
+ std::string final_name;
+
+ local_path = path;
+ path = g_utf8_strchr(path, -1, G_SEARCHPATH_SEPARATOR);
+ /* Not sure whether this is UTF8 happy, but it would seem
+ like it considering that I'm searching (and finding)
+ the ':' character */
+ if (path != nullptr) {
+ path[0] = '\0';
+ path++;
+ }
+
+ if (*local_path == '\0') {
+ final_name = _string;
+ } else {
+ final_name = Glib::build_filename(local_path, _string);
+ }
+
+ if (Glib::file_test(final_name, filetest)) {
+ g_free(orig_path);
+ _absolute_location = final_name;
+ return true;
+ }
+
+#ifdef _WIN32
+ // Unfortunately file extensions tend to be different on Windows and we can't know
+ // which one it is, so try all extensions glib assumes to be executable.
+ // As we can only guess here, return the version without extension if either one is found,
+ // so that we don't accidentally override (or conflict with) some g_spawn_* magic.
+ if (_type == TYPE_EXECUTABLE) {
+ static const std::vector<std::string> extensions = {".exe", ".cmd", ".bat", ".com"};
+ if (extension.empty() ||
+ std::find(extensions.begin(), extensions.end(), extension) == extensions.end())
+ {
+ for (auto extension : extensions) {
+ if (Glib::file_test(final_name + extension, filetest)) {
+ g_free(orig_path);
+ _absolute_location = final_name;
+ return true;
+ }
+ }
+ }
+ }
+#endif
+ }
+
+ g_free(orig_path);
+ return false; /* Reverse logic in this one */
+ }
+ } /* switch _location */
+ break;
+ } /* TYPE_FILE, TYPE_EXECUTABLE */
+ default:
+ return false;
+ } /* switch _type */
+
+ return true;
+}
+
+/**
+ \brief Accessor to the name attribute.
+ \return A string containing the name of the dependency.
+
+ Returns the name of the dependency as found in the configuration file.
+
+*/
+const gchar* Dependency::get_name()
+{
+ return _string;
+}
+
+/**
+ \brief Path of this dependency
+ \return Absolute path to the dependency file
+ (or an empty string if dependency was not found or is of TYPE_EXTENSION)
+
+ Returns the verified absolute path of the dependency file.
+ This value is only available after checking the Dependency by calling Dependency::check().
+*/
+std::string Dependency::get_path()
+{
+ if (_type == TYPE_EXTENSION) {
+ g_warning("Requested absolute path of dependency '%s' which is of 'extension' type.", _string);
+ return "";
+ }
+ if (_absolute_location == UNCHECKED) {
+ g_warning("Requested absolute path of dependency '%s' which is unchecked.", _string);
+ return "";
+ }
+
+ return _absolute_location;
+}
+
+/**
+ \brief Print out a dependency to a string.
+*/
+Glib::ustring Dependency::info_string()
+{
+ Glib::ustring str = Glib::ustring::compose("%1:\n\t%2: %3\n\t%4: %5\n\t%6: %7",
+ _("Dependency"),
+ _("type"), _(_type_str[_type]),
+ _("location"), _(_location_str[_location]),
+ _("string"), _string);
+
+ if (_description) {
+ str += Glib::ustring::compose("\n\t%1: %2\n", _(" description: "), _(_description));
+ }
+
+ return str;
+}
+
+} } /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/dependency.h b/src/extension/dependency.h
new file mode 100644
index 0000000..e2c2791
--- /dev/null
+++ b/src/extension/dependency.h
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_DEPENDENCY_H__
+#define INKSCAPE_EXTENSION_DEPENDENCY_H__
+
+#include <glibmm/ustring.h>
+#include "xml/repr.h"
+
+namespace Inkscape {
+namespace Extension {
+
+class Extension;
+
+/** \brief A class to represent a dependency for an extension. There
+ are different things that can be done in a dependency, and
+ this class takes care of all of them. */
+class Dependency {
+
+public:
+ /** \brief All the possible types of dependencies. */
+ enum type_t {
+ TYPE_EXECUTABLE, /**< Look for an executable */
+ TYPE_FILE, /**< Look to make sure a file exists */
+ TYPE_EXTENSION, /**< Make sure a specific extension is loaded and functional */
+ TYPE_CNT /**< Number of types */
+ };
+
+ /** \brief All of the possible locations to look for the dependency. */
+ enum location_t {
+ LOCATION_PATH, /**< Look in the PATH for this dependency - historically this is the default
+ (it's a bit odd for interpreted script files but makes sense for other executables) */
+ LOCATION_EXTENSIONS, /**< Look in the extensions directory
+ (note: this can be in both, user and system locations!) */
+ LOCATION_INX, /**< Look relative to the inx file's location */
+ LOCATION_ABSOLUTE, /**< This dependency is already defined in absolute terms */
+ LOCATION_CNT /**< Number of locations to look */
+ };
+
+private:
+ static constexpr const char *UNCHECKED = "---unchecked---";
+
+ /** \brief The XML representation of the dependency. */
+ Inkscape::XML::Node * _repr;
+ /** \brief The string that is in the XML tags pulled out. */
+ const gchar * _string = nullptr;
+ /** \brief The description of the dependency for the users. */
+ const gchar * _description = nullptr;
+ /** \brief The absolute path to the dependency file determined while checking this dependency. */
+ std::string _absolute_location = UNCHECKED;
+
+ /** \brief Storing the type of this particular dependency. */
+ type_t _type = TYPE_FILE;
+ /** \brief The location to look for this particular dependency. */
+ location_t _location = LOCATION_PATH;
+
+ /** \brief Strings to represent the different enum values in
+ \c type_t in the XML */
+ static gchar const * _type_str[TYPE_CNT];
+ /** \brief Strings to represent the different enum values in
+ \c location_t in the XML */
+ static gchar const * _location_str[LOCATION_CNT];
+
+ /** \brief Reference to the extension requesting this dependency. */
+ const Extension *_extension;
+
+public:
+ Dependency (Inkscape::XML::Node *in_repr, const Extension *extension, type_t type=TYPE_FILE);
+ virtual ~Dependency ();
+ bool check();
+ const gchar* get_name();
+ std::string get_path();
+
+ Glib::ustring info_string();
+}; /* class Dependency */
+
+
+} } /* namespace Extension, Inkscape */
+
+#endif /* INKSCAPE_EXTENSION_DEPENDENCY_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/effect.cpp b/src/extension/effect.cpp
new file mode 100644
index 0000000..5d78381
--- /dev/null
+++ b/src/extension/effect.cpp
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Abhishek Sharma
+ * Sushant A.A. <sushant.co19@gmail.com>
+ *
+ * Copyright (C) 2002-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "effect.h"
+
+#include "execution-env.h"
+#include "inkscape.h"
+#include "timer.h"
+
+#include "implementation/implementation.h"
+#include "prefdialog/prefdialog.h"
+#include "ui/view/view.h"
+#include "inkscape-application.h"
+
+
+/* Inkscape::Extension::Effect */
+
+namespace Inkscape {
+namespace Extension {
+
+Effect * Effect::_last_effect = nullptr;
+
+/**
+ * Adds effect to Gio::Actions
+ *
+ * \c effect is Filter or Extension
+ * \c show_prefs is used to show preferences dialog
+*/
+void
+action_effect (Effect* effect, bool show_prefs)
+{
+ if (effect->_workingDialog && show_prefs) {
+ effect->prefs(InkscapeApplication::instance()->get_active_view());
+ } else {
+ effect->effect(InkscapeApplication::instance()->get_active_view());
+ }
+}
+
+// Modifying string to get submenu id
+std::string
+action_menu_name (std::string menu)
+{
+ transform(menu.begin(), menu.end(), menu.begin(), ::tolower);
+ for (auto &x:menu) {
+ if (x==' ') {
+ x = '-';
+ }
+ }
+ return menu;
+}
+
+Effect::Effect (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory)
+ : Extension(in_repr, in_imp, base_directory)
+ , _menu_node(nullptr), _workingDialog(true)
+ , _prefDialog(nullptr)
+{
+ Inkscape::XML::Node * local_effects_menu = nullptr;
+
+ // can't use document level because it is not defined
+ static auto app = InkscapeApplication::instance();
+
+ if (!app) {
+ // This happens during tests.
+ // std::cerr << "Effect::Effect:: no app!" << std::endl;
+ return;
+ }
+
+ if (!Inkscape::Application::exists()) {
+ return;
+ }
+
+ // This is a weird hack
+ if (!strcmp(this->get_id(), "org.inkscape.filter.dropshadow"))
+ return;
+
+ bool hidden = false;
+
+ no_doc = false;
+ no_live_preview = false;
+
+ // Setting initial value of description to name of action incase if there is no description
+ Glib::ustring description = get_name();
+
+ if (repr != nullptr) {
+
+ for (Inkscape::XML::Node *child = repr->firstChild(); child != nullptr; child = child->next()) {
+ if (!strcmp(child->name(), INKSCAPE_EXTENSION_NS "effect")) {
+ if (child->attribute("needs-document") && !strcmp(child->attribute("needs-document"), "false")) {
+ no_doc = true;
+ }
+ if (child->attribute("needs-live-preview") && !strcmp(child->attribute("needs-live-preview"), "false")) {
+ no_live_preview = true;
+ }
+ if (child->attribute("implements-custom-gui") && !strcmp(child->attribute("implements-custom-gui"), "true")) {
+ _workingDialog = false;
+ }
+ for (Inkscape::XML::Node *effect_child = child->firstChild(); effect_child != nullptr; effect_child = effect_child->next()) {
+ if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "effects-menu")) {
+ // printf("Found local effects menu in %s\n", this->get_name());
+ local_effects_menu = effect_child->firstChild();
+ if (effect_child->attribute("hidden") && !strcmp(effect_child->attribute("hidden"), "true")) {
+ hidden = true;
+ }
+ }
+ if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "menu-tip") ||
+ !strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "_menu-tip")) {
+ // printf("Found local effects menu in %s\n", this->get_name());
+ description = effect_child->firstChild()->content();
+ }
+ } // children of "effect"
+ break; // there can only be one effect
+ } // find "effect"
+ } // children of "inkscape-extension"
+ } // if we have an XML file
+
+ // Replace any dashes with underscores
+ std::string aid = std::string(get_id());
+ std::replace(aid.begin(), aid.end(), '_', '-');
+ std::string action_id = "app." + aid;
+
+ static auto gapp = InkscapeApplication::instance()->gtk_app();
+ if (gapp) {
+ // Might be in command line mode without GUI (testing).
+ gapp->add_action( aid, sigc::bind<Effect*>(sigc::ptr_fun(&action_effect), this, true));
+ gapp->add_action( aid + ".noprefs", sigc::bind<Effect*>(sigc::ptr_fun(&action_effect), this, false));
+ }
+
+ if (!hidden) {
+ // Submenu retrieval as a list of strings (to handle nested menus).
+ std::list<Glib::ustring> sub_menu_list;
+ get_menu(local_effects_menu, sub_menu_list);
+
+ if (local_effects_menu && local_effects_menu->attribute("name") && !strcmp(local_effects_menu->attribute("name"), ("Filters"))) {
+
+ std::vector<std::vector<Glib::ustring>>raw_data_filter =
+ {{ action_id, get_name(), "Filter", description },
+ { action_id + ".noprefs", Glib::ustring(get_name()) + " " + _("(No preferences)"), "Filter", description }};
+ app->get_action_extra_data().add_data(raw_data_filter);
+
+ } else {
+
+ std::vector<std::vector<Glib::ustring>>raw_data_effect =
+ {{ action_id, get_name(), "Effect", description },
+ { action_id + ".noprefs", Glib::ustring(get_name()) + " " + _("(No preferences)"), "Effect", description }};
+ app->get_action_extra_data().add_data(raw_data_effect);
+
+ sub_menu_list.push_front("Effects");
+ }
+
+ // std::cout << " Effect: name: " << get_name();
+ // std::cout << " id: " << aid.c_str();
+ // std::cout << " menu: ";
+ // for (auto sub_menu : sub_menu_list) {
+ // std::cout << "|" << sub_menu.raw(); // Must use raw() as somebody has messed up encoding.
+ // }
+ // std::cout << "|" << std::endl;
+
+ // Add submenu to effect data
+ gchar *ellipsized_name = widget_visible_count() ? g_strdup_printf(_("%s..."), get_name()) : nullptr;
+ Glib::ustring menu_name = ellipsized_name ? ellipsized_name : get_name();
+ app->get_action_effect_data().add_data(aid, sub_menu_list, menu_name);
+ g_free(ellipsized_name);
+ }
+}
+
+void
+Effect::get_menu (Inkscape::XML::Node * pattern, std::list<Glib::ustring>& sub_menu_list)
+{
+ if (!pattern) {
+ return;
+ }
+
+ Glib::ustring merge_name;
+
+ gchar const *menu_name = pattern->attribute("name");
+ if (!menu_name) {
+ menu_name = pattern->attribute("_name");
+ }
+ if (!menu_name) {
+ return;
+ }
+
+ if (_translation_enabled) {
+ merge_name = get_translation(menu_name);
+ } else {
+ merge_name = _(menu_name);
+ }
+
+ // Making sub menu string
+ sub_menu_list.push_back(merge_name);
+
+ get_menu(pattern->firstChild(), sub_menu_list);
+}
+
+Effect::~Effect ()
+{
+ if (get_last_effect() == this)
+ set_last_effect(nullptr);
+ if (_menu_node) {
+ if (_menu_node->parent()) {
+ _menu_node->parent()->removeChild(_menu_node);
+ }
+ Inkscape::GC::release(_menu_node);
+ }
+ return;
+}
+
+bool
+Effect::prefs (Inkscape::UI::View::View * doc)
+{
+ if (_prefDialog != nullptr) {
+ _prefDialog->raise();
+ return true;
+ }
+
+ if (widget_visible_count() == 0) {
+ effect(doc);
+ return true;
+ }
+
+ if (!loaded())
+ set_state(Extension::STATE_LOADED);
+ if (!loaded()) return false;
+
+ Glib::ustring name = this->get_name();
+ _prefDialog = new PrefDialog(name, nullptr, this);
+ _prefDialog->show();
+
+ return true;
+}
+
+/**
+ \brief The function that 'does' the effect itself
+ \param doc The Inkscape::UI::View::View to do the effect on
+
+ This function first insures that the extension is loaded, and if not,
+ loads it. It then calls the implementation to do the actual work. It
+ also resets the last effect pointer to be this effect. Finally, it
+ executes a \c SPDocumentUndo::done to commit the changes to the undo
+ stack.
+*/
+void
+Effect::effect (Inkscape::UI::View::View * doc)
+{
+ //printf("Execute effect\n");
+ if (!loaded())
+ set_state(Extension::STATE_LOADED);
+ if (!loaded()) return;
+ ExecutionEnv executionEnv(this, doc, nullptr, _workingDialog, true);
+ execution_env = &executionEnv;
+ timer->lock();
+ executionEnv.run();
+ if (executionEnv.wait()) {
+ executionEnv.commit();
+ } else {
+ executionEnv.cancel();
+ }
+ timer->unlock();
+
+ return;
+}
+
+/** \brief Sets which effect was called last
+ \param in_effect The effect that has been called
+
+ This function sets the static variable \c _last_effect
+
+ If the \c in_effect variable is \c NULL then the last effect
+ verb is made insensitive.
+*/
+void
+Effect::set_last_effect (Effect * in_effect)
+{
+ _last_effect = in_effect;
+ return;
+}
+
+Inkscape::XML::Node *
+Effect::find_menu (Inkscape::XML::Node * menustruct, const gchar *name)
+{
+ if (menustruct == nullptr) return nullptr;
+ for (Inkscape::XML::Node * child = menustruct;
+ child != nullptr;
+ child = child->next()) {
+ if (!strcmp(child->name(), name)) {
+ return child;
+ }
+ Inkscape::XML::Node * firstchild = child->firstChild();
+ if (firstchild != nullptr) {
+ Inkscape::XML::Node *found = find_menu (firstchild, name);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ return nullptr;
+}
+
+
+Gtk::Box *
+Effect::get_info_widget()
+{
+ return Extension::get_info_widget();
+}
+
+PrefDialog *
+Effect::get_pref_dialog ()
+{
+ return _prefDialog;
+}
+
+void
+Effect::set_pref_dialog (PrefDialog * prefdialog)
+{
+ _prefDialog = prefdialog;
+ return;
+}
+
+} } /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/effect.h b/src/extension/effect.h
new file mode 100644
index 0000000..b5e5b1f
--- /dev/null
+++ b/src/extension/effect.h
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#ifndef INKSCAPE_EXTENSION_EFFECT_H__
+#define INKSCAPE_EXTENSION_EFFECT_H__
+
+#include <list>
+
+#include <glibmm/i18n.h>
+#include "extension.h"
+#include "inkscape-application.h"
+
+namespace Gtk {
+ class Box;
+}
+
+class SPDocument;
+
+namespace Inkscape {
+
+
+namespace Extension {
+class PrefDialog;
+
+/** \brief Effects are extensions that take a document and do something
+ to it in place. This class adds the extra functions required
+ to make extensions effects.
+*/
+class Effect : public Extension {
+ /** \brief This is the last effect that was used. This is used in
+ a menu item to rapidly recall the same effect. */
+ static Effect * _last_effect;
+
+ Inkscape::XML::Node *find_menu (Inkscape::XML::Node * menustruct, const gchar *name);
+ void get_menu (Inkscape::XML::Node * pattern, std::list<Glib::ustring>& sub_menu_list);
+
+ /** \brief Menu node created for this effect */
+ Inkscape::XML::Node * _menu_node;
+
+ /** \brief The preference dialog if it is shown */
+ PrefDialog * _prefDialog;
+
+public:
+ Effect(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory);
+ ~Effect () override;
+
+ bool prefs (Inkscape::UI::View::View * doc);
+ void effect (Inkscape::UI::View::View * doc);
+
+ /** \brief Whether a working dialog should be shown */
+ bool _workingDialog;
+
+ /** \brief Static function to get the last effect used */
+ static Effect * get_last_effect () { return _last_effect; };
+ static void set_last_effect (Effect * in_effect);
+
+ static void place_menus ();
+ void place_menu (Inkscape::XML::Node * menus);
+
+ Gtk::Box * get_info_widget();
+
+ bool no_doc; // if true, the effect does not process SVG document at all, so no need to save, read, and watch for errors
+ bool no_live_preview; // if true, the effect does not need "live preview" checkbox in its dialog
+
+ PrefDialog *get_pref_dialog ();
+ void set_pref_dialog (PrefDialog * prefdialog);
+private:
+ static gchar * remove_ (gchar * instr);
+};
+
+} } /* namespace Inkscape, Extension */
+#endif /* INKSCAPE_EXTENSION_EFFECT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/execution-env.cpp b/src/extension/execution-env.cpp
new file mode 100644
index 0000000..9d5cd3a
--- /dev/null
+++ b/src/extension/execution-env.cpp
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/dialog.h>
+#include <gtkmm/messagedialog.h>
+
+#include "execution-env.h"
+#include "prefdialog/prefdialog.h"
+#include "implementation/implementation.h"
+
+#include "selection.h"
+#include "effect.h"
+#include "document.h"
+#include "desktop.h"
+#include "inkscape.h"
+#include "document-undo.h"
+#include "desktop.h"
+#include "object/sp-namedview.h"
+
+#include "ui/widget/canvas.h" // To get window (perverse!)
+
+namespace Inkscape {
+namespace Extension {
+
+/** \brief Create an execution environment that will allow the effect
+ to execute independently.
+ \param effect The effect that we should execute
+ \param doc The Document to execute on
+ \param docCache The cache created for that document
+ \param show_working Show the working dialog
+ \param show_error Show the error dialog (not working)
+
+ Grabs the selection of the current document so that it can get
+ restored. Will generate a document cache if one isn't provided.
+*/
+ExecutionEnv::ExecutionEnv (Effect * effect, Inkscape::UI::View::View * doc, Implementation::ImplementationDocumentCache * docCache, bool show_working, bool show_errors) :
+ _state(ExecutionEnv::INIT),
+ _visibleDialog(nullptr),
+ _mainloop(nullptr),
+ _doc(doc),
+ _docCache(docCache),
+ _effect(effect),
+ _show_working(show_working)
+{
+ SPDesktop *desktop = (SPDesktop *)_doc;
+ SPDocument *document = _doc->doc();
+ if (document && desktop) {
+ // Temporarily prevent undo in this scope
+ Inkscape::DocumentUndo::ScopedInsensitive pauseUndo(document);
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (selection) {
+ // Make sure all selected objects have an ID attribute
+ selection->enforceIds();
+ }
+ }
+
+ genDocCache();
+
+ return;
+}
+
+/** \brief Destroy an execution environment
+
+ Destroys the dialog if created and the document cache.
+*/
+ExecutionEnv::~ExecutionEnv () {
+ if (_visibleDialog != nullptr) {
+ _visibleDialog->hide();
+ delete _visibleDialog;
+ _visibleDialog = nullptr;
+ }
+ killDocCache();
+ return;
+}
+
+/** \brief Generate a document cache if needed
+
+ If there isn't one we create a new one from the implementation
+ from the effect's implementation.
+*/
+void
+ExecutionEnv::genDocCache () {
+ if (_docCache == nullptr) {
+ // printf("Gen Doc Cache\n");
+ _docCache = _effect->get_imp()->newDocCache(_effect, _doc);
+ }
+ return;
+}
+
+/** \brief Destroy a document cache
+
+ Just delete it.
+*/
+void
+ExecutionEnv::killDocCache () {
+ if (_docCache != nullptr) {
+ // printf("Killed Doc Cache\n");
+ delete _docCache;
+ _docCache = nullptr;
+ }
+ return;
+}
+
+/** \brief Create the working dialog
+
+ Builds the dialog with a message saying that the effect is working.
+ And make sure to connect to the cancel.
+*/
+void
+ExecutionEnv::createWorkingDialog () {
+ if (_visibleDialog != nullptr) {
+ _visibleDialog->hide();
+ delete _visibleDialog;
+ _visibleDialog = nullptr;
+ }
+
+ SPDesktop *desktop = (SPDesktop *)_doc;
+ Gtk::Widget *toplevel = desktop->getCanvas()->get_toplevel();
+ Gtk::Window *window = dynamic_cast<Gtk::Window *>(toplevel);
+ if (!window) {
+ return;
+ }
+
+ gchar * dlgmessage = g_strdup_printf(_("'%s' working, please wait..."), _effect->get_name());
+ _visibleDialog = new Gtk::MessageDialog(*window,
+ dlgmessage,
+ false, // use markup
+ Gtk::MESSAGE_INFO,
+ Gtk::BUTTONS_CANCEL,
+ true); // modal
+ _visibleDialog->signal_response().connect(sigc::mem_fun(this, &ExecutionEnv::workingCanceled));
+ g_free(dlgmessage);
+
+ Gtk::Dialog *dlg = _effect->get_pref_dialog();
+ if (dlg) {
+ _visibleDialog->set_transient_for(*dlg);
+ } else {
+ // ToDo: Do we need to make the window transient for the main window here?
+ // Currently imossible to test because of GUI freezing during save,
+ // see https://bugs.launchpad.net/inkscape/+bug/967416
+ }
+ _visibleDialog->show_now();
+
+ return;
+}
+
+void
+ExecutionEnv::workingCanceled( const int /*resp*/) {
+ cancel();
+ undo();
+ return;
+}
+
+void
+ExecutionEnv::cancel () {
+ SPDesktop *desktop = (SPDesktop *)_doc;
+ desktop->clearWaitingCursor();
+ _effect->get_imp()->cancelProcessing();
+ return;
+}
+
+void
+ExecutionEnv::undo () {
+ DocumentUndo::cancel(_doc->doc());
+ return;
+}
+
+void
+ExecutionEnv::commit () {
+ DocumentUndo::done(_doc->doc(), _effect->get_name(), "");
+ Effect::set_last_effect(_effect);
+ _effect->get_imp()->commitDocument();
+ killDocCache();
+ return;
+}
+
+void
+ExecutionEnv::reselect () {
+ SPDesktop *desktop = SP_ACTIVE_DESKTOP;
+ if(desktop) {
+ Inkscape::Selection *selection = desktop->getSelection();
+ if (selection) {
+ selection->restoreBackup();
+ }
+ }
+ return;
+}
+
+void
+ExecutionEnv::run () {
+ _state = ExecutionEnv::RUNNING;
+ if (_show_working) {
+ createWorkingDialog();
+ }
+ SPDesktop *desktop = (SPDesktop *)_doc;
+ Inkscape::Selection *selection = desktop->getSelection();
+ selection->setBackup();
+ desktop->setWaitingCursor();
+ _effect->get_imp()->effect(_effect, _doc, _docCache);
+ desktop->clearWaitingCursor();
+ _state = ExecutionEnv::COMPLETE;
+ selection->restoreBackup();
+ // _runComplete.signal();
+ return;
+}
+
+void
+ExecutionEnv::runComplete () {
+ _mainloop->quit();
+}
+
+bool
+ExecutionEnv::wait () {
+ if (_state != ExecutionEnv::COMPLETE) {
+ if (_mainloop) {
+ _mainloop = Glib::MainLoop::create(false);
+ }
+
+ sigc::connection conn = _runComplete.connect(sigc::mem_fun(this, &ExecutionEnv::runComplete));
+ _mainloop->run();
+
+ conn.disconnect();
+ }
+
+ return true;
+}
+
+
+
+} } /* namespace Inkscape, Extension */
+
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/execution-env.h b/src/extension/execution-env.h
new file mode 100644
index 0000000..d33bdc4
--- /dev/null
+++ b/src/extension/execution-env.h
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_EXECUTION_ENV_H__
+#define INKSCAPE_EXTENSION_EXECUTION_ENV_H__
+
+#include <glibmm/main.h>
+#include <glibmm/ustring.h>
+
+#include <gtkmm/dialog.h>
+
+namespace Inkscape {
+
+namespace UI {
+namespace View {
+class View;
+} // namespace View
+} // namespace UI
+
+namespace Extension {
+
+class Effect;
+
+namespace Implementation
+{
+class ImplementationDocumentCache;
+}
+
+class ExecutionEnv {
+private:
+ enum state_t {
+ INIT, //< The context has been initialized
+ COMPLETE, //< We've completed atleast once
+ RUNNING //< The effect is currently running
+ };
+ /** \brief What state the execution engine is in. */
+ state_t _state;
+
+ /** \brief If there is a working dialog it'll be referenced
+ right here. */
+ Gtk::Dialog * _visibleDialog;
+ /** \brief Signal that the run is complete. */
+ sigc::signal<void> _runComplete;
+ /** \brief In some cases we need a mainLoop, when we do, this is
+ a pointer to it. */
+ Glib::RefPtr<Glib::MainLoop> _mainloop;
+ /** \brief The document that we're working on. */
+ Inkscape::UI::View::View * _doc;
+ /** \brief A document cache if we were passed one. */
+ Implementation::ImplementationDocumentCache * _docCache;
+
+ /** \brief The effect that we're executing in this context. */
+ Effect * _effect;
+
+ /** \brief Show the working dialog when the effect is executing. */
+ bool _show_working;
+public:
+
+ /** \brief Create a new context for execution of an effect
+ \param effect The effect to execute
+ \param doc The document to execute the effect on
+ \param docCache The implementation cache of the document. May be
+ NULL in which case it'll be created by the execution
+ environment.
+ \prarm show_working Show a small dialog signaling the effect
+ is working. Allows for user canceling.
+ \param show_errors If the effect has an error, show it or not.
+ */
+ ExecutionEnv (Effect * effect,
+ Inkscape::UI::View::View * doc,
+ Implementation::ImplementationDocumentCache * docCache = nullptr,
+ bool show_working = true,
+ bool show_errors = true);
+ virtual ~ExecutionEnv ();
+
+ /** \brief Starts the execution of the effect
+ \return Returns whether the effect was executed to completion */
+ void run ();
+ /** \brief Cancel the execution of the effect */
+ void cancel ();
+ /** \brief Commit the changes to the document */
+ void commit ();
+ /** \brief Undoes what the effect completed. */
+ void undo ();
+ /** \brief Wait for the effect to complete if it hasn't. */
+ bool wait ();
+ void reselect ();
+
+ /** \brief Return reference to working dialog (if any) */
+ Gtk::Dialog *get_working_dialog () { return _visibleDialog; };
+
+private:
+ void runComplete ();
+ void createWorkingDialog ();
+ void workingCanceled (const int resp);
+ void genDocCache ();
+ void killDocCache ();
+};
+
+} } /* namespace Inkscape, Extension */
+#endif /* INKSCAPE_EXTENSION_EXECUTION_ENV_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/extension.cpp b/src/extension/extension.cpp
new file mode 100644
index 0000000..b95607f
--- /dev/null
+++ b/src/extension/extension.cpp
@@ -0,0 +1,1111 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *
+ * Inkscape::Extension::Extension:
+ * the ability to have features that are more modular so that they
+ * can be added and removed easily. This is the basis for defining
+ * those actions.
+ */
+
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension.h"
+#include "implementation/implementation.h"
+#include "implementation/script.h"
+#include "implementation/xslt.h"
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+
+#include <glib/gstdio.h>
+#include <glib/gprintf.h>
+
+#include <glibmm/i18n.h>
+#include <gtkmm/box.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/grid.h>
+#include <gtkmm/label.h>
+
+#include "db.h"
+#include "dependency.h"
+#include "inkscape.h"
+#include "timer.h"
+
+#include "io/resource.h"
+#include "io/sys.h"
+
+#include "prefdialog/parameter.h"
+#include "prefdialog/widget.h"
+
+#include "xml/repr.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+/* Inkscape::Extension::Extension */
+
+FILE *Extension::error_file = nullptr;
+
+/**
+ \return none
+ \brief Constructs an Extension from a Inkscape::XML::Node
+ \param in_repr The repr that should be used to build it
+ \param base_directory Base directory of extensions that were loaded from a file (.inx file's location)
+
+ This function is the basis of building an extension for Inkscape. It
+ currently extracts the fields from the Repr that are used in the
+ extension. The Repr will likely include other children that are
+ not related to the module directly. If the Repr does not include
+ a name and an ID the module will be left in an errored state.
+*/
+Extension::Extension(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory)
+ : _gui(true)
+ , execution_env(nullptr)
+{
+ g_return_if_fail(in_repr); // should be ensured in system.cpp
+ repr = in_repr;
+ Inkscape::GC::anchor(repr);
+
+ if (in_imp == nullptr) {
+ imp = new Implementation::Implementation();
+ } else {
+ imp = in_imp;
+ }
+
+ if (base_directory) {
+ _base_directory = *base_directory;
+ }
+
+ // get name of the translation catalog ("gettext textdomain") that the extension wants to use for translations
+ // and lookup the locale directory for it
+ const char *translationdomain = repr->attribute("translationdomain");
+ if (translationdomain) {
+ _translationdomain = translationdomain;
+ } else {
+ _translationdomain = "inkscape"; // default to the Inkscape catalog
+ }
+ if (!strcmp(_translationdomain, "none")) {
+ // special keyword "none" means the extension author does not want translation of extension strings
+ _translation_enabled = false;
+ _translationdomain = nullptr;
+ } else if (!strcmp(_translationdomain, "inkscape")) {
+ // this is our default domain; we know the location already (also respects INKSCAPE_LOCALEDIR)
+ _gettext_catalog_dir = bindtextdomain("inkscape", nullptr);
+ } else {
+ lookup_translation_catalog();
+ }
+
+ // Read XML tree and parse extension
+ Inkscape::XML::Node *child_repr = repr->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
+ chname++;
+ }
+
+ if (!strcmp(chname, "id")) {
+ const char *id = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr;
+ if (id) {
+ _id = g_strdup(id);
+ } else {
+ throw extension_no_id();
+ }
+ } else if (!strcmp(chname, "name")) {
+ const char *name = child_repr->firstChild() ? child_repr->firstChild()->content() : nullptr;
+ if (name) {
+ _name = g_strdup(name);
+ } else {
+ throw extension_no_name();
+ }
+ } else if (InxWidget::is_valid_widget_name(chname)) {
+ InxWidget *widget = InxWidget::make(child_repr, this);
+ if (widget) {
+ _widgets.push_back(widget);
+ }
+ } else if (!strcmp(chname, "dependency")) {
+ _deps.push_back(new Dependency(child_repr, this));
+ } else if (!strcmp(chname, "script")) { // TODO: should these be parsed in their respective Implementation?
+ for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) {
+ if (child->type() == Inkscape::XML::NodeType::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200)
+ const char *interpreted = child->attribute("interpreter");
+ Dependency::type_t type = interpreted ? Dependency::TYPE_FILE : Dependency::TYPE_EXECUTABLE;
+ _deps.push_back(new Dependency(child, this, type));
+ break;
+ }
+ }
+ } else if (!strcmp(chname, "xslt")) { // TODO: should these be parsed in their respective Implementation?
+ for (Inkscape::XML::Node *child = child_repr->firstChild(); child != nullptr; child = child->next()) {
+ if (child->type() == Inkscape::XML::NodeType::ELEMENT_NODE) { // skip non-element nodes (see LP #1372200)
+ _deps.push_back(new Dependency(child, this, Dependency::TYPE_FILE));
+ break;
+ }
+ }
+ } else {
+ // We could do some sanity checking here.
+ // However, we don't really know which additional elements Extension subclasses might need...
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ // all extensions need an ID and a name
+ if (!_id) {
+ throw extension_no_id();
+ }
+ if (!_name) {
+ throw extension_no_name();
+ }
+
+ // filter out extensions that are not compatible with the current platform
+#ifndef _WIN32
+ if (strstr(_id, "win32")) {
+ throw extension_not_compatible();
+ }
+#endif
+
+ // finally register the extension if all checks passed
+ db.register_ext (this);
+}
+
+/**
+ \return none
+ \brief Destroys the Extension
+
+ This function frees all of the strings that could be attached
+ to the extension and also unreferences the repr. This is better
+ than freeing it because it may (I wouldn't know why) be referenced
+ in another place.
+*/
+Extension::~Extension ()
+{
+ set_state(STATE_UNLOADED);
+
+ db.unregister_ext(this);
+
+ Inkscape::GC::release(repr);
+
+ g_free(_id);
+ g_free(_name);
+
+ delete timer;
+ timer = nullptr;
+
+ for (auto widget : _widgets) {
+ delete widget;
+ }
+
+ for (auto & _dep : _deps) {
+ delete _dep;
+ }
+ _deps.clear();
+}
+
+/**
+ \return none
+ \brief A function to set whether the extension should be loaded
+ or unloaded
+ \param in_state Which state should the extension be in?
+
+ It checks to see if this is a state change or not. If we're changing
+ states it will call the appropriate function in the implementation,
+ load or unload. Currently, there is no error checking in this
+ function. There should be.
+*/
+void
+Extension::set_state (state_t in_state)
+{
+ if (_state == STATE_DEACTIVATED) return;
+ if (in_state != _state) {
+ /** \todo Need some more error checking here! */
+ switch (in_state) {
+ case STATE_LOADED:
+ if (imp->load(this))
+ _state = STATE_LOADED;
+
+ if (timer != nullptr) {
+ delete timer;
+ }
+ timer = new ExpirationTimer(this);
+
+ break;
+ case STATE_UNLOADED:
+ imp->unload(this);
+ _state = STATE_UNLOADED;
+
+ if (timer != nullptr) {
+ delete timer;
+ timer = nullptr;
+ }
+ break;
+ case STATE_DEACTIVATED:
+ _state = STATE_DEACTIVATED;
+
+ if (timer != nullptr) {
+ delete timer;
+ timer = nullptr;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return;
+}
+
+/**
+ \return The state the extension is in
+ \brief A getter for the state variable.
+*/
+Extension::state_t
+Extension::get_state ()
+{
+ return _state;
+}
+
+/**
+ \return Whether the extension is loaded or not
+ \brief A quick function to test the state of the extension
+*/
+bool
+Extension::loaded ()
+{
+ return get_state() == STATE_LOADED;
+}
+
+/**
+ \return A boolean saying whether the extension passed the checks
+ \brief A function to check the validity of the extension
+
+ This function chekcs to make sure that there is an id, a name, a
+ repr and an implementation for this extension. Then it checks all
+ of the dependencies to see if they pass. Finally, it asks the
+ implementation to do a check of itself.
+
+ On each check, if there is a failure, it will print a message to the
+ error log for that failure. It is important to note that the function
+ keeps executing if it finds an error, to try and get as many of them
+ into the error log as possible. This should help people debug
+ installations, and figure out what they need to get for the full
+ functionality of Inkscape to be available.
+*/
+bool
+Extension::check ()
+{
+ const char * inx_failure = _(" This is caused by an improper .inx file for this extension."
+ " An improper .inx file could have been caused by a faulty installation of Inkscape.");
+
+ if (repr == nullptr) {
+ printFailure(Glib::ustring(_("the XML description of it got lost.")) + inx_failure);
+ return false;
+ }
+ if (imp == nullptr) {
+ printFailure(Glib::ustring(_("no implementation was defined for the extension.")) + inx_failure);
+ return false;
+ }
+
+ bool retval = true;
+ for (auto _dep : _deps) {
+ if (_dep->check() == false) {
+ printFailure(Glib::ustring(_("a dependency was not met.")));
+ error_file_write(_dep->info_string());
+ retval = false;
+ }
+ }
+
+ if (retval) {
+ return imp->check(this);
+ }
+
+ error_file_write("");
+ return retval;
+}
+
+/** \brief A quick function to print out a standard start of extension
+ errors in the log.
+ \param reason A string explaining why this failed
+
+ Real simple, just put everything into \c error_file.
+*/
+void
+Extension::printFailure (Glib::ustring reason)
+{
+ _error_reason = Glib::ustring::compose(_("Extension \"%1\" failed to load because %2"), _name, reason);
+ error_file_write(_error_reason);
+}
+
+/**
+ \return The XML tree that is used to define the extension
+ \brief A getter for the internal Repr, does not add a reference.
+*/
+Inkscape::XML::Node *
+Extension::get_repr ()
+{
+ return repr;
+}
+
+/**
+ \return The textual id of this extension
+ \brief Get the ID of this extension - not a copy don't delete!
+*/
+gchar *
+Extension::get_id () const
+{
+ return _id;
+}
+
+/**
+ \return The textual name of this extension
+ \brief Get the name of this extension - not a copy don't delete!
+*/
+const gchar *
+Extension::get_name () const
+{
+ return get_translation(_name, nullptr);
+}
+
+/**
+ \return None
+ \brief This function diactivates the extension (which makes it
+ unusable, but not deleted)
+
+ This function is used to removed an extension from functioning, but
+ not delete it completely. It sets the state to \c STATE_DEACTIVATED to
+ mark to the world that it has been deactivated. It also removes
+ the current implementation and replaces it with a standard one. This
+ makes it so that we don't have to continually check if there is an
+ implementation, but we are guaranteed to have a benign one.
+
+ \warning It is important to note that there is no 'activate' function.
+ Running this function is irreversible.
+*/
+void
+Extension::deactivate ()
+{
+ set_state(STATE_DEACTIVATED);
+
+ /* Removing the old implementation, and making this use the default. */
+ /* This should save some memory */
+ delete imp;
+ imp = new Implementation::Implementation();
+
+ return;
+}
+
+/**
+ \return Whether the extension has been deactivated
+ \brief Find out the status of the extension
+*/
+bool
+Extension::deactivated ()
+{
+ return get_state() == STATE_DEACTIVATED;
+}
+
+/** Gets the location of the dependency file as an absolute path
+ *
+ * Iterates over all dependencies of this extension and finds the one with matching name,
+ * then returns the absolute path to this dependency file as determined previously.
+ *
+ * TODO: This function should not be necessary, but we parse script dependencies twice:
+ * - Once here in the Extension::Extension() constructor
+ * - A second time in Script::load() in "script.cpp" when determining the script location
+ * Theoretically we could return the wrong path if an extension depends on two files with the same name
+ * in different relative locations. In practice this risk should be close to zero, though.
+ *
+ * @return Absolute path of the dependency file
+ */
+std::string Extension::get_dependency_location(const char *name)
+{
+ for (auto dep : _deps) {
+ if (!strcmp(name, dep->get_name())) {
+ return dep->get_path();
+ }
+ }
+
+ return "";
+}
+
+/** recursively searches directory for a file named filename; returns true if found */
+static bool _find_filename_recursive(std::string directory, std::string const &filename) {
+ Glib::Dir dir(directory);
+
+ std::string name = dir.read_name();
+ while (!name.empty()) {
+ std::string fullpath = Glib::build_filename(directory, name);
+ // g_message("%s", fullpath.c_str());
+
+ if (Glib::file_test(fullpath, Glib::FILE_TEST_IS_DIR)) {
+ if (_find_filename_recursive(fullpath, filename)) {
+ return true;
+ }
+ } else if (name == filename) {
+ return true;
+ }
+ name = dir.read_name();
+ }
+
+ return false;
+}
+
+/** Searches for a gettext catalog matching the extension's translationdomain
+ *
+ * This function will attempt to find the correct gettext catalog for the translationdomain
+ * requested by the extension.
+ *
+ * For this the following three locations are recursively searched for "${translationdomain}.mo":
+ * - the 'locale' directory in the .inx file's folder
+ * - the 'locale' directory in the "extensions" folder containing the .inx
+ * - the system location for gettext catalogs, i.e. where Inkscape's own catalog is located
+ *
+ * If one matching file is found, the directory is assumed to be the correct location and registered with gettext
+ */
+void Extension::lookup_translation_catalog() {
+ g_assert(!_base_directory.empty());
+
+ // get locale folder locations
+ std::string locale_dir_current_extension;
+ std::string locale_dir_extensions;
+ std::string locale_dir_system;
+
+ locale_dir_current_extension = Glib::build_filename(_base_directory, "locale");
+
+ size_t index = _base_directory.find_last_of("extensions");
+ if (index != std::string::npos) {
+ locale_dir_extensions = Glib::build_filename(_base_directory.substr(0, index+1), "locale");
+ }
+
+ locale_dir_system = bindtextdomain("inkscape", nullptr);
+
+ // collect unique locations into vector
+ std::vector<std::string> locale_dirs;
+ if (locale_dir_current_extension != locale_dir_extensions) {
+ locale_dirs.push_back(std::move(locale_dir_current_extension));
+ }
+ locale_dirs.push_back(std::move(locale_dir_extensions));
+ locale_dirs.push_back(std::move(locale_dir_system));
+
+ // iterate over locations and look for the one that has the correct catalog
+ std::string search_name;
+ search_name += _translationdomain;
+ search_name += ".mo";
+ for (auto locale_dir : locale_dirs) {
+ if (!Glib::file_test(locale_dir, Glib::FILE_TEST_IS_DIR)) {
+ continue;
+ }
+
+ if (_find_filename_recursive(locale_dir, search_name)) {
+ _gettext_catalog_dir = locale_dir;
+ break;
+ }
+ }
+
+ // register catalog with gettext if found, disable translation for this extension otherwise
+ if (!_gettext_catalog_dir.empty()) {
+ const char *current_dir = bindtextdomain(_translationdomain, nullptr);
+ if (_gettext_catalog_dir != current_dir) {
+ g_info("Binding textdomain '%s' to '%s'.", _translationdomain, _gettext_catalog_dir.c_str());
+ bindtextdomain(_translationdomain, _gettext_catalog_dir.c_str());
+ bind_textdomain_codeset(_translationdomain, "UTF-8");
+ }
+ } else {
+ g_warning("Failed to locate message catalog for textdomain '%s'.", _translationdomain);
+ _translation_enabled = false;
+ _translationdomain = nullptr;
+ }
+}
+
+/** Gets a translation within the context of the current extension
+ *
+ * Query gettext for the translated version of the input string,
+ * handling the preferred translation domain of the extension internally.
+ *
+ * @param msgid String to translate
+ * @param msgctxt Context for the translation
+ *
+ * @return Translated string (or original string if extension is not supposed to be translated)
+ */
+const char *Extension::get_translation(const char *msgid, const char *msgctxt) const {
+ if (!_translation_enabled) {
+ return msgid;
+ }
+
+ if (!strcmp(msgid, "")) {
+ g_warning("Attempting to translate an empty string in extension '%s', which is not supported.", _id);
+ return msgid;
+ }
+
+ if (msgctxt) {
+ return g_dpgettext2(_translationdomain, msgctxt, msgid);
+ } else {
+ return g_dgettext(_translationdomain, msgid);
+ }
+}
+
+/** Sets environment suitable for executing this Extension
+ *
+ * Currently sets the environment variables INKEX_GETTEXT_DOMAIN and INKEX_GETTEXT_DIRECTORY
+ * to make the "translationdomain" accessible to child processes spawned by this extension's Implementation.
+ *
+ * @param doc Optional document, if provided sets the DOCUMENT_PATH from the document's save location.
+ */
+void Extension::set_environment(const SPDocument *doc) {
+ Glib::unsetenv("INKEX_GETTEXT_DOMAIN");
+ Glib::unsetenv("INKEX_GETTEXT_DIRECTORY");
+
+ // This is needed so extensions can interact with the user's profile, keep settings etc.
+ Glib::setenv("INKSCAPE_PROFILE_DIR", std::string(Inkscape::IO::Resource::profile_path()));
+
+ // This is needed so files can be saved relative to their document location (see image-extract)
+ if (doc) {
+ auto path = doc->getDocumentFilename();
+ if (!path) {
+ path = ""; // Set to blank string so extensions know the difference between old inkscape and not-saved document.
+ }
+ Glib::setenv("DOCUMENT_PATH", std::string(path));
+ }
+
+ if (_translationdomain) {
+ Glib::setenv("INKEX_GETTEXT_DOMAIN", std::string(_translationdomain));
+ }
+ if (!_gettext_catalog_dir.empty()) {
+ Glib::setenv("INKEX_GETTEXT_DIRECTORY", _gettext_catalog_dir);
+ }
+}
+
+/** Uses the object's type to figure out what the type is.
+ *
+ * @return Returns the type of extension that this object is.
+ */
+ModuleImpType Extension::get_implementation_type()
+{
+ if (dynamic_cast<Implementation::Script *>(imp)) {
+ return MODULE_EXTENSION;
+ } else if (dynamic_cast<Implementation::XSLT *>(imp)) {
+ return MODULE_XSLT;
+ }
+ // MODULE_UNKNOWN_IMP is not required because it never results in an
+ // object being created. Thus this function wouldn't be available.
+ return MODULE_PLUGIN;
+}
+
+/**
+ \brief A function to get the parameters in a string form
+ \return An array with all the parameters in it.
+
+*/
+void
+Extension::paramListString (std::list <std::string> &retlist)
+{
+ // first collect all widgets in the current extension
+ std::vector<InxWidget *> widget_list;
+ for (auto widget : _widgets) {
+ widget->get_widgets(widget_list);
+ }
+
+ // then build a list of parameter strings from parameter names and values, as '--name=value'
+ for (auto widget : widget_list) {
+ InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets
+ if (parameter) {
+ const char *name = parameter->name();
+ std::string value = parameter->value_to_string();
+
+ if (name && !value.empty()) { // TODO: Shouldn't empty string values be allowed?
+ std::string parameter_string;
+ parameter_string += "--";
+ parameter_string += name;
+ parameter_string += "=";
+ parameter_string += value;
+ retlist.push_back(parameter_string);
+ }
+ }
+ }
+
+ return;
+}
+
+InxParameter *Extension::get_param(const gchar *name)
+{
+ if (!name || _widgets.empty()) {
+ throw Extension::param_not_exist();
+ }
+
+ // first collect all widgets in the current extension
+ std::vector<InxWidget *> widget_list;
+ for (auto widget : _widgets) {
+ widget->get_widgets(widget_list);
+ }
+
+ // then search for a parameter with a matching name
+ for (auto widget : widget_list) {
+ InxParameter *parameter = dynamic_cast<InxParameter *>(widget); // filter InxParameters from InxWidgets
+ if (parameter && !strcmp(parameter->name(), name)) {
+ return parameter;
+ }
+ }
+
+ // if execution reaches here, no parameter matching 'name' was found
+ throw Extension::param_not_exist();
+}
+
+InxParameter const *Extension::get_param(const gchar *name) const
+{
+ return const_cast<Extension *>(this)->get_param(name);
+}
+
+
+/**
+ \return The value of the parameter identified by the name
+ \brief Gets a parameter identified by name with the bool placed in value.
+ \param name The name of the parameter to get
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+bool
+Extension::get_param_bool(const gchar *name) const
+{
+ const InxParameter *param;
+ param = get_param(name);
+ return param->get_bool();
+}
+
+/**
+ * \return The value of the param or the alternate if the param doesn't exist.
+ * \brief Like get_param_bool but with a default on param_not_exist error.
+ */
+bool Extension::get_param_bool(const gchar *name, bool alt) const
+{
+ try {
+ return get_param_bool(name);
+ } catch (Extension::param_not_exist) {
+ return alt;
+ }
+}
+
+/**
+ \return The integer value for the parameter specified
+ \brief Gets a parameter identified by name with the integer placed in value.
+ \param name The name of the parameter to get
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+int
+Extension::get_param_int(const gchar *name) const
+{
+ const InxParameter *param;
+ param = get_param(name);
+ return param->get_int();
+}
+
+/**
+ * \return The value of the param or the alternate if the param doesn't exist.
+ * \brief Like get_param_int but with a default on param_not_exist error.
+ */
+int Extension::get_param_int(const gchar *name, int alt) const
+{
+ try {
+ return get_param_int(name);
+ } catch (Extension::param_not_exist) {
+ return alt;
+ }
+}
+
+
+/**
+ \return The double value for the float parameter specified
+ \brief Gets a float parameter identified by name with the double placed in value.
+ \param name The name of the parameter to get
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+double
+Extension::get_param_float(const gchar *name) const
+{
+ const InxParameter *param;
+ param = get_param(name);
+ return param->get_float();
+}
+
+/**
+ * \return The value of the param or the alternate if the param doesn't exist.
+ * \brief Like get_param_float but with a default on param_not_exist error.
+ */
+double Extension::get_param_float(const gchar *name, double alt) const
+{
+ try {
+ return get_param_float(name);
+ } catch (Extension::param_not_exist) {
+ return alt;
+ }
+}
+
+/**
+ \return The string value for the parameter specified
+ \brief Gets a parameter identified by name with the string placed in value.
+ \param name The name of the parameter to get
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+const char *
+Extension::get_param_string(const gchar *name) const
+{
+ const InxParameter *param;
+ param = get_param(name);
+ return param->get_string();
+}
+
+/**
+ * \return The value of the param or the alternate if the param doesn't exist.
+ * \brief Like get_param_string but with a default on param_not_exist error.
+ */
+const char *Extension::get_param_string(const gchar *name, const char *alt) const
+{
+ try {
+ return get_param_string(name);
+ } catch (Extension::param_not_exist) {
+ return alt;
+ }
+}
+
+/**
+ \return The string value for the parameter specified
+ \brief Gets a parameter identified by name with the string placed in value.
+ \param name The name of the parameter to get
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+const char *
+Extension::get_param_optiongroup(const gchar *name) const
+{
+ const InxParameter *param;
+ param = get_param(name);
+ return param->get_optiongroup();
+}
+
+/**
+ * \return The value of the param or the alternate if the param doesn't exist.
+ * \brief Like get_param_optiongroup but with a default on param_not_exist error.
+ */
+const char *Extension::get_param_optiongroup(const gchar *name, const char *alt) const
+{
+ try {
+ return get_param_optiongroup(name);
+ } catch (Extension::param_not_exist) {
+ return alt;
+ }
+}
+
+/**
+ * This is useful to find out, if a given string \c value is selectable in a optiongroup named \cname.
+ *
+ * @param name The name of the optiongroup parameter to get.
+ * @return true if value exists, false if not
+ */
+bool
+Extension::get_param_optiongroup_contains(const gchar *name, const char *value) const
+{
+ const InxParameter *param;
+ param = get_param(name);
+ return param->get_optiongroup_contains(value);
+}
+
+/**
+ \return The unsigned integer RGBA value for the parameter specified
+ \brief Gets a parameter identified by name with the unsigned int placed in value.
+ \param name The name of the parameter to get
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+guint32
+Extension::get_param_color(const gchar *name) const
+{
+ const InxParameter *param;
+ param = get_param(name);
+ return param->get_color();
+}
+
+/**
+ \return The passed in value
+ \brief Sets a parameter identified by name with the boolean in the parameter value.
+ \param name The name of the parameter to set
+ \param value The value to set the parameter to
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+bool
+Extension::set_param_bool(const gchar *name, const bool value)
+{
+ InxParameter *param;
+ param = get_param(name);
+ return param->set_bool(value);
+}
+
+/**
+ \return The passed in value
+ \brief Sets a parameter identified by name with the integer in the parameter value.
+ \param name The name of the parameter to set
+ \param value The value to set the parameter to
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+int
+Extension::set_param_int(const gchar *name, const int value)
+{
+ InxParameter *param;
+ param = get_param(name);
+ return param->set_int(value);
+}
+
+/**
+ \return The passed in value
+ \brief Sets a parameter identified by name with the double in the parameter value.
+ \param name The name of the parameter to set
+ \param value The value to set the parameter to
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+double
+Extension::set_param_float(const gchar *name, const double value)
+{
+ InxParameter *param;
+ param = get_param(name);
+ return param->set_float(value);
+}
+
+/**
+ \return The passed in value
+ \brief Sets a parameter identified by name with the string in the parameter value.
+ \param name The name of the parameter to set
+ \param value The value to set the parameter to
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+const char *
+Extension::set_param_string(const gchar *name, const char *value)
+{
+ InxParameter *param;
+ param = get_param(name);
+ return param->set_string(value);
+}
+
+/**
+ \return The passed in value
+ \brief Sets a parameter identified by name with the string in the parameter value.
+ \param name The name of the parameter to set
+ \param value The value to set the parameter to
+
+ Look up in the parameters list, const then execute the function on that found parameter.
+*/
+const char *
+Extension::set_param_optiongroup(const gchar *name, const char *value)
+{
+ InxParameter *param;
+ param = get_param(name);
+ return param->set_optiongroup(value);
+}
+
+/**
+ \return The passed in value
+ \brief Sets a parameter identified by name with the unsigned integer RGBA value in the parameter value.
+ \param name The name of the parameter to set
+ \param value The value to set the parameter to
+
+Look up in the parameters list, const then execute the function on that found parameter.
+*/
+guint32
+Extension::set_param_color(const gchar *name, const guint32 color)
+{
+ InxParameter *param;
+ param = get_param(name);
+ return param->set_color(color);
+}
+
+
+/** \brief A function to open the error log file. */
+void
+Extension::error_file_open ()
+{
+ gchar *ext_error_file = Inkscape::IO::Resource::log_path(EXTENSION_ERROR_LOG_FILENAME);
+ error_file = Inkscape::IO::fopen_utf8name(ext_error_file, "w+");
+ if (!error_file) {
+ g_warning(_("Could not create extension error log file '%s'"), ext_error_file);
+ }
+ g_free(ext_error_file);
+};
+
+/** \brief A function to close the error log file. */
+void
+Extension::error_file_close ()
+{
+ if (error_file) {
+ fclose(error_file);
+ }
+};
+
+/** \brief A function to write to the error log file. */
+void
+Extension::error_file_write (Glib::ustring text)
+{
+ if (error_file) {
+ g_fprintf(error_file, "%s\n", text.c_str());
+ }
+};
+
+/** \brief A widget to represent the inside of an AutoGUI widget */
+class AutoGUI : public Gtk::Box {
+public:
+ /** \brief Create an AutoGUI object */
+ AutoGUI () : Gtk::Box(Gtk::ORIENTATION_VERTICAL) {};
+
+ /**
+ * Adds a widget with a tool tip into the autogui.
+ *
+ * If there is no widget, nothing happens. Otherwise it is just
+ * added into the VBox. If there is a tooltip (non-NULL) then it
+ * is placed on the widget.
+ *
+ * @param widg Widget to add.
+ * @param tooltip Tooltip for the widget.
+ */
+ void addWidget(Gtk::Widget *widg, gchar const *tooltip, int indent) {
+ if (widg) {
+ widg->set_margin_start(indent * InxParameter::GUI_INDENTATION);
+ this->pack_start(*widg, false, true, 0); // fill=true does not have an effect here, but allows the
+ // child to choose to expand by setting hexpand/vexpand
+ if (tooltip) {
+ widg->set_tooltip_text(tooltip);
+ } else {
+ widg->set_tooltip_text("");
+ widg->set_has_tooltip(false);
+ }
+ }
+ };
+};
+
+/** \brief A function to automatically generate a GUI from the extensions' widgets
+ \return Generated widget
+
+ This function just goes through each widget, and calls it's 'get_widget'.
+ Then, each of those is placed into a Gtk::VBox, which is then returned to the calling function.
+
+ If there are no visible parameters, this function just returns NULL.
+*/
+Gtk::Widget *
+Extension::autogui (SPDocument *doc, Inkscape::XML::Node *node, sigc::signal<void> *changeSignal)
+{
+ if (!_gui || widget_visible_count() == 0) {
+ return nullptr;
+ }
+
+ AutoGUI * agui = Gtk::manage(new AutoGUI());
+ agui->set_border_width(InxParameter::GUI_BOX_MARGIN);
+ agui->set_spacing(InxParameter::GUI_BOX_SPACING);
+
+ // go through the list of widgets and add the all non-hidden ones
+ for (auto widget : _widgets) {
+ if (widget->get_hidden()) {
+ continue;
+ }
+
+ Gtk::Widget *widg = widget->get_widget(changeSignal);
+ gchar const *tip = widget->get_tooltip();
+ int indent = widget->get_indent();
+
+ agui->addWidget(widg, tip, indent);
+ }
+
+ agui->show();
+ return agui;
+};
+
+/* Extension editor dialog stuff */
+
+Gtk::Box *
+Extension::get_info_widget()
+{
+ Gtk::Box * retval = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ retval->set_border_width(4);
+
+ Gtk::Frame * info = Gtk::manage(new Gtk::Frame("General Extension Information"));
+ retval->pack_start(*info, true, true, 4);
+
+ auto table = Gtk::manage(new Gtk::Grid());
+ table->set_border_width(4);
+ table->set_column_spacing(4);
+
+ info->add(*table);
+
+ int row = 0;
+ add_val(_("Name:"), get_translation(_name), table, &row);
+ add_val(_("ID:"), _id, table, &row);
+ add_val(_("State:"), _state == STATE_LOADED ? _("Loaded") : _state == STATE_UNLOADED ? _("Unloaded") : _("Deactivated"), table, &row);
+
+ retval->show_all();
+ return retval;
+}
+
+void Extension::add_val(Glib::ustring labelstr, Glib::ustring valuestr, Gtk::Grid * table, int * row)
+{
+ Gtk::Label * label;
+ Gtk::Label * value;
+
+ (*row)++;
+ label = Gtk::manage(new Gtk::Label(labelstr, Gtk::ALIGN_START));
+ value = Gtk::manage(new Gtk::Label(valuestr, Gtk::ALIGN_START));
+
+ table->attach(*label, 0, (*row) - 1, 1, 1);
+ table->attach(*value, 1, (*row) - 1, 1, 1);
+
+ label->show();
+ value->show();
+
+ return;
+}
+
+Gtk::Box *
+Extension::get_params_widget()
+{
+ Gtk::Box * retval = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ Gtk::Widget * content = Gtk::manage(new Gtk::Label("Params"));
+ retval->pack_start(*content, true, true, 4);
+ content->show();
+ retval->show();
+ return retval;
+}
+
+unsigned int Extension::widget_visible_count ( )
+{
+ unsigned int _visible_count = 0;
+ for (auto widget : _widgets) {
+ if (!widget->get_hidden()) {
+ _visible_count++;
+ }
+ }
+ return _visible_count;
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/extension.h b/src/extension/extension.h
new file mode 100644
index 0000000..7ac64a8
--- /dev/null
+++ b/src/extension/extension.h
@@ -0,0 +1,307 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_H
+#define INK_EXTENSION_H
+
+/** \file
+ * Frontend to certain, possibly pluggable, actions.
+ */
+
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <fstream>
+#include <ostream>
+#include <vector>
+
+#include <glib.h>
+#include <sigc++/signal.h>
+
+namespace Glib {
+ class ustring;
+}
+
+namespace Gtk {
+ class Grid;
+ class Box;
+ class Widget;
+}
+
+/** The key that is used to identify that the I/O should be autodetected */
+#define SP_MODULE_KEY_AUTODETECT "autodetect"
+/** This is the key for the SVG input module */
+#define SP_MODULE_KEY_INPUT_SVG "org.inkscape.input.svg"
+#define SP_MODULE_KEY_INPUT_SVGZ "org.inkscape.input.svgz"
+/** Specifies the input module that should be used if none are selected */
+#define SP_MODULE_KEY_INPUT_DEFAULT SP_MODULE_KEY_AUTODETECT
+/** The key for outputting standard W3C SVG */
+#define SP_MODULE_KEY_OUTPUT_SVG "org.inkscape.output.svg.plain"
+#define SP_MODULE_KEY_OUTPUT_SVGZ "org.inkscape.output.svgz.plain"
+/** This is an output file that has SVG data with the Sodipodi namespace extensions */
+#define SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE "org.inkscape.output.svg.inkscape"
+#define SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE "org.inkscape.output.svgz.inkscape"
+/** Which output module should be used? */
+#define SP_MODULE_KEY_OUTPUT_DEFAULT SP_MODULE_KEY_AUTODETECT
+
+/** Internal raster extensions */
+#define SP_MODULE_KEY_RASTER_PNG "org.inkscape.output.png.inkscape"
+
+/** Defines the key for Postscript printing */
+#define SP_MODULE_KEY_PRINT_PS "org.inkscape.print.ps"
+#define SP_MODULE_KEY_PRINT_CAIRO_PS "org.inkscape.print.ps.cairo"
+#define SP_MODULE_KEY_PRINT_CAIRO_EPS "org.inkscape.print.eps.cairo"
+/** Defines the key for PDF printing */
+#define SP_MODULE_KEY_PRINT_PDF "org.inkscape.print.pdf"
+#define SP_MODULE_KEY_PRINT_CAIRO_PDF "org.inkscape.print.pdf.cairo"
+/** Defines the key for LaTeX printing */
+#define SP_MODULE_KEY_PRINT_LATEX "org.inkscape.print.latex"
+/** Defines the key for printing with GNOME Print */
+#define SP_MODULE_KEY_PRINT_GNOME "org.inkscape.print.gnome"
+
+/** Mime type for SVG */
+#define MIME_SVG "image/svg+xml"
+
+/** Name of the extension error file */
+#define EXTENSION_ERROR_LOG_FILENAME "extension-errors.log"
+
+
+#define INKSCAPE_EXTENSION_URI "http://www.inkscape.org/namespace/inkscape/extension"
+#define INKSCAPE_EXTENSION_NS_NC "extension"
+#define INKSCAPE_EXTENSION_NS "extension:"
+
+enum ModuleImpType
+{
+ MODULE_EXTENSION, // implementation/script.h python extensions
+ MODULE_XSLT, // implementation/xslt.h xml transform extensions
+ MODULE_PLUGIN, // plugins/*/*.h C++ extensions
+ MODULE_UNKNOWN_IMP // No implementation, so nothing created.
+};
+enum ModuleFuncType
+{
+ MODULE_INPUT,
+ MODULE_OUTPUT,
+ MODULE_FILTER,
+ MODULE_PRINT,
+ MODULE_PATH_EFFECT,
+ MODULE_UNKNOWN_FUNC
+};
+
+class SPDocument;
+
+namespace Inkscape {
+
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class ExecutionEnv;
+class Dependency;
+class ExpirationTimer;
+class ExpirationTimer;
+class InxParameter;
+class InxWidget;
+
+namespace Implementation
+{
+class Implementation;
+}
+
+
+/** The object that is the basis for the Extension system. This object
+ contains all of the information that all Extension have. The
+ individual items are detailed within. This is the interface that
+ those who want to _use_ the extensions system should use. This
+ is most likely to be those who are inside the Inkscape program. */
+class Extension {
+public:
+ /** An enumeration to identify if the Extension has been loaded or not. */
+ enum state_t {
+ STATE_LOADED, /**< The extension has been loaded successfully */
+ STATE_UNLOADED, /**< The extension has not been loaded */
+ STATE_DEACTIVATED /**< The extension is missing something which makes it unusable */
+ };
+
+private:
+ gchar *_id = nullptr; /**< The unique identifier for the Extension */
+ gchar *_name = nullptr; /**< A user friendly name for the Extension */
+ state_t _state = STATE_UNLOADED; /**< Which state the Extension is currently in */
+ std::vector<Dependency *> _deps; /**< Dependencies for this extension */
+ static FILE *error_file; /**< This is the place where errors get reported */
+ bool _gui;
+ std::string _error_reason; /**< Short, textual explanation for the latest error */
+
+protected:
+ Inkscape::XML::Node *repr; /**< The XML description of the Extension */
+ Implementation::Implementation * imp; /**< An object that holds all the functions for making this work */
+ ExecutionEnv * execution_env; /**< Execution environment of the extension
+ * (currently only used by Effects) */
+ std::string _base_directory; /**< Directory containing the .inx file,
+ * relative paths in the extension should usually be relative to it */
+ ExpirationTimer * timer = nullptr; /**< Timeout to unload after a given time */
+ bool _translation_enabled = true; /**< Attempt translation of strings provided by the extension? */
+
+private:
+ const char *_translationdomain = nullptr; /**< Domainname of gettext textdomain that should
+ * be used for translation of the extension's strings */
+ std::string _gettext_catalog_dir; /**< Directory containing the gettext catalog for _translationdomain */
+
+ void lookup_translation_catalog();
+
+public:
+ Extension(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory);
+ virtual ~Extension();
+
+ void set_state (state_t in_state);
+ state_t get_state ();
+ bool loaded ();
+ virtual bool check ();
+ Inkscape::XML::Node * get_repr ();
+ gchar * get_id () const;
+ const gchar * get_name () const;
+ void deactivate ();
+ bool deactivated ();
+ void printFailure (Glib::ustring reason);
+ std::string const &getErrorReason() { return _error_reason; };
+ Implementation::Implementation * get_imp () { return imp; };
+ void set_execution_env (ExecutionEnv * env) { execution_env = env; };
+ ExecutionEnv *get_execution_env () { return execution_env; };
+ std::string get_base_directory() const { return _base_directory; };
+ void set_base_directory(std::string const &base_directory) { _base_directory = base_directory; };
+ std::string get_dependency_location(const char *name);
+ const char *get_translation(const char* msgid, const char *msgctxt=nullptr) const;
+ void set_environment(const SPDocument *doc=nullptr);
+ ModuleImpType get_implementation_type();
+
+/* Parameter Stuff */
+private:
+ std::vector<InxWidget *> _widgets; /**< A list of widgets for this extension. */
+
+public:
+ /** \brief A function to get the number of visible parameters of the extension.
+ \return The number of visible parameters. */
+ unsigned int widget_visible_count ( );
+
+public:
+ /** An error class for when a parameter is looked for that just
+ * simply doesn't exist */
+ class param_not_exist {};
+
+ /** no valid ID found while parsing XML representation */
+ class extension_no_id{};
+
+ /** no valid name found while parsing XML representation */
+ class extension_no_name{};
+
+ /** extension is not compatible with the current system and should not be loaded */
+ class extension_not_compatible{};
+
+ /** An error class for when a filename already exists, but the user
+ * doesn't want to overwrite it */
+ class no_overwrite {};
+
+private:
+ void make_param (Inkscape::XML::Node * paramrepr);
+
+ /**
+ * Looks up the parameter with the specified name.
+ *
+ * Searches the list of parameters attached to this extension,
+ * looking for a parameter with a matching name.
+ *
+ * This function can throw a 'param_not_exist' exception if the
+ * name is not found.
+ *
+ * @param name Name of the parameter to search for.
+ * @return Parameter with matching name.
+ */
+ InxParameter *get_param(const gchar *name);
+
+ InxParameter const *get_param(const gchar *name) const;
+
+public:
+ bool get_param_bool (const gchar *name) const;
+ bool get_param_bool (const gchar *name, bool alt) const;
+ int get_param_int (const gchar *name) const;
+ int get_param_int (const gchar *name, int alt) const;
+ double get_param_float (const gchar *name) const;
+ double get_param_float (const gchar *name, double alt) const;
+ const char *get_param_string (const gchar *name, const char *alt) const;
+ const char *get_param_string (const gchar *name) const;
+ const char *get_param_optiongroup (const gchar *name, const char *alt) const;
+ const char *get_param_optiongroup (const gchar *name) const;
+ guint32 get_param_color (const gchar *name) const;
+
+ bool get_param_optiongroup_contains (const gchar *name, const char *value) const;
+
+ bool set_param_bool (const gchar *name, const bool value);
+ int set_param_int (const gchar *name, const int value);
+ double set_param_float (const gchar *name, const double value);
+ const char *set_param_string (const gchar *name, const char *value);
+ const char *set_param_optiongroup (const gchar *name, const char *value);
+ guint32 set_param_color (const gchar *name, const guint32 color);
+
+
+ /* Error file handling */
+public:
+ static void error_file_open ();
+ static void error_file_close ();
+ static void error_file_write (Glib::ustring text);
+
+public:
+ Gtk::Widget *autogui (SPDocument *doc, Inkscape::XML::Node *node, sigc::signal<void> *changeSignal = nullptr);
+ void paramListString(std::list <std::string> &retlist);
+ void set_gui(bool s) { _gui = s; }
+ bool get_gui() { return _gui; }
+
+ /* Extension editor dialog stuff */
+public:
+ Gtk::Box *get_info_widget();
+ Gtk::Box *get_params_widget();
+protected:
+ inline static void add_val(Glib::ustring labelstr, Glib::ustring valuestr, Gtk::Grid * table, int * row);
+};
+
+
+
+/*
+
+This is a prototype for how collections should work. Whoever gets
+around to implementing this gets to decide what a 'folder' and an
+'item' really is. That is the joy of implementing it, eh?
+
+class Collection : public Extension {
+
+public:
+ folder get_root (void);
+ int get_count (folder);
+ thumbnail get_thumbnail(item);
+ item[] get_items(folder);
+ folder[] get_folders(folder);
+ metadata get_metadata(item);
+ image get_image(item);
+
+};
+*/
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif // INK_EXTENSION_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/find_extension_by_mime.h b/src/extension/find_extension_by_mime.h
new file mode 100644
index 0000000..f58580e
--- /dev/null
+++ b/src/extension/find_extension_by_mime.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Find an extension by its mime type.
+ */
+/* Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Frank Felfe <innerspace@iname.com>
+ * bulia byak <buliabyak@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ * Kris De Gussem <Kris.DeGussem@gmail.com>
+ *
+ * Copyright (C) 2012 Kris De Gussem
+ * Copyright (C) 2010 authors
+ * Copyright (C) 1999-2005 authors
+ * Copyright (C) 2004 David Turner
+ * Copyright (C) 2001-2002 Ximian, Inc.
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "input.h"
+
+namespace Inkscape {
+namespace Extension {
+static inline Inkscape::Extension::Extension *find_by_mime(const char *const mime)
+{
+
+ Inkscape::Extension::DB::InputList o;
+ Inkscape::Extension::db.get_input_list(o);
+ Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
+ while (i != o.end() && strcmp((*i)->get_mimetype(), mime) != 0) {
+ ++i;
+ }
+ return *i;
+}
+}
+}
diff --git a/src/extension/implementation/implementation.cpp b/src/extension/implementation/implementation.cpp
new file mode 100644
index 0000000..36cd299
--- /dev/null
+++ b/src/extension/implementation/implementation.cpp
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ Author: Ted Gould <ted@gould.cx>
+ Copyright (c) 2003-2005,2007
+
+ Released under GNU GPL v2+, read the file 'COPYING' for more information.
+
+ This file is the backend to the extensions system. These are
+ the parts of the system that most users will never see, but are
+ important for implementing the extensions themselves. This file
+ contains the base class for all of that.
+*/
+
+#include "implementation.h"
+
+#include <extension/output.h>
+#include <extension/input.h>
+#include <extension/effect.h>
+
+#include "selection.h"
+#include "desktop.h"
+
+
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+Gtk::Widget *
+Implementation::prefs_input(Inkscape::Extension::Input *module, gchar const */*filename*/) {
+ return module->autogui(nullptr, nullptr);
+}
+
+Gtk::Widget *
+Implementation::prefs_output(Inkscape::Extension::Output *module) {
+ return module->autogui(nullptr, nullptr);
+}
+
+Gtk::Widget *Implementation::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, ImplementationDocumentCache * /*docCache*/)
+{
+ if (module->widget_visible_count() == 0) {
+ return nullptr;
+ }
+
+ SPDocument * current_document = view->doc();
+
+ auto selected = ((SPDesktop *) view)->getSelection()->items();
+ Inkscape::XML::Node const* first_select = nullptr;
+ if (!selected.empty()) {
+ const SPItem * item = selected.front();
+ first_select = item->getRepr();
+ }
+
+ // TODO deal with this broken const correctness:
+ return module->autogui(current_document, const_cast<Inkscape::XML::Node *>(first_select), changeSignal);
+} // Implementation::prefs_effect
+
+} /* namespace Implementation */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/implementation/implementation.h b/src/extension/implementation/implementation.h
new file mode 100644
index 0000000..edd9e72
--- /dev/null
+++ b/src/extension/implementation/implementation.h
@@ -0,0 +1,208 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ Author: Ted Gould <ted@gould.cx>
+ Copyright (c) 2003-2005,2007
+
+ Released under GNU GPL v2+, read the file 'COPYING' for more information.
+
+ This file is the backend to the extensions system. These are
+ the parts of the system that most users will never see, but are
+ important for implementing the extensions themselves. This file
+ contains the base class for all of that.
+*/
+#ifndef SEEN_INKSCAPE_EXTENSION_IMPLEMENTATION_H
+#define SEEN_INKSCAPE_EXTENSION_IMPLEMENTATION_H
+
+#include <vector>
+#include <sigc++/signal.h>
+#include <glibmm/value.h>
+#include <2geom/forward.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+class SPDocument;
+class SPStyle;
+
+namespace Inkscape {
+
+namespace UI {
+namespace View {
+class View;
+}
+}
+
+namespace XML {
+ class Node;
+}
+
+namespace Extension {
+
+class Effect;
+class Extension;
+class Input;
+class Output;
+class Print;
+
+namespace Implementation {
+
+/**
+ * A cache for the document and this implementation.
+ */
+class ImplementationDocumentCache {
+
+ /**
+ * The document that this instance is working on.
+ */
+ Inkscape::UI::View::View * _view;
+public:
+ explicit ImplementationDocumentCache (Inkscape::UI::View::View * view) { _view = view;};
+
+ virtual ~ImplementationDocumentCache ( ) { return; };
+ Inkscape::UI::View::View const * view ( ) { return _view; };
+};
+
+/**
+ * Base class for all implementations of modules. This is whether they are done systematically by
+ * having something like the scripting system, or they are implemented internally they all derive
+ * from this class.
+ */
+class Implementation {
+public:
+ // ----- Constructor / destructor -----
+ Implementation() = default;
+
+ virtual ~Implementation() = default;
+
+ // ----- Basic functions for all Extension -----
+ virtual bool load(Inkscape::Extension::Extension * /*module*/) { return true; }
+
+ virtual void unload(Inkscape::Extension::Extension * /*module*/) {}
+
+ /**
+ * Create a new document cache object.
+ * This function just returns \c NULL. Subclasses are likely
+ * to reimplement it to do something useful.
+ * @param ext The extension that is referencing us
+ * @param doc The document to create the cache of
+ * @return A new document cache that is valid as long as the document
+ * is not changed.
+ */
+ virtual ImplementationDocumentCache * newDocCache (Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * /*doc*/) { return nullptr; }
+
+ /** Verify any dependencies. */
+ virtual bool check(Inkscape::Extension::Extension * /*module*/) { return true; }
+
+ virtual bool cancelProcessing () { return true; }
+ virtual void commitDocument () {}
+
+ // ----- Input functions -----
+ /** Find out information about the file. */
+ virtual Gtk::Widget *prefs_input(Inkscape::Extension::Input *module,
+ gchar const *filename);
+
+ virtual SPDocument *open(Inkscape::Extension::Input * /*module*/,
+ gchar const * /*filename*/) { return nullptr; }
+
+ // ----- Output functions -----
+ /** Find out information about the file. */
+ virtual Gtk::Widget *prefs_output(Inkscape::Extension::Output *module);
+ virtual void save(Inkscape::Extension::Output * /*module*/, SPDocument * /*doc*/, gchar const * /*filename*/) {}
+ virtual void export_raster(
+ Inkscape::Extension::Output * /*module*/,
+ const SPDocument * /*doc*/,
+ std::string const &/*png_file*/,
+ gchar const * /*filename*/) {}
+
+ // ----- Effect functions -----
+ /** Find out information about the file. */
+ virtual Gtk::Widget * prefs_effect(Inkscape::Extension::Effect *module,
+ Inkscape::UI::View::View *view,
+ sigc::signal<void> *changeSignal,
+ ImplementationDocumentCache *docCache);
+ virtual void effect(Inkscape::Extension::Effect * /*module*/,
+ Inkscape::UI::View::View * /*document*/,
+ ImplementationDocumentCache * /*docCache*/) {}
+
+ // ----- Print functions -----
+ virtual unsigned setup(Inkscape::Extension::Print * /*module*/) { return 0; }
+ virtual unsigned set_preview(Inkscape::Extension::Print * /*module*/) { return 0; }
+
+ virtual unsigned begin(Inkscape::Extension::Print * /*module*/,
+ SPDocument * /*doc*/) { return 0; }
+ virtual unsigned finish(Inkscape::Extension::Print * /*module*/) { return 0; }
+
+ /**
+ * Tell the printing engine whether text should be text or path.
+ * Default value is false because most printing engines will support
+ * paths more than they'll support text. (at least they do today)
+ * \retval true Render the text as a path
+ * \retval false Render text using the text function (above)
+ */
+ virtual bool textToPath(Inkscape::Extension::Print * /*ext*/) { return false; }
+
+ /**
+ * Get "fontEmbedded" param, i.e. tell the printing engine whether fonts should be embedded.
+ * Only available for Adobe Type 1 fonts in EPS output as of now
+ * \retval true Fonts have to be embedded in the output so that the user might not need
+ * to install fonts to have the interpreter read the document correctly
+ * \retval false Do not embed fonts
+ */
+ virtual bool fontEmbedded(Inkscape::Extension::Print * /*ext*/) { return false; }
+
+ // ----- Rendering methods -----
+ virtual unsigned bind(Inkscape::Extension::Print * /*module*/,
+ Geom::Affine const & /*transform*/,
+ float /*opacity*/) { return 0; }
+ virtual unsigned release(Inkscape::Extension::Print * /*module*/) { return 0; }
+ virtual unsigned fill(Inkscape::Extension::Print * /*module*/,
+ Geom::PathVector const & /*pathv*/,
+ Geom::Affine const & /*ctm*/,
+ SPStyle const * /*style*/,
+ Geom::OptRect const & /*pbox*/,
+ Geom::OptRect const & /*dbox*/,
+ Geom::OptRect const & /*bbox*/) { return 0; }
+ virtual unsigned stroke(Inkscape::Extension::Print * /*module*/,
+ Geom::PathVector const & /*pathv*/,
+ Geom::Affine const & /*transform*/,
+ SPStyle const * /*style*/,
+ Geom::OptRect const & /*pbox*/,
+ Geom::OptRect const & /*dbox*/,
+ Geom::OptRect const & /*bbox*/) { return 0; }
+ virtual unsigned image(Inkscape::Extension::Print * /*module*/,
+ unsigned char * /*px*/,
+ unsigned int /*w*/,
+ unsigned int /*h*/,
+ unsigned int /*rs*/,
+ Geom::Affine const & /*transform*/,
+ SPStyle const * /*style*/) { return 0; }
+ virtual unsigned text(Inkscape::Extension::Print * /*module*/,
+ char const * /*text*/,
+ Geom::Point const & /*p*/,
+ SPStyle const * /*style*/) { return 0; }
+ virtual void processPath(Inkscape::XML::Node * /*node*/) {}
+
+ /**
+ * If detach = true, when saving to a file, don't store URIs relative to the filename
+ */
+ virtual void setDetachBase(bool detach) {}
+};
+
+
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // __INKSCAPE_EXTENSION_IMPLEMENTATION_H__
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/implementation/script.cpp b/src/extension/implementation/script.cpp
new file mode 100644
index 0000000..0d0a07e
--- /dev/null
+++ b/src/extension/implementation/script.cpp
@@ -0,0 +1,1022 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * Code for handling extensions (i.e. scripts).
+ *
+ * Authors:
+ * Bryce Harrington <bryce@osdl.org>
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2002-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glib/gstdio.h>
+#include <glibmm.h>
+#include <glibmm/convert.h>
+#include <glibmm/miscutils.h>
+#include <gtkmm/main.h>
+#include <gtkmm/messagedialog.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/textview.h>
+
+#include "desktop.h"
+#include "inkscape.h"
+#include "inkscape-window.h"
+#include "path-prefix.h"
+#include "preferences.h"
+#include "script.h"
+#include "selection.h"
+
+#include "extension/db.h"
+#include "extension/effect.h"
+#include "extension/execution-env.h"
+#include "extension/init.h"
+#include "extension/input.h"
+#include "extension/output.h"
+#include "extension/system.h"
+#include "io/resource.h"
+#include "layer-manager.h"
+#include "object/sp-namedview.h"
+#include "object/sp-path.h"
+#include "ui/desktop/menubar.h"
+#include "ui/dialog-events.h"
+#include "ui/tool/control-point-selection.h"
+#include "ui/tool/multi-path-manipulator.h"
+#include "ui/tool/path-manipulator.h"
+#include "ui/tools/node-tool.h"
+#include "ui/view/view.h"
+#include "widgets/desktop-widget.h"
+#include "xml/attribute-record.h"
+#include "xml/node.h"
+
+/* Namespaces */
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+/** \brief Make GTK+ events continue to come through a little bit
+
+ This just keeps coming the events through so that we'll make the GUI
+ update and look pretty.
+*/
+void Script::pump_events () {
+ while ( Gtk::Main::events_pending() ) {
+ Gtk::Main::iteration();
+ }
+ return;
+}
+
+
+/** \brief A table of what interpreters to call for a given language
+
+ This table is used to keep track of all the programs to execute a
+ given script. It also tracks the preference to use to overwrite
+ the given interpreter to a custom one per user.
+*/
+const std::map<std::string, Script::interpreter_t> Script::interpreterTab = {
+ // clang-format off
+#ifdef _WIN32
+ { "perl", {"perl-interpreter", {"wperl" }}},
+ { "python", {"python-interpreter", {"pythonw" }}},
+#elif defined __APPLE__
+ { "perl", {"perl-interpreter", {"perl" }}},
+ { "python", {"python-interpreter", {"python3" }}},
+#else
+ { "perl", {"perl-interpreter", {"perl" }}},
+ { "python", {"python-interpreter", {"python3", "python" }}},
+#endif
+ { "python2", {"python2-interpreter", {"python2", "python" }}},
+ { "ruby", {"ruby-interpreter", {"ruby" }}},
+ { "shell", {"shell-interpreter", {"sh" }}},
+ // clang-format on
+};
+
+
+
+/** \brief Look up an interpreter name, and translate to something that
+ is executable
+ \param interpNameArg The name of the interpreter that we're looking
+ for, should be an entry in interpreterTab
+*/
+std::string Script::resolveInterpreterExecutable(const Glib::ustring &interpNameArg)
+{
+ // 0. Do we have a supported interpreter type?
+ auto interp = interpreterTab.find(interpNameArg);
+ if (interp == interpreterTab.end()) {
+ g_critical("Script::resolveInterpreterExecutable(): unknown script interpreter '%s'", interpNameArg.c_str());
+ return "";
+ }
+
+ std::list<Glib::ustring> searchList;
+ std::copy(interp->second.defaultvals.begin(), interp->second.defaultvals.end(), std::back_inserter(searchList));
+
+ // 1. Check preferences for an override.
+ auto prefs = Inkscape::Preferences::get();
+ auto prefInterp = prefs->getString("/extensions/" + Glib::ustring(interp->second.prefstring));
+
+ if (!prefInterp.empty()) {
+ searchList.push_front(prefInterp);
+ }
+
+ // 2. Search for things in the path if they're there or an absolute
+ for (const auto& binname : searchList) {
+ auto interpreter_path = Glib::filename_from_utf8(binname);
+
+ if (!Glib::path_is_absolute(interpreter_path)) {
+ auto found_path = Glib::find_program_in_path(interpreter_path);
+ if (!found_path.empty()) {
+ return found_path;
+ }
+ } else {
+ return interpreter_path;
+ }
+ }
+
+ // 3. Error
+ g_critical("Script::resolveInterpreterExecutable(): failed to locate script interpreter '%s'", interpNameArg.c_str());
+ return "";
+}
+
+/** \brief This function creates a script object and sets up the
+ variables.
+ \return A script object
+
+ This function just sets the command to NULL. It should get built
+ officially in the load function. This allows for less allocation
+ of memory in the unloaded state.
+*/
+Script::Script()
+ : Implementation()
+ , _canceled(false)
+ , parent_window(nullptr)
+{
+}
+
+/**
+ * \brief Destructor
+ */
+Script::~Script()
+= default;
+
+
+/**
+ \return none
+ \brief This function 'loads' an extension, basically it determines
+ the full command for the extension and stores that.
+ \param module The extension to be loaded.
+
+ The most difficult part about this function is finding the actual
+ command through all of the Reprs. Basically it is hidden down a
+ couple of layers, and so the code has to move down too. When
+ the command is actually found, it has its relative directory
+ solved.
+
+ At that point all of the loops are exited, and there is an
+ if statement to make sure they didn't exit because of not finding
+ the command. If that's the case, the extension doesn't get loaded
+ and should error out at a higher level.
+*/
+
+bool Script::load(Inkscape::Extension::Extension *module)
+{
+ if (module->loaded()) {
+ return true;
+ }
+
+ helper_extension = "";
+
+ /* This should probably check to find the executable... */
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+ for (child_repr = child_repr->firstChild(); child_repr != nullptr; child_repr = child_repr->next()) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "command")) {
+ const gchar *interpretstr = child_repr->attribute("interpreter");
+ if (interpretstr != nullptr) {
+ std::string interpString = resolveInterpreterExecutable(interpretstr);
+ if (interpString.empty()) {
+ continue; // can't have a script extension with empty interpreter
+ }
+ command.push_back(interpString);
+ }
+ // TODO: we already parse commands as dependencies in extension.cpp
+ // can can we optimize this to be less indirect?
+ const char *script_name = child_repr->firstChild()->content();
+ std::string script_location = module->get_dependency_location(script_name);
+ command.push_back(std::move(script_location));
+ } else if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
+ helper_extension = child_repr->firstChild()->content();
+ }
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ // TODO: Currently this causes extensions to fail silently, see comment in Extension::set_state()
+ g_return_val_if_fail(command.size() > 0, false);
+
+ return true;
+}
+
+
+/**
+ \return None.
+ \brief Unload this puppy!
+ \param module Extension to be unloaded.
+
+ This function just sets the module to unloaded. It free's the
+ command if it has been allocated.
+*/
+void Script::unload(Inkscape::Extension::Extension */*module*/)
+{
+ command.clear();
+ helper_extension = "";
+}
+
+
+
+
+/**
+ \return Whether the check passed or not
+ \brief Check every dependency that was given to make sure we should keep this extension
+ \param module The Extension in question
+
+*/
+bool Script::check(Inkscape::Extension::Extension *module)
+{
+ int script_count = 0;
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "script")) {
+ script_count++;
+
+ // check if all helper_extensions attached to this script were registered
+ child_repr = child_repr->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "helper_extension")) {
+ gchar const *helper = child_repr->firstChild()->content();
+ if (Inkscape::Extension::db.get(helper) == nullptr) {
+ return false;
+ }
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ if (script_count == 0) {
+ return false;
+ }
+
+ return true;
+}
+
+class ScriptDocCache : public ImplementationDocumentCache {
+ friend class Script;
+protected:
+ std::string _filename;
+ int _tempfd;
+public:
+ ScriptDocCache (Inkscape::UI::View::View * view);
+ ~ScriptDocCache ( ) override;
+};
+
+ScriptDocCache::ScriptDocCache (Inkscape::UI::View::View * view) :
+ ImplementationDocumentCache(view),
+ _filename(""),
+ _tempfd(0)
+{
+ try {
+ _tempfd = Glib::file_open_tmp(_filename, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+
+ SPDesktop *desktop = (SPDesktop *) view;
+ sp_namedview_document_from_window(desktop);
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool("/options/svgoutput/disable_optimizations", true);
+ Inkscape::Extension::save(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
+ view->doc(), _filename.c_str(), false, false, Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ prefs->setBool("/options/svgoutput/disable_optimizations", false);
+ return;
+}
+
+ScriptDocCache::~ScriptDocCache ( )
+{
+ close(_tempfd);
+ unlink(_filename.c_str());
+}
+
+ImplementationDocumentCache *Script::newDocCache( Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view ) {
+ return new ScriptDocCache(view);
+}
+
+
+/**
+ \return A dialog for preferences
+ \brief A stub function right now
+ \param module Module who's preferences need getting
+ \param filename Hey, the file you're getting might be important
+
+ This function should really do something, right now it doesn't.
+*/
+Gtk::Widget *Script::prefs_input(Inkscape::Extension::Input *module,
+ const gchar */*filename*/)
+{
+ return module->autogui(nullptr, nullptr);
+}
+
+
+
+/**
+ \return A dialog for preferences
+ \brief A stub function right now
+ \param module Module whose preferences need getting
+
+ This function should really do something, right now it doesn't.
+*/
+Gtk::Widget *Script::prefs_output(Inkscape::Extension::Output *module)
+{
+ return module->autogui(nullptr, nullptr);
+}
+
+/**
+ \return A new document that has been opened
+ \brief This function uses a filename that is put in, and calls
+ the extension's command to create an SVG file which is
+ returned.
+ \param module Extension to use.
+ \param filename File to open.
+
+ First things first, this function needs a temporary file name. To
+ create one of those the function Glib::file_open_tmp is used with
+ the header of ink_ext_.
+
+ The extension is then executed using the 'execute' function
+ with the filename assigned and then the temporary filename.
+ After execution the SVG should be in the temporary file.
+
+ Finally, the temporary file is opened using the SVG input module and
+ a document is returned. That document has its filename set to
+ the incoming filename (so that it's not the temporary filename).
+ That document is then returned from this function.
+*/
+SPDocument *Script::open(Inkscape::Extension::Input *module,
+ const gchar *filenameArg)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment();
+
+ std::string tempfilename_out;
+ int tempfd_out = 0;
+ try {
+ tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return nullptr;
+ }
+
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+
+ file_listener fileout;
+ int data_read = execute(command, params, lfilename, fileout);
+ fileout.toFile(tempfilename_out);
+
+ SPDocument * mydoc = nullptr;
+ if (data_read > 10) {
+ if (helper_extension.size()==0) {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
+ tempfilename_out.c_str());
+ } else {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(helper_extension.c_str()),
+ tempfilename_out.c_str());
+ }
+ } // data_read
+
+ if (mydoc != nullptr) {
+ mydoc->setDocumentBase(nullptr);
+ mydoc->changeFilenameAndHrefs(filenameArg);
+ }
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_out);
+
+ unlink(tempfilename_out.c_str());
+
+ return mydoc;
+} // open
+
+
+
+/**
+ \return none
+ \brief This function uses an extension to save a document. It first
+ creates an SVG file of the document, and then runs it through
+ the script.
+ \param module Extension to be used
+ \param doc Document to be saved
+ \param filename The name to save the final file as
+ \return false in case of any failure writing the file, otherwise true
+
+ Well, at some point people need to save - it is really what makes
+ the entire application useful. And, it is possible that someone
+ would want to use an extension for this, so we need a function to
+ do that, eh?
+
+ First things first, the document is saved to a temporary file that
+ is an SVG file. To get the temporary filename Glib::file_open_tmp is used with
+ ink_ext_ as a prefix. Don't worry, this file gets deleted at the
+ end of the function.
+
+ After we have the SVG file, then Script::execute is called with
+ the temporary file name and the final output filename. This should
+ put the output of the script into the final output file. We then
+ delete the temporary file.
+*/
+void Script::save(Inkscape::Extension::Output *module,
+ SPDocument *doc,
+ const gchar *filenameArg)
+{
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ std::string tempfilename_in;
+ int tempfd_in = 0;
+ try {
+ tempfd_in = Glib::file_open_tmp(tempfilename_in, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ if (helper_extension.size() == 0) {
+ Inkscape::Extension::save(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE),
+ doc, tempfilename_in.c_str(), false, false,
+ Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ } else {
+ Inkscape::Extension::save(
+ Inkscape::Extension::db.get(helper_extension.c_str()),
+ doc, tempfilename_in.c_str(), false, false,
+ Inkscape::Extension::FILE_SAVE_METHOD_TEMPORARY);
+ }
+
+
+ file_listener fileout;
+ int data_read = execute(command, params, tempfilename_in, fileout);
+
+ bool success = false;
+
+ if (data_read > 0) {
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+ success = fileout.toFile(lfilename);
+ }
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_in);
+ // FIXME: convert to utf8 (from "filename encoding") and unlink_utf8name
+ unlink(tempfilename_in.c_str());
+
+ if (success == false) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ return;
+}
+
+
+void Script::export_raster(Inkscape::Extension::Output *module,
+ const SPDocument *doc,
+ const std::string &png_file,
+ const gchar *filenameArg)
+{
+ if(!module->is_raster()) {
+ g_error("Can not export raster to non-raster extension.");
+ return;
+ }
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(doc);
+
+ file_listener fileout;
+ int data_read = execute(command, params, png_file, fileout);
+
+ bool success = false;
+ if (data_read > 0) {
+ std::string lfilename = Glib::filename_from_utf8(filenameArg);
+ success = fileout.toFile(lfilename);
+ }
+ if (success == false) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+ return;
+}
+
+/**
+ \return none
+ \brief This function uses an extension as an effect on a document.
+ \param module Extension to effect with.
+ \param doc Document to run through the effect.
+
+ This function is a little bit trickier than the previous two. It
+ needs two temporary files to get its work done. Both of these
+ files have random names created for them using the Glib::file_open_temp function
+ with the ink_ext_ prefix in the temporary directory. Like the other
+ functions, the temporary files are deleted at the end.
+
+ To save/load the two temporary documents (both are SVG) the internal
+ modules for SVG load and save are used. They are both used through
+ the module system function by passing their keys into the functions.
+
+ The command itself is built a little bit differently than in other
+ functions because the effect support selections. So on the command
+ line a list of all the ids that are selected is included. Currently,
+ this only works for a single selected object, but there will be more.
+ The command string is filled with the data, and then after the execution
+ it is freed.
+
+ The execute function is used at the core of this function
+ to execute the Script on the two SVG documents (actually only one
+ exists at the time, the other is created by that script). At that
+ point both should be full, and the second one is loaded.
+*/
+void Script::effect(Inkscape::Extension::Effect *module,
+ Inkscape::UI::View::View *doc,
+ ImplementationDocumentCache * docCache)
+{
+ if (docCache == nullptr) {
+ docCache = newDocCache(module, doc);
+ }
+ ScriptDocCache * dc = dynamic_cast<ScriptDocCache *>(docCache);
+ if (dc == nullptr) {
+ printf("TOO BAD TO LIVE!!!");
+ exit(1);
+ }
+ if (doc == nullptr)
+ {
+ g_warning("Script::effect: View not defined");
+ return;
+ }
+
+ SPDesktop *desktop = reinterpret_cast<SPDesktop *>(doc);
+ sp_namedview_document_from_window(desktop);
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ module->set_environment(desktop->getDocument());
+
+ parent_window = module->get_execution_env()->get_working_dialog();
+
+ if (module->no_doc) {
+ // this is a no-doc extension, e.g. a Help menu command;
+ // just run the command without any files, ignoring errors
+
+ Glib::ustring empty;
+ file_listener outfile;
+ execute(command, params, empty, outfile);
+
+ // Hack to allow for extension manager to reload extensions
+ // TODO: Find a better way to do this, e.g. implement an action and have extensions (or users)
+ // call that instead when there's a change that requires extensions to reload
+ if (!g_strcmp0(module->get_id(), "org.inkscape.extension.manager")) {
+ Inkscape::Extension::refresh_user_extensions();
+ build_menu(); // Rebuild main menubar.
+ }
+
+ return;
+ }
+
+ std::string tempfilename_out;
+ int tempfd_out = 0;
+ try {
+ tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX.svg");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+
+ if (desktop) {
+ Inkscape::Selection * selection = desktop->getSelection();
+ if (selection) {
+ params = selection->params;
+ module->paramListString(params);
+ selection->clear();
+ }
+ }
+
+ file_listener fileout;
+ int data_read = execute(command, params, dc->_filename, fileout);
+ fileout.toFile(tempfilename_out);
+
+ pump_events();
+
+ SPDocument * mydoc = nullptr;
+ if (data_read > 10) {
+ try {
+ mydoc = Inkscape::Extension::open(
+ Inkscape::Extension::db.get(SP_MODULE_KEY_INPUT_SVG),
+ tempfilename_out.c_str());
+ } catch (const Inkscape::Extension::Input::open_failed &e) {
+ g_warning("Extension returned output that could not be parsed: %s", e.what());
+ Gtk::MessageDialog warning(
+ _("The output from the extension could not be parsed."),
+ false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_OK, true);
+ warning.set_transient_for( parent_window ? *parent_window : *(INKSCAPE.active_desktop()->getToplevel()) );
+ warning.run();
+ }
+ } // data_read
+
+ pump_events();
+
+ // make sure we don't leak file descriptors from Glib::file_open_tmp
+ close(tempfd_out);
+
+ g_unlink(tempfilename_out.c_str());
+
+ if (mydoc) {
+ SPDocument* vd=doc->doc();
+ if (vd != nullptr)
+ {
+ mydoc->changeFilenameAndHrefs(vd->getDocumentFilename());
+
+ vd->emitReconstructionStart();
+ copy_doc(vd->getReprRoot(), mydoc->getReprRoot());
+ vd->emitReconstructionFinish();
+
+ // Getting the named view from the document generated by the extension
+ SPNamedView *nv = mydoc->getNamedView();
+
+ //Check if it has a default layer set up
+ SPObject *layer = nullptr;
+ if ( nv != nullptr)
+ {
+ SPDocument *document = desktop->doc();
+ if (document != nullptr) {
+ //If so, get that layer
+ if( nv->default_layer_id != 0 ) {
+ layer = document->getObjectById(g_quark_to_string(nv->default_layer_id));
+ }
+ desktop->showGrids(nv->grids_visible);
+ }
+ }
+
+ sp_namedview_update_layers_from_document(desktop);
+ //If that layer exists,
+ if (layer) {
+ //set the current layer
+ desktop->layerManager().setCurrentLayer(layer);
+ }
+ }
+ mydoc->release();
+ }
+
+ return;
+}
+
+
+
+/**
+ \brief A function to replace all the elements in an old document
+ by those from a new document.
+ document and repinserts them into an emptied old document.
+ \param oldroot The root node of the old (destination) document.
+ \param newroot The root node of the new (source) document.
+
+ This function first deletes all the root attributes in the old document followed
+ by copying all the root attributes from the new document to the old document.
+
+ It then deletes all the elements in the old document by
+ making two passes, the first to create a list of the old elements and
+ the second to actually delete them. This two pass approach removes issues
+ with the list being changed while parsing through it... lots of nasty bugs.
+
+ Then, it copies all the element in the new document into the old document.
+
+ Finally, it copies the attributes in namedview.
+*/
+void Script::copy_doc (Inkscape::XML::Node * oldroot, Inkscape::XML::Node * newroot)
+{
+ if ((oldroot == nullptr) ||(newroot == nullptr))
+ {
+ g_warning("Error on copy_doc: NULL pointer input.");
+ return;
+ }
+
+ // For copying attributes in root and in namedview
+ std::vector<gchar const *> attribs;
+
+ // Must explicitly copy root attributes. This must be done first since
+ // copying grid lines calls "SPGuide::set()" which needs to know the
+ // width, height, and viewBox of the root element.
+
+ // Make a list of all attributes of the old root node.
+ for (const auto & iter : oldroot->attributeList()) {
+ attribs.push_back(g_quark_to_string(iter.key));
+ }
+
+ // Delete the attributes of the old root node.
+ for (auto attrib : attribs) {
+ oldroot->removeAttribute(attrib);
+ }
+
+ // Set the new attributes.
+ for (const auto & iter : newroot->attributeList()) {
+ gchar const *name = g_quark_to_string(iter.key);
+ oldroot->setAttribute(name, newroot->attribute(name));
+ }
+
+ // Question: Why is the "sodipodi:namedview" special? Treating it as a normal
+ // element results in crashes.
+ // Seems to be a bug:
+ // http://inkscape.13.x6.nabble.com/Effect-that-modifies-the-document-properties-tt2822126.html
+
+ std::vector<Inkscape::XML::Node *> delete_list;
+
+ // Make list
+ for (Inkscape::XML::Node * child = oldroot->firstChild();
+ child != nullptr;
+ child = child->next()) {
+ if (!strcmp("sodipodi:namedview", child->name())) {
+ for (Inkscape::XML::Node * oldroot_namedview_child = child->firstChild();
+ oldroot_namedview_child != nullptr;
+ oldroot_namedview_child = oldroot_namedview_child->next()) {
+ delete_list.push_back(oldroot_namedview_child);
+ }
+ break;
+ }
+ }
+
+ // Unparent (delete)
+ for (auto & i : delete_list) {
+ sp_repr_unparent(i);
+ }
+ attribs.clear();
+ oldroot->mergeFrom(newroot, "id", true, true);
+}
+
+/** \brief This function checks the stderr file, and if it has data,
+ shows it in a warning dialog to the user
+ \param filename Filename of the stderr file
+*/
+void Script::checkStderr (const Glib::ustring &data,
+ Gtk::MessageType type,
+ const Glib::ustring &message)
+{
+ Gtk::MessageDialog warning(message, false, type, Gtk::BUTTONS_OK, true);
+ warning.set_resizable(true);
+ GtkWidget *dlg = GTK_WIDGET(warning.gobj());
+ if (parent_window) {
+ warning.set_transient_for(*parent_window);
+ } else {
+ sp_transientize(dlg);
+ }
+
+ auto vbox = warning.get_content_area();
+
+ /* Gtk::TextView * textview = new Gtk::TextView(Gtk::TextBuffer::create()); */
+ Gtk::TextView * textview = new Gtk::TextView();
+ textview->set_editable(false);
+ textview->set_wrap_mode(Gtk::WRAP_WORD);
+ textview->show();
+
+ textview->get_buffer()->set_text(data.c_str());
+
+ Gtk::ScrolledWindow * scrollwindow = new Gtk::ScrolledWindow();
+ scrollwindow->add(*textview);
+ scrollwindow->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
+ scrollwindow->set_shadow_type(Gtk::SHADOW_IN);
+ scrollwindow->show();
+ scrollwindow->set_size_request(0, 60);
+
+ vbox->pack_start(*scrollwindow, true, true, 5 /* fix these */);
+
+ warning.run();
+
+ delete textview;
+ delete scrollwindow;
+
+ return;
+}
+
+bool Script::cancelProcessing () {
+ _canceled = true;
+ if (_main_loop) {
+ _main_loop->quit();
+ }
+ Glib::spawn_close_pid(_pid);
+
+ return true;
+}
+
+
+/** \brief This is the core of the extension file as it actually does
+ the execution of the extension.
+ \param in_command The command to be executed
+ \param filein Filename coming in
+ \param fileout Filename of the out file
+ \return Number of bytes that were read into the output file.
+
+ The first thing that this function does is build the command to be
+ executed. This consists of the first string (in_command) and then
+ the filename for input (filein). This file is put on the command
+ line.
+
+ The next thing that this function does is open a pipe to the
+ command and get the file handle in the ppipe variable. It then
+ opens the output file with the output file handle. Both of these
+ operations are checked extensively for errors.
+
+ After both are opened, then the data is copied from the output
+ of the pipe into the file out using \a fread and \a fwrite. These two
+ functions are used because of their primitive nature - they make
+ no assumptions about the data. A buffer is used in the transfer,
+ but the output of \a fread is stored so the exact number of bytes
+ is handled gracefully.
+
+ At the very end (after the data has been copied) both of the files
+ are closed, and we return to what we were doing.
+*/
+int Script::execute (const std::list<std::string> &in_command,
+ const std::list<std::string> &in_params,
+ const Glib::ustring &filein,
+ file_listener &fileout)
+{
+ g_return_val_if_fail(!in_command.empty(), 0);
+
+ std::vector<std::string> argv;
+
+ bool interpreted = (in_command.size() == 2);
+ std::string program = in_command.front();
+ std::string script = interpreted ? in_command.back() : "";
+ std::string working_directory = "";
+
+ // We should always have an absolute path here:
+ // - For interpreted scripts, see Script::resolveInterpreterExecutable()
+ // - For "normal" scripts this should be done as part of the dependency checking, see Dependency::check()
+ if (!Glib::path_is_absolute(program)) {
+ g_critical("Script::execute(): Got unexpected relative path '%s'. Please report a bug.", program.c_str());
+ return 0;
+ }
+ argv.push_back(program);
+
+ if (interpreted) {
+ // On Windows, Python garbles Unicode command line parameters
+ // in an useless way. This means extensions fail when Inkscape
+ // is run from an Unicode directory.
+ // As a workaround, we set the working directory to the one
+ // containing the script.
+ working_directory = Glib::path_get_dirname(script);
+ script = Glib::path_get_basename(script);
+ argv.push_back(script);
+ }
+
+ // assemble the rest of argv
+ std::copy(in_params.begin(), in_params.end(), std::back_inserter(argv));
+ if (!filein.empty()) {
+ auto filein_native = Glib::filename_from_utf8(filein);
+ if (!Glib::path_is_absolute(filein_native))
+ filein_native = Glib::build_filename(Glib::get_current_dir(), filein_native);
+ argv.push_back(filein_native);
+ }
+
+ //for(int i=0;i<argv.size(); ++i){printf("%s ",argv[i].c_str());}printf("\n");
+
+ int stdout_pipe, stderr_pipe;
+
+ try {
+ Glib::spawn_async_with_pipes(working_directory, // working directory
+ argv, // arg v
+ static_cast<Glib::SpawnFlags>(0), // no flags
+ sigc::slot<void>(),
+ &_pid, // Pid
+ nullptr, // STDIN
+ &stdout_pipe, // STDOUT
+ &stderr_pipe); // STDERR
+ } catch (Glib::Error &e) {
+ g_critical("Script::execute(): failed to execute program '%s'.\n\tReason: %s", program.c_str(), e.what().data());
+ return 0;
+ }
+
+ // Create a new MainContext for the loop so that the original context sources are not run here,
+ // this enforces that only the file_listeners should be read in this new MainLoop
+ Glib::RefPtr<Glib::MainContext> _main_context = Glib::MainContext::create();
+ _main_loop = Glib::MainLoop::create(_main_context, false);
+
+ file_listener fileerr;
+ fileout.init(stdout_pipe, _main_loop);
+ fileerr.init(stderr_pipe, _main_loop);
+
+ _canceled = false;
+ _main_loop->run();
+
+ // Ensure all the data is out of the pipe
+ while (!fileout.isDead()) {
+ fileout.read(Glib::IO_IN);
+ }
+ while (!fileerr.isDead()) {
+ fileerr.read(Glib::IO_IN);
+ }
+
+ _main_loop.reset();
+
+ if (_canceled) {
+ // std::cout << "Script Canceled" << std::endl;
+ return 0;
+ }
+
+ Glib::ustring stderr_data = fileerr.string();
+ if (stderr_data.length() != 0 &&
+ INKSCAPE.use_gui()
+ ) {
+ checkStderr(stderr_data, Gtk::MESSAGE_INFO,
+ _("Inkscape has received additional data from the script executed. "
+ "The script did not return an error, but this may indicate the results will not be as expected."));
+ }
+
+ Glib::ustring stdout_data = fileout.string();
+ if (stdout_data.length() == 0) {
+ return 0;
+ }
+
+ // std::cout << "Finishing Execution." << std::endl;
+ return stdout_data.length();
+}
+
+
+void Script::file_listener::init(int fd, Glib::RefPtr<Glib::MainLoop> main) {
+ _channel = Glib::IOChannel::create_from_fd(fd);
+ _channel->set_close_on_unref(true);
+ _channel->set_encoding();
+ _conn = main->get_context()->signal_io().connect(sigc::mem_fun(*this, &file_listener::read), _channel, Glib::IO_IN | Glib::IO_HUP | Glib::IO_ERR);
+ _main_loop = main;
+
+ return;
+}
+
+bool Script::file_listener::read(Glib::IOCondition condition) {
+ if (condition != Glib::IO_IN) {
+ _main_loop->quit();
+ return false;
+ }
+
+ Glib::IOStatus status;
+ Glib::ustring out;
+ status = _channel->read_line(out);
+ _string += out;
+
+ if (status != Glib::IO_STATUS_NORMAL) {
+ _main_loop->quit();
+ _dead = true;
+ return false;
+ }
+
+ return true;
+}
+
+bool Script::file_listener::toFile(const Glib::ustring &name) {
+ return toFile(Glib::filename_from_utf8(name));
+}
+
+bool Script::file_listener::toFile(const std::string &name) {
+ try {
+ Glib::RefPtr<Glib::IOChannel> stdout_file = Glib::IOChannel::create_from_file(name, "w");
+ stdout_file->set_encoding();
+ stdout_file->write(_string);
+ } catch (Glib::FileError &e) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8 :
diff --git a/src/extension/implementation/script.h b/src/extension/implementation/script.h
new file mode 100644
index 0000000..bc865f4
--- /dev/null
+++ b/src/extension/implementation/script.h
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Code for handling extensions (i.e., scripts)
+ *
+ * Authors:
+ * Bryce Harrington <bryce@osdl.org>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN
+#define INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN
+
+#include "implementation.h"
+#include <gtkmm/enums.h>
+#include <gtkmm/window.h>
+#include <glibmm/main.h>
+#include <glibmm/spawn.h>
+#include <glibmm/fileutils.h>
+
+namespace Inkscape {
+namespace XML {
+class Node;
+} // namespace XML
+
+namespace Extension {
+namespace Implementation {
+
+/**
+ * Utility class used for loading and launching script extensions
+ */
+class Script : public Implementation {
+public:
+
+ Script();
+ ~Script() override;
+ bool load(Inkscape::Extension::Extension *module) override;
+ void unload(Inkscape::Extension::Extension *module) override;
+ bool check(Inkscape::Extension::Extension *module) override;
+
+ ImplementationDocumentCache * newDocCache(Inkscape::Extension::Extension * ext, Inkscape::UI::View::View * view) override;
+
+ Gtk::Widget *prefs_input(Inkscape::Extension::Input *module, gchar const *filename) override;
+ SPDocument *open(Inkscape::Extension::Input *module, gchar const *filename) override;
+ Gtk::Widget *prefs_output(Inkscape::Extension::Output *module) override;
+ void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override;
+ void export_raster(Inkscape::Extension::Output *module,
+ const SPDocument *doc, std::string const &png_file, gchar const *filename) override;
+ void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *doc, ImplementationDocumentCache * docCache) override;
+ bool cancelProcessing () override;
+
+private:
+ bool _canceled;
+ Glib::Pid _pid;
+ Glib::RefPtr<Glib::MainLoop> _main_loop;
+
+ /**
+ * The command that has been derived from
+ * the configuration file with appropriate directories
+ */
+ std::list<std::string> command;
+
+ /**
+ * This is the extension that will be used
+ * as the helper to read in or write out the
+ * data
+ */
+ Glib::ustring helper_extension;
+
+ /**
+ * The window which should be considered as "parent window" of the script execution,
+ * e.g. when showin warning messages
+ *
+ * If set to NULL the main window of the currently active document is used.
+ */
+ Gtk::Window *parent_window;
+
+ void copy_doc(Inkscape::XML::Node * olddoc, Inkscape::XML::Node * newdoc);
+ void checkStderr (Glib::ustring const& filename, Gtk::MessageType type, Glib::ustring const& message);
+
+ class file_listener {
+ Glib::ustring _string;
+ sigc::connection _conn;
+ Glib::RefPtr<Glib::IOChannel> _channel;
+ Glib::RefPtr<Glib::MainLoop> _main_loop;
+ bool _dead;
+
+ public:
+ file_listener () : _dead(false) { };
+ virtual ~file_listener () {
+ _conn.disconnect();
+ };
+
+ bool isDead () { return _dead; }
+ void init(int fd, Glib::RefPtr<Glib::MainLoop> main);
+ bool read(Glib::IOCondition condition);
+ Glib::ustring string () { return _string; };
+ bool toFile(const Glib::ustring &name);
+ bool toFile(const std::string &name);
+ };
+
+ int execute (const std::list<std::string> &in_command,
+ const std::list<std::string> &in_params,
+ const Glib::ustring &filein,
+ file_listener &fileout);
+
+ void pump_events();
+
+ /** \brief A definition of an interpreter, which can be specified
+ in the INX file, but we need to know what to call */
+ struct interpreter_t {
+ std::string prefstring; /**< The preferences key that can override the default */
+ std::vector<std::string> defaultvals; /**< The default values to check if the preferences are wrong */
+ };
+ static const std::map<std::string, interpreter_t> interpreterTab;
+
+ std::string resolveInterpreterExecutable(const Glib::ustring &interpNameArg);
+
+}; // class Script
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // INKSCAPE_EXTENSION_IMPEMENTATION_SCRIPT_H_SEEN
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/implementation/xslt.cpp b/src/extension/implementation/xslt.cpp
new file mode 100644
index 0000000..dced1dd
--- /dev/null
+++ b/src/extension/implementation/xslt.cpp
@@ -0,0 +1,253 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Code for handling XSLT extensions.
+ */
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "xslt.h"
+
+#include <unistd.h>
+#include <cstring>
+
+#include <glibmm/fileutils.h>
+#include <libxslt/transform.h>
+#include <libxslt/xsltutils.h>
+
+#include "document.h"
+#include "file.h"
+
+#include "extension/extension.h"
+#include "extension/output.h"
+#include "extension/input.h"
+
+#include "io/resource.h"
+
+#include "xml/node.h"
+#include "xml/repr.h"
+
+#include <clocale>
+
+Inkscape::XML::Document * sp_repr_do_read (xmlDocPtr doc, const gchar * default_ns);
+
+/* Namespaces */
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+/* Real functions */
+/**
+ \return A XSLT object
+ \brief This function creates a XSLT object and sets up the
+ variables.
+
+*/
+XSLT::XSLT() :
+ Implementation(),
+ _filename(""),
+ _parsedDoc(nullptr),
+ _stylesheet(nullptr)
+{
+}
+
+bool XSLT::check(Inkscape::Extension::Extension *module)
+{
+ if (load(module)) {
+ unload(module);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool XSLT::load(Inkscape::Extension::Extension *module)
+{
+ if (module->loaded()) { return true; }
+
+ Inkscape::XML::Node *child_repr = module->get_repr()->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "xslt")) {
+ child_repr = child_repr->firstChild();
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "file")) {
+ // TODO: we already parse xslt files as dependencies in extension.cpp
+ // can can we optimize this to be less indirect?
+ const char *filename = child_repr->firstChild()->content();
+ _filename = module->get_dependency_location(filename);
+ }
+ child_repr = child_repr->next();
+ }
+
+ break;
+ }
+ child_repr = child_repr->next();
+ }
+
+ _parsedDoc = xmlParseFile(_filename.c_str());
+ if (_parsedDoc == nullptr) { return false; }
+
+ _stylesheet = xsltParseStylesheetDoc(_parsedDoc);
+
+ return true;
+}
+
+void XSLT::unload(Inkscape::Extension::Extension *module)
+{
+ if (!module->loaded()) { return; }
+ xsltFreeStylesheet(_stylesheet);
+ // No need to use xmlfreedoc(_parsedDoc), it's handled by xsltFreeStylesheet(_stylesheet);
+ return;
+}
+
+SPDocument * XSLT::open(Inkscape::Extension::Input */*module*/,
+ gchar const *filename)
+{
+ xmlDocPtr filein = xmlParseFile(filename);
+ if (filein == nullptr) { return nullptr; }
+
+ const char * params[1];
+ params[0] = nullptr;
+ char *oldlocale = g_strdup(std::setlocale(LC_NUMERIC, nullptr));
+ std::setlocale(LC_NUMERIC, "C");
+
+ xmlDocPtr result = xsltApplyStylesheet(_stylesheet, filein, params);
+ xmlFreeDoc(filein);
+
+ Inkscape::XML::Document * rdoc = sp_repr_do_read( result, SP_SVG_NS_URI);
+ xmlFreeDoc(result);
+ std::setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ if (rdoc == nullptr) {
+ return nullptr;
+ }
+
+ if (strcmp(rdoc->root()->name(), "svg:svg") != 0) {
+ return nullptr;
+ }
+
+ gchar * base = nullptr;
+ gchar * name = nullptr;
+ gchar * s = nullptr, * p = nullptr;
+ s = g_strdup(filename);
+ p = strrchr(s, '/');
+ if (p) {
+ name = g_strdup(p + 1);
+ p[1] = '\0';
+ base = g_strdup(s);
+ } else {
+ base = nullptr;
+ name = g_strdup(filename);
+ }
+ g_free(s);
+
+ SPDocument * doc = SPDocument::createDoc(rdoc, filename, base, name, true, nullptr);
+
+ g_free(base); g_free(name);
+
+ return doc;
+}
+
+void XSLT::save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename)
+{
+ /* TODO: Should we assume filename to be in utf8 or to be a raw filename?
+ * See JavaFXOutput::save for discussion.
+ *
+ * From JavaFXOutput::save (now removed):
+ * ---
+ * N.B. The name `filename_utf8' represents the fact that we want it to be in utf8; whereas in
+ * fact we know that some callers of Extension::save pass something in the filesystem's
+ * encoding, while others do g_filename_to_utf8 before calling.
+ *
+ * In terms of safety, it's best to make all callers pass actual filenames, since in general
+ * one can't round-trip from filename to utf8 back to the same filename. Whereas the argument
+ * for passing utf8 filenames is one of convenience: we often want to pass to g_warning or
+ * store as a string (rather than a byte stream) in XML, or the like. */
+ g_return_if_fail(doc != nullptr);
+ g_return_if_fail(filename != nullptr);
+
+ Inkscape::XML::Node *repr = doc->getReprRoot();
+
+ std::string tempfilename_out;
+ int tempfd_out = 0;
+ try {
+ tempfd_out = Glib::file_open_tmp(tempfilename_out, "ink_ext_XXXXXX");
+ } catch (...) {
+ /// \todo Popup dialog here
+ return;
+ }
+
+ if (!sp_repr_save_rebased_file(repr->document(), tempfilename_out.c_str(), SP_SVG_NS_URI,
+ doc->getDocumentBase(), filename)) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ xmlDocPtr svgdoc = xmlParseFile(tempfilename_out.c_str());
+ close(tempfd_out);
+ if (svgdoc == nullptr) {
+ return;
+ }
+
+ std::list<std::string> params;
+ module->paramListString(params);
+ const int max_parameters = params.size() * 2;
+ const char * xslt_params[max_parameters+1] ;
+
+ int count = 0;
+ for(auto & param : params) {
+ std::size_t pos = param.find("=");
+ std::ostringstream parameter;
+ std::ostringstream value;
+ parameter << param.substr(2,pos-2);
+ value << param.substr(pos+1);
+ xslt_params[count++] = g_strdup_printf("%s", parameter.str().c_str());
+ xslt_params[count++] = g_strdup_printf("'%s'", value.str().c_str());
+ }
+ xslt_params[count] = nullptr;
+
+ // workaround for inbox#2208
+ char *oldlocale = g_strdup(std::setlocale(LC_NUMERIC, nullptr));
+ std::setlocale(LC_NUMERIC, "C");
+ xmlDocPtr newdoc = xsltApplyStylesheet(_stylesheet, svgdoc, xslt_params);
+ //xmlSaveFile(filename, newdoc);
+ int success = xsltSaveResultToFilename(filename, newdoc, _stylesheet, 0);
+ std::setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ xmlFreeDoc(newdoc);
+ xmlFreeDoc(svgdoc);
+
+ xsltCleanupGlobals();
+ xmlCleanupParser();
+
+ if (success < 1) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ return;
+}
+
+
+} /* Implementation */
+} /* module */
+} /* Inkscape */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/implementation/xslt.h b/src/extension/implementation/xslt.h
new file mode 100644
index 0000000..745d7f5
--- /dev/null
+++ b/src/extension/implementation/xslt.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Code for handling XSLT extensions
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__
+#define __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__
+
+#include "implementation.h"
+
+#include "libxml/tree.h"
+#include "libxslt/xslt.h"
+#include "libxslt/xsltInternals.h"
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+}
+
+
+namespace Inkscape {
+namespace Extension {
+namespace Implementation {
+
+class XSLT : public Implementation {
+private:
+ std::string _filename;
+ xmlDocPtr _parsedDoc;
+ xsltStylesheetPtr _stylesheet;
+
+public:
+ XSLT ();
+
+ bool load(Inkscape::Extension::Extension *module) override;
+ void unload(Inkscape::Extension::Extension *module) override;
+
+ bool check(Inkscape::Extension::Extension *module) override;
+
+ SPDocument *open(Inkscape::Extension::Input *module,
+ gchar const *filename) override;
+ void save(Inkscape::Extension::Output *module, SPDocument *doc, gchar const *filename) override;
+};
+
+} /* Inkscape */
+} /* Extension */
+} /* Implementation */
+#endif /* __INKSCAPE_EXTENSION_IMPEMENTATION_XSLT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/init.cpp b/src/extension/init.cpp
new file mode 100644
index 0000000..5e291c1
--- /dev/null
+++ b/src/extension/init.cpp
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is what gets executed to initialize all of the modules. For
+ * the internal modules this involves executing their initialization
+ * functions, for external ones it involves reading their .spmodule
+ * files and bringing them into Sodipodi.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+# include "internal/pdfinput/pdf-input.h"
+#endif
+
+#include "path-prefix.h"
+
+#include "inkscape.h"
+
+#include <glibmm/fileutils.h>
+#include <glibmm/i18n.h>
+#include <glibmm/ustring.h>
+
+#include "system.h"
+#include "db.h"
+#include "internal/svgz.h"
+# include "internal/emf-inout.h"
+# include "internal/emf-print.h"
+# include "internal/wmf-inout.h"
+# include "internal/wmf-print.h"
+
+#include <cairo.h>
+#ifdef CAIRO_HAS_PDF_SURFACE
+# include "internal/cairo-renderer-pdf-out.h"
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+# include "internal/cairo-ps-out.h"
+#endif
+#include "internal/png-output.h"
+#include "internal/pov-out.h"
+#include "internal/odf.h"
+#include "internal/latex-pstricks-out.h"
+#include "internal/latex-pstricks.h"
+#include "internal/gdkpixbuf-input.h"
+#include "internal/bluredge.h"
+#include "internal/gimpgrad.h"
+#include "internal/grid.h"
+#ifdef WITH_LIBWPG
+#include "internal/wpg-input.h"
+#endif
+#ifdef WITH_LIBVISIO
+#include "internal/vsd-input.h"
+#endif
+#ifdef WITH_LIBCDR
+#include "internal/cdr-input.h"
+#endif
+#include "preferences.h"
+#include "io/sys.h"
+#include "io/resource.h"
+
+#ifdef WITH_MAGICK
+#include <Magick++.h>
+#include "internal/bitmap/adaptiveThreshold.h"
+#include "internal/bitmap/addNoise.h"
+#include "internal/bitmap/blur.h"
+#include "internal/bitmap/channel.h"
+#include "internal/bitmap/charcoal.h"
+#include "internal/bitmap/colorize.h"
+#include "internal/bitmap/contrast.h"
+#include "internal/bitmap/crop.h"
+#include "internal/bitmap/cycleColormap.h"
+#include "internal/bitmap/despeckle.h"
+#include "internal/bitmap/edge.h"
+#include "internal/bitmap/emboss.h"
+#include "internal/bitmap/enhance.h"
+#include "internal/bitmap/equalize.h"
+#include "internal/bitmap/gaussianBlur.h"
+#include "internal/bitmap/implode.h"
+#include "internal/bitmap/level.h"
+#include "internal/bitmap/levelChannel.h"
+#include "internal/bitmap/medianFilter.h"
+#include "internal/bitmap/modulate.h"
+#include "internal/bitmap/negate.h"
+#include "internal/bitmap/normalize.h"
+#include "internal/bitmap/oilPaint.h"
+#include "internal/bitmap/opacity.h"
+#include "internal/bitmap/raise.h"
+#include "internal/bitmap/reduceNoise.h"
+#include "internal/bitmap/sample.h"
+#include "internal/bitmap/shade.h"
+#include "internal/bitmap/sharpen.h"
+#include "internal/bitmap/solarize.h"
+#include "internal/bitmap/spread.h"
+#include "internal/bitmap/swirl.h"
+//#include "internal/bitmap/threshold.h"
+#include "internal/bitmap/unsharpmask.h"
+#include "internal/bitmap/wave.h"
+#endif /* WITH_MAGICK */
+
+#include "internal/filter/filter.h"
+
+#include "init.h"
+
+using namespace Inkscape::IO::Resource;
+
+namespace Inkscape {
+namespace Extension {
+
+/** This is the extension that all files are that are pulled from
+ the extension directory and parsed */
+#define SP_MODULE_EXTENSION "inx"
+
+static void check_extensions();
+
+/**
+ * \return none
+ * \brief Examines the given string preference and checks to see
+ * that at least one of the registered extensions matches
+ * it. If not, a default is assigned.
+ * \param pref_path Preference path to update
+ * \param pref_default Default string to set
+ * \param extension_family List of extensions to search
+ */
+static void
+update_pref(Glib::ustring const &pref_path,
+ gchar const *pref_default)
+{
+ Glib::ustring pref = Inkscape::Preferences::get()->getString(pref_path);
+ if (!Inkscape::Extension::db.get( pref.data() ) /*missing*/) {
+ Inkscape::Preferences::get()->setString(pref_path, pref_default);
+ }
+}
+
+// A list of user extensions loaded, used for refreshing
+static std::vector<Glib::ustring> user_extensions;
+
+/**
+ * Invokes the init routines for internal modules.
+ *
+ * This should be a list of all the internal modules that need to initialized. This is just a
+ * convenient place to put them.
+ */
+void
+init()
+{
+ /* TODO: Change to Internal */
+ Internal::Svg::init();
+ Internal::Svgz::init();
+
+#ifdef CAIRO_HAS_PDF_SURFACE
+ Internal::CairoRendererPdfOutput::init();
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+ Internal::CairoPsOutput::init();
+ Internal::CairoEpsOutput::init();
+#endif
+#ifdef HAVE_POPPLER
+ Internal::PdfInput::init();
+#endif
+ Internal::PrintEmf::init();
+ Internal::Emf::init();
+ Internal::PrintWmf::init();
+ Internal::Wmf::init();
+ Internal::PngOutput::init();
+ Internal::PovOutput::init();
+ Internal::OdfOutput::init();
+ Internal::PrintLatex::init();
+ Internal::LatexOutput::init();
+#ifdef WITH_LIBWPG
+ Internal::WpgInput::init();
+#endif
+#ifdef WITH_LIBVISIO
+ Internal::VsdInput::init();
+#endif
+#ifdef WITH_LIBCDR
+ Internal::CdrInput::init();
+#endif
+
+ /* Effects */
+ Internal::BlurEdge::init();
+ Internal::GimpGrad::init();
+ Internal::Grid::init();
+
+ /* Raster Effects */
+#ifdef WITH_MAGICK
+ Magick::InitializeMagick(NULL);
+
+ Internal::Bitmap::AdaptiveThreshold::init();
+ Internal::Bitmap::AddNoise::init();
+ Internal::Bitmap::Blur::init();
+ Internal::Bitmap::Channel::init();
+ Internal::Bitmap::Charcoal::init();
+ Internal::Bitmap::Colorize::init();
+ Internal::Bitmap::Contrast::init();
+ Internal::Bitmap::Crop::init();
+ Internal::Bitmap::CycleColormap::init();
+ Internal::Bitmap::Edge::init();
+ Internal::Bitmap::Despeckle::init();
+ Internal::Bitmap::Emboss::init();
+ Internal::Bitmap::Enhance::init();
+ Internal::Bitmap::Equalize::init();
+ Internal::Bitmap::GaussianBlur::init();
+ Internal::Bitmap::Implode::init();
+ Internal::Bitmap::Level::init();
+ Internal::Bitmap::LevelChannel::init();
+ Internal::Bitmap::MedianFilter::init();
+ Internal::Bitmap::Modulate::init();
+ Internal::Bitmap::Negate::init();
+ Internal::Bitmap::Normalize::init();
+ Internal::Bitmap::OilPaint::init();
+ Internal::Bitmap::Opacity::init();
+ Internal::Bitmap::Raise::init();
+ Internal::Bitmap::ReduceNoise::init();
+ Internal::Bitmap::Sample::init();
+ Internal::Bitmap::Shade::init();
+ Internal::Bitmap::Sharpen::init();
+ Internal::Bitmap::Solarize::init();
+ Internal::Bitmap::Spread::init();
+ Internal::Bitmap::Swirl::init();
+ //Internal::Bitmap::Threshold::init();
+ Internal::Bitmap::Unsharpmask::init();
+ Internal::Bitmap::Wave::init();
+#endif /* WITH_MAGICK */
+
+ Internal::Filter::Filter::filters_all();
+
+ // User extensions first so they can over-ride
+ load_user_extensions();
+
+ for(auto &filename: get_filenames(SYSTEM, EXTENSIONS, {SP_MODULE_EXTENSION})) {
+ build_from_file(filename.c_str());
+ }
+
+ /* this is at the very end because it has several catch-alls
+ * that are possibly over-ridden by other extensions (such as
+ * svgz)
+ */
+ Internal::GdkpixbufInput::init();
+
+ /* now we need to check and make sure everyone is happy */
+ check_extensions();
+
+ /* This is a hack to deal with updating saved outdated module
+ * names in the prefs...
+ */
+ update_pref("/dialogs/save_as/default",
+ SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE
+ // Inkscape::Extension::db.get_output_list()
+ );
+}
+
+void
+load_user_extensions()
+{
+ // There's no need to ask for SYSTEM extensions, just ask for user extensions.
+ for(auto &filename: get_filenames(USER, EXTENSIONS, {SP_MODULE_EXTENSION})) {
+ bool exist = false;
+ for(auto &filename2: user_extensions) {
+ if (filename == filename2) {
+ exist = true;
+ break;
+ }
+ }
+ if (!exist) {
+ build_from_file(filename.c_str());
+ user_extensions.push_back(filename);
+ }
+ }
+}
+
+/**
+ * Refresh user extensions
+ *
+ * Remember to call check_extensions() once completed.
+ */
+void
+refresh_user_extensions()
+{
+ load_user_extensions();
+ check_extensions();
+}
+
+
+static void
+check_extensions_internal(Extension *in_plug, gpointer in_data)
+{
+ int *count = (int *)in_data;
+
+ if (in_plug == nullptr) return;
+ if (!in_plug->deactivated() && !in_plug->check()) {
+ in_plug->deactivate();
+ (*count)++;
+ }
+}
+
+static void check_extensions()
+{
+ int count = 1;
+
+ Inkscape::Extension::Extension::error_file_open();
+ while (count != 0) {
+ count = 0;
+ db.foreach(check_extensions_internal, (gpointer)&count);
+ }
+ Inkscape::Extension::Extension::error_file_close();
+}
+
+} } /* namespace Inkscape::Extension */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/init.h b/src/extension/init.h
new file mode 100644
index 0000000..71de5ac
--- /dev/null
+++ b/src/extension/init.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is what gets executed to initialize all of the modules. For
+ * the internal modules this invovles executing their initialization
+ * functions, for external ones it involves reading their .spmodule
+ * files and bringing them into Sodipodi.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_INIT_H__
+#define INKSCAPE_EXTENSION_INIT_H__
+
+namespace Inkscape {
+namespace Extension {
+
+void init ();
+void load_user_extensions();
+void refresh_user_extensions();
+} } /* namespace Inkscape::Extension */
+
+#endif /* INKSCAPE_EXTENSION_INIT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/input.cpp b/src/extension/input.cpp
new file mode 100644
index 0000000..b82d44a
--- /dev/null
+++ b/src/extension/input.cpp
@@ -0,0 +1,254 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "input.h"
+
+#include "timer.h"
+
+#include "implementation/implementation.h"
+
+#include "prefdialog/prefdialog.h"
+
+#include "xml/repr.h"
+
+
+/* Inkscape::Extension::Input */
+
+namespace Inkscape {
+namespace Extension {
+
+/**
+ \return None
+ \brief Builds a SPModuleInput object from a XML description
+ \param module The module to be initialized
+ \param repr The XML description in a Inkscape::XML::Node tree
+
+ Okay, so you want to build a SPModuleInput object.
+
+ This function first takes and does the build of the parent class,
+ which is SPModule. Then, it looks for the <input> section of the
+ XML description. Under there should be several fields which
+ describe the input module to excruciating detail. Those are parsed,
+ copied, and put into the structure that is passed in as module.
+ Overall, there are many levels of indentation, just to handle the
+ levels of indentation in the XML file.
+*/
+Input::Input (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory)
+ : Extension(in_repr, in_imp, base_directory)
+{
+ mimetype = nullptr;
+ extension = nullptr;
+ filetypename = nullptr;
+ filetypetooltip = nullptr;
+
+ if (repr != nullptr) {
+ Inkscape::XML::Node * child_repr;
+
+ child_repr = repr->firstChild();
+
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "input")) {
+ child_repr = child_repr->firstChild();
+ while (child_repr != nullptr) {
+ char const * chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (chname[0] == '_') /* Allow _ for translation of tags */
+ chname++;
+ if (!strcmp(chname, "extension")) {
+ g_free (extension);
+ extension = g_strdup(child_repr->firstChild()->content());
+ }
+ if (!strcmp(chname, "mimetype")) {
+ g_free (mimetype);
+ mimetype = g_strdup(child_repr->firstChild()->content());
+ }
+ if (!strcmp(chname, "filetypename")) {
+ g_free (filetypename);
+ filetypename = g_strdup(child_repr->firstChild()->content());
+ }
+ if (!strcmp(chname, "filetypetooltip")) {
+ g_free (filetypetooltip);
+ filetypetooltip = g_strdup(child_repr->firstChild()->content());
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ break;
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ }
+
+ return;
+}
+
+/**
+ \return None
+ \brief Destroys an Input extension
+*/
+Input::~Input ()
+{
+ g_free(mimetype);
+ g_free(extension);
+ g_free(filetypename);
+ g_free(filetypetooltip);
+ return;
+}
+
+/**
+ \return Whether this extension checks out
+ \brief Validate this extension
+
+ This function checks to make sure that the input extension has
+ a filename extension and a MIME type. Then it calls the parent
+ class' check function which also checks out the implementation.
+*/
+bool
+Input::check ()
+{
+ if (extension == nullptr)
+ return FALSE;
+ if (mimetype == nullptr)
+ return FALSE;
+
+ return Extension::check();
+}
+
+/**
+ \return A new document
+ \brief This function creates a document from a file
+ \param uri The filename to create the document from
+
+ This function acts as the first step in creating a new document
+ from a file. The first thing that this does is make sure that the
+ file actually exists. If it doesn't, a NULL is returned. If the
+ file exits, then it is opened using the implementation of this extension.
+*/
+SPDocument *
+Input::open (const gchar *uri)
+{
+ if (!loaded()) {
+ set_state(Extension::STATE_LOADED);
+ }
+ if (!loaded()) {
+ return nullptr;
+ }
+ timer->touch();
+
+ SPDocument *const doc = imp->open(this, uri);
+
+ return doc;
+}
+
+/**
+ \return IETF mime-type for the extension
+ \brief Get the mime-type that describes this extension
+*/
+gchar *
+Input::get_mimetype()
+{
+ return mimetype;
+}
+
+/**
+ \return Filename extension for the extension
+ \brief Get the filename extension for this extension
+*/
+gchar *
+Input::get_extension()
+{
+ return extension;
+}
+
+/**
+ \return The name of the filetype supported
+ \brief Get the name of the filetype supported
+*/
+const char *
+Input::get_filetypename(bool translated)
+{
+ const char *name;
+
+ if (filetypename)
+ name = filetypename;
+ else
+ name = get_name();
+
+ if (name && translated && filetypename) {
+ return get_translation(name);
+ } else {
+ return name;
+ }
+}
+
+/**
+ \return Tooltip giving more information on the filetype
+ \brief Get the tooltip for more information on the filetype
+*/
+const char *
+Input::get_filetypetooltip(bool translated)
+{
+ if (filetypetooltip && translated) {
+ return get_translation(filetypetooltip);
+ } else {
+ return filetypetooltip;
+ }
+}
+
+/**
+ \return A dialog to get settings for this extension
+ \brief Create a dialog for preference for this extension
+
+ Calls the implementation to get the preferences.
+*/
+bool
+Input::prefs (const gchar *uri)
+{
+ if (!loaded()) {
+ set_state(Extension::STATE_LOADED);
+ }
+ if (!loaded()) {
+ return false;
+ }
+
+ Gtk::Widget * controls;
+ controls = imp->prefs_input(this, uri);
+ if (controls == nullptr) {
+ // std::cout << "No preferences for Input" << std::endl;
+ return true;
+ }
+
+ Glib::ustring name = this->get_name();
+ PrefDialog *dialog = new PrefDialog(name, controls);
+ int response = dialog->run();
+ dialog->hide();
+
+ delete dialog;
+
+ return (response == Gtk::RESPONSE_OK);
+}
+
+} } /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/input.h b/src/extension/input.h
new file mode 100644
index 0000000..64cce94
--- /dev/null
+++ b/src/extension/input.h
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#ifndef INKSCAPE_EXTENSION_INPUT_H__
+#define INKSCAPE_EXTENSION_INPUT_H__
+
+#include <exception>
+#include <glib.h>
+#include "extension.h"
+
+class SPDocument;
+
+namespace Inkscape {
+namespace Extension {
+
+class Input : public Extension {
+ gchar *mimetype; /**< What is the mime type this inputs? */
+ gchar *extension; /**< The extension of the input files */
+ gchar *filetypename; /**< A userfriendly name for the file type */
+ gchar *filetypetooltip; /**< A more detailed description of the filetype */
+
+public:
+ struct open_failed : public std::exception {
+ ~open_failed() noexcept override = default;
+ const char *what() const noexcept override { return "Open failed"; }
+ };
+ struct no_extension_found : public std::exception {
+ ~no_extension_found() noexcept override = default;
+ const char *what() const noexcept override { return "No suitable input extension found"; }
+ };
+ struct open_cancelled : public std::exception {
+ ~open_cancelled() noexcept override = default;
+ const char *what() const noexcept override { return "Open was cancelled"; }
+ };
+
+ Input(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory);
+ ~Input() override;
+
+ bool check() override;
+
+ SPDocument * open (gchar const *uri);
+ gchar * get_mimetype ();
+ gchar * get_extension ();
+ const char * get_filetypename (bool translated=false);
+ const char * get_filetypetooltip (bool translated=false);
+ bool prefs (gchar const *uri);
+};
+
+} } /* namespace Inkscape, Extension */
+#endif /* INKSCAPE_EXTENSION_INPUT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/bitmap/adaptiveThreshold.cpp b/src/extension/internal/bitmap/adaptiveThreshold.cpp
new file mode 100644
index 0000000..7c85d84
--- /dev/null
+++ b/src/extension/internal/bitmap/adaptiveThreshold.cpp
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "adaptiveThreshold.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+AdaptiveThreshold::applyEffect(Magick::Image *image) {
+ image->adaptiveThreshold(_width, _height);
+}
+
+void
+AdaptiveThreshold::refreshParameters(Inkscape::Extension::Effect *module) {
+ _width = module->get_param_int("width");
+ _height = module->get_param_int("height");
+ _offset = module->get_param_int("offset");
+}
+
+#include "../clear-n_.h"
+
+void
+AdaptiveThreshold::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Adaptive Threshold") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.adaptiveThreshold</id>\n"
+ "<param name=\"width\" gui-text=\"" N_("Width:") "\" type=\"int\" min=\"-100\" max=\"100\">5</param>\n"
+ "<param name=\"height\" gui-text=\"" N_("Height:") "\" type=\"int\" min=\"-100\" max=\"100\">5</param>\n"
+ "<param name=\"offset\" gui-text=\"" N_("Offset:") "\" type=\"int\" min=\"0\" max=\"100\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Apply adaptive thresholding to selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new AdaptiveThreshold());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/adaptiveThreshold.h b/src/extension/internal/bitmap/adaptiveThreshold.h
new file mode 100644
index 0000000..066f13b
--- /dev/null
+++ b/src/extension/internal/bitmap/adaptiveThreshold.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class AdaptiveThreshold : public ImageMagick
+{
+private:
+ unsigned int _width;
+ unsigned int _height;
+ unsigned _offset;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/addNoise.cpp b/src/extension/internal/bitmap/addNoise.cpp
new file mode 100644
index 0000000..0bedb3c
--- /dev/null
+++ b/src/extension/internal/bitmap/addNoise.cpp
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "addNoise.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+AddNoise::applyEffect(Magick::Image *image) {
+ Magick::NoiseType noiseType = Magick::UniformNoise;
+ if (!strcmp(_noiseTypeName, "Uniform Noise")) noiseType = Magick::UniformNoise;
+ else if (!strcmp(_noiseTypeName, "Gaussian Noise")) noiseType = Magick::GaussianNoise;
+ else if (!strcmp(_noiseTypeName, "Multiplicative Gaussian Noise")) noiseType = Magick::MultiplicativeGaussianNoise;
+ else if (!strcmp(_noiseTypeName, "Impulse Noise")) noiseType = Magick::ImpulseNoise;
+ else if (!strcmp(_noiseTypeName, "Laplacian Noise")) noiseType = Magick::LaplacianNoise;
+ else if (!strcmp(_noiseTypeName, "Poisson Noise")) noiseType = Magick::PoissonNoise;
+
+ image->addNoise(noiseType);
+}
+
+void
+AddNoise::refreshParameters(Inkscape::Extension::Effect *module) {
+ _noiseTypeName = module->get_param_optiongroup("noiseType");
+}
+
+#include "../clear-n_.h"
+
+void
+AddNoise::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Add Noise") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.addNoise</id>\n"
+ "<param name=\"noiseType\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value='Uniform Noise'>" N_("Uniform Noise") "</option>\n"
+ "<option value='Gaussian Noise'>" N_("Gaussian Noise") "</option>\n"
+ "<option value='Multiplicative Gaussian Noise'>" N_("Multiplicative Gaussian Noise") "</option>\n"
+ "<option value='Impulse Noise'>" N_("Impulse Noise") "</option>\n"
+ "<option value='Laplacian Noise'>" N_("Laplacian Noise") "</option>\n"
+ "<option value='Poisson Noise'>" N_("Poisson Noise") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Add random noise to selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new AddNoise());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/addNoise.h b/src/extension/internal/bitmap/addNoise.h
new file mode 100644
index 0000000..06ce8c3
--- /dev/null
+++ b/src/extension/internal/bitmap/addNoise.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class AddNoise : public ImageMagick
+{
+private:
+ const gchar* _noiseTypeName;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/blur.cpp b/src/extension/internal/bitmap/blur.cpp
new file mode 100644
index 0000000..ba9d523
--- /dev/null
+++ b/src/extension/internal/bitmap/blur.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "blur.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Blur::applyEffect(Magick::Image *image) {
+ image->blur(_radius, _sigma);
+}
+
+void
+Blur::refreshParameters(Inkscape::Extension::Effect *module) {
+ _radius = module->get_param_float("radius");
+ _sigma = module->get_param_float("sigma");
+}
+
+#include "../clear-n_.h"
+
+void
+Blur::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Blur") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.blur</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">1</param>\n"
+ "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"100\">0.5</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Blur selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Blur());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/blur.h b/src/extension/internal/bitmap/blur.h
new file mode 100644
index 0000000..0ed158a
--- /dev/null
+++ b/src/extension/internal/bitmap/blur.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Blur : public ImageMagick
+{
+private:
+ double _radius;
+ double _sigma;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/channel.cpp b/src/extension/internal/bitmap/channel.cpp
new file mode 100644
index 0000000..38ba8f1
--- /dev/null
+++ b/src/extension/internal/bitmap/channel.cpp
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "channel.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Channel::applyEffect(Magick::Image *image) {
+ Magick::ChannelType layer = Magick::UndefinedChannel;
+ if (!strcmp(_layerName, "Red Channel")) layer = Magick::RedChannel;
+ else if (!strcmp(_layerName, "Green Channel")) layer = Magick::GreenChannel;
+ else if (!strcmp(_layerName, "Blue Channel")) layer = Magick::BlueChannel;
+ else if (!strcmp(_layerName, "Cyan Channel")) layer = Magick::CyanChannel;
+ else if (!strcmp(_layerName, "Magenta Channel")) layer = Magick::MagentaChannel;
+ else if (!strcmp(_layerName, "Yellow Channel")) layer = Magick::YellowChannel;
+ else if (!strcmp(_layerName, "Black Channel")) layer = Magick::BlackChannel;
+ else if (!strcmp(_layerName, "Opacity Channel")) layer = Magick::OpacityChannel;
+ else if (!strcmp(_layerName, "Matte Channel")) layer = Magick::MatteChannel;
+
+ image->channel(layer);
+}
+
+void
+Channel::refreshParameters(Inkscape::Extension::Effect *module) {
+ _layerName = module->get_param_optiongroup("layer");
+}
+
+#include "../clear-n_.h"
+
+void
+Channel::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Channel") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.channel</id>\n"
+ "<param name=\"layer\" gui-text=\"" N_("Layer:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value='Red Channel'>" N_("Red Channel") "</option>\n"
+ "<option value='Green Channel'>" N_("Green Channel") "</option>\n"
+ "<option value='Blue Channel'>" N_("Blue Channel") "</option>\n"
+ "<option value='Cyan Channel'>" N_("Cyan Channel") "</option>\n"
+ "<option value='Magenta Channel'>" N_("Magenta Channel") "</option>\n"
+ "<option value='Yellow Channel'>" N_("Yellow Channel") "</option>\n"
+ "<option value='Black Channel'>" N_("Black Channel") "</option>\n"
+ "<option value='Opacity Channel'>" N_("Opacity Channel") "</option>\n"
+ "<option value='Matte Channel'>" N_("Matte Channel") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Extract specific channel from image") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Channel());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/channel.h b/src/extension/internal/bitmap/channel.h
new file mode 100644
index 0000000..e215344
--- /dev/null
+++ b/src/extension/internal/bitmap/channel.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Channel : public ImageMagick {
+
+private:
+ const gchar * _layerName;
+
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/charcoal.cpp b/src/extension/internal/bitmap/charcoal.cpp
new file mode 100644
index 0000000..6343399
--- /dev/null
+++ b/src/extension/internal/bitmap/charcoal.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "charcoal.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Charcoal::applyEffect(Magick::Image* image) {
+ image->charcoal(_radius, _sigma);
+}
+
+void
+Charcoal::refreshParameters(Inkscape::Extension::Effect* module) {
+ _radius = module->get_param_float("radius");
+ _sigma = module->get_param_float("sigma");
+}
+
+#include "../clear-n_.h"
+
+void
+Charcoal::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Charcoal") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.charcoal</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">1</param>\n"
+ "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"100\">0.5</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Apply charcoal stylization to selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Charcoal());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/charcoal.h b/src/extension/internal/bitmap/charcoal.h
new file mode 100644
index 0000000..da381d9
--- /dev/null
+++ b/src/extension/internal/bitmap/charcoal.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Charcoal : public ImageMagick
+{
+private:
+ double _radius;
+ double _sigma;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/colorize.cpp b/src/extension/internal/bitmap/colorize.cpp
new file mode 100644
index 0000000..ea9d748
--- /dev/null
+++ b/src/extension/internal/bitmap/colorize.cpp
@@ -0,0 +1,69 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "colorize.h"
+
+#include "color.h"
+
+#include <iostream>
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Colorize::applyEffect(Magick::Image *image) {
+ float r = ((_color >> 24) & 0xff) / 255.0F;
+ float g = ((_color >> 16) & 0xff) / 255.0F;
+ float b = ((_color >> 8) & 0xff) / 255.0F;
+ float a = ((_color ) & 0xff) / 255.0F;
+
+ Magick::ColorRGB mc(r,g,b);
+
+ image->colorize(a * 100, mc);
+}
+
+void
+Colorize::refreshParameters(Inkscape::Extension::Effect *module) {
+ _color = module->get_param_color("color");
+}
+
+#include "../clear-n_.h"
+
+void
+Colorize::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Colorize") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.colorize</id>\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Colorize selected bitmap(s) with specified color, using given opacity") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Colorize());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/colorize.h b/src/extension/internal/bitmap/colorize.h
new file mode 100644
index 0000000..7c16ef5
--- /dev/null
+++ b/src/extension/internal/bitmap/colorize.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Colorize : public ImageMagick {
+private:
+ unsigned int _opacity;
+ guint32 _color;
+
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/contrast.cpp b/src/extension/internal/bitmap/contrast.cpp
new file mode 100644
index 0000000..2f43923
--- /dev/null
+++ b/src/extension/internal/bitmap/contrast.cpp
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "contrast.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Contrast::applyEffect(Magick::Image *image) {
+ // the contrast method's argument seems to be binary, so we perform it multiple times
+ // to get the desired level of effect
+ for (unsigned int i = 0; i < _sharpen; i ++)
+ image->contrast(1);
+}
+
+void
+Contrast::refreshParameters(Inkscape::Extension::Effect *module) {
+ _sharpen = module->get_param_int("sharpen");
+}
+
+#include "../clear-n_.h"
+
+void
+Contrast::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Contrast") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.contrast</id>\n"
+ "<param name=\"sharpen\" gui-text=\"" N_("Adjust:") "\" type=\"int\" min=\"0\" max=\"10\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Increase or decrease contrast in bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Contrast());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/contrast.h b/src/extension/internal/bitmap/contrast.h
new file mode 100644
index 0000000..c0e95e4
--- /dev/null
+++ b/src/extension/internal/bitmap/contrast.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Contrast : public ImageMagick
+{
+private:
+ unsigned int _sharpen;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/crop.cpp b/src/extension/internal/bitmap/crop.cpp
new file mode 100644
index 0000000..785c32a
--- /dev/null
+++ b/src/extension/internal/bitmap/crop.cpp
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2011 Authors:
+ * Nicolas Dufour <nicoduf@yahoo.fr>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "2geom/transforms.h"
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "crop.h"
+#include "selection-chemistry.h"
+#include "object/sp-item.h"
+#include "object/sp-item-transform.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Crop::applyEffect(Magick::Image *image) {
+ int width = image->baseColumns() - (_left + _right);
+ int height = image->baseRows() - (_top + _bottom);
+ if (width > 0 and height > 0) {
+ image->crop(Magick::Geometry(width, height, _left, _top, false, false));
+ image->page("+0+0");
+ }
+}
+
+void
+Crop::postEffect(Magick::Image *image, SPItem *item) {
+
+ // Scale bbox
+ Geom::Scale scale (0,0);
+ scale = Geom::Scale(image->columns() / (double) image->baseColumns(),
+ image->rows() / (double) image->baseRows());
+ item->scale_rel(scale);
+
+ // Translate proportionaly to the image/bbox ratio
+ Geom::OptRect bbox(item->desktopGeometricBounds());
+ //g_warning("bbox. W:%f, H:%f, X:%f, Y:%f", bbox->dimensions()[Geom::X], bbox->dimensions()[Geom::Y], bbox->min()[Geom::X], bbox->min()[Geom::Y]);
+
+ Geom::Translate translate (0,0);
+ translate = Geom::Translate(((_left - _right) / 2.0) * (bbox->dimensions()[Geom::X] / (double) image->columns()),
+ ((_bottom - _top) / 2.0) * (bbox->dimensions()[Geom::Y] / (double) image->rows()));
+ item->move_rel(translate);
+}
+
+void
+Crop::refreshParameters(Inkscape::Extension::Effect *module) {
+ _top = module->get_param_int("top");
+ _bottom = module->get_param_int("bottom");
+ _left = module->get_param_int("left");
+ _right = module->get_param_int("right");
+}
+
+#include "../clear-n_.h"
+
+void
+Crop::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Crop") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.crop</id>\n"
+ "<param name=\"top\" gui-text=\"" N_("Top (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n"
+ "<param name=\"bottom\" gui-text=\"" N_("Bottom (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n"
+ "<param name=\"left\" gui-text=\"" N_("Left (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n"
+ "<param name=\"right\" gui-text=\"" N_("Right (px):") "\" type=\"int\" min=\"0\" max=\"100000\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Crop selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Crop());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/crop.h b/src/extension/internal/bitmap/crop.h
new file mode 100644
index 0000000..da53878
--- /dev/null
+++ b/src/extension/internal/bitmap/crop.h
@@ -0,0 +1,35 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2010 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ * Nicolas Dufour <nicoduf@yahoo.fr>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Crop : public ImageMagick
+{
+private:
+ int _top;
+ int _bottom;
+ int _left;
+ int _right;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void postEffect(Magick::Image *image, SPItem *item) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/cycleColormap.cpp b/src/extension/internal/bitmap/cycleColormap.cpp
new file mode 100644
index 0000000..c28e5e6
--- /dev/null
+++ b/src/extension/internal/bitmap/cycleColormap.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "cycleColormap.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+CycleColormap::applyEffect(Magick::Image *image) {
+ image->cycleColormap(_amount);
+}
+
+void
+CycleColormap::refreshParameters(Inkscape::Extension::Effect *module) {
+ _amount = module->get_param_int("amount");
+}
+
+#include "../clear-n_.h"
+
+void
+CycleColormap::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Cycle Colormap") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.cycleColormap</id>\n"
+ "<param name=\"amount\" gui-text=\"" N_("Amount:") "\" type=\"int\" min=\"0\" max=\"360\">180</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Cycle colormap(s) of selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new CycleColormap());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/cycleColormap.h b/src/extension/internal/bitmap/cycleColormap.h
new file mode 100644
index 0000000..0d66b15
--- /dev/null
+++ b/src/extension/internal/bitmap/cycleColormap.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class CycleColormap : public ImageMagick {
+private:
+ int _amount;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/despeckle.cpp b/src/extension/internal/bitmap/despeckle.cpp
new file mode 100644
index 0000000..46a1baf
--- /dev/null
+++ b/src/extension/internal/bitmap/despeckle.cpp
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "despeckle.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Despeckle::applyEffect(Magick::Image *image) {
+ image->despeckle();
+}
+
+void
+Despeckle::refreshParameters(Inkscape::Extension::Effect */*module*/) {
+}
+
+#include "../clear-n_.h"
+
+void
+Despeckle::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Despeckle") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.despeckle</id>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Reduce speckle noise of selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Despeckle());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/despeckle.h b/src/extension/internal/bitmap/despeckle.h
new file mode 100644
index 0000000..0c731ee
--- /dev/null
+++ b/src/extension/internal/bitmap/despeckle.h
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Despeckle : public ImageMagick {
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/edge.cpp b/src/extension/internal/bitmap/edge.cpp
new file mode 100644
index 0000000..93b7394
--- /dev/null
+++ b/src/extension/internal/bitmap/edge.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "edge.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Edge::applyEffect(Magick::Image *image) {
+ image->edge(_radius);
+}
+
+void
+Edge::refreshParameters(Inkscape::Extension::Effect *module) {
+ _radius = module->get_param_int("radius");
+}
+
+#include "../clear-n_.h"
+
+void
+Edge::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Edge") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.edge</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"int\" min=\"0\" max=\"100\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Highlight edges of selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Edge());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/edge.h b/src/extension/internal/bitmap/edge.h
new file mode 100644
index 0000000..2c5fe14
--- /dev/null
+++ b/src/extension/internal/bitmap/edge.h
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Edge : public ImageMagick {
+private:
+ unsigned int _radius;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/emboss.cpp b/src/extension/internal/bitmap/emboss.cpp
new file mode 100644
index 0000000..86988c9
--- /dev/null
+++ b/src/extension/internal/bitmap/emboss.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "emboss.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Emboss::applyEffect(Magick::Image *image) {
+ image->emboss(_radius, _sigma);
+}
+
+void
+Emboss::refreshParameters(Inkscape::Extension::Effect *module) {
+ _radius = module->get_param_float("radius");
+ _sigma = module->get_param_float("sigma");
+}
+
+#include "../clear-n_.h"
+
+void
+Emboss::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Emboss") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.emboss</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">1.0</param>\n"
+ "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"-50\" max=\"50\">0.5</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Emboss selected bitmap(s); highlight edges with 3D effect") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Emboss());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/emboss.h b/src/extension/internal/bitmap/emboss.h
new file mode 100644
index 0000000..51d04d6
--- /dev/null
+++ b/src/extension/internal/bitmap/emboss.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Emboss : public ImageMagick
+{
+private:
+ double _radius;
+ double _sigma;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/enhance.cpp b/src/extension/internal/bitmap/enhance.cpp
new file mode 100644
index 0000000..391d1f1
--- /dev/null
+++ b/src/extension/internal/bitmap/enhance.cpp
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "enhance.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Enhance::applyEffect(Magick::Image *image) {
+ image->enhance();
+}
+
+void
+Enhance::refreshParameters(Inkscape::Extension::Effect */*module*/) { }
+
+#include "../clear-n_.h"
+
+void
+Enhance::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Enhance") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.enhance</id>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Enhance selected bitmap(s); minimize noise") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Enhance());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/enhance.h b/src/extension/internal/bitmap/enhance.h
new file mode 100644
index 0000000..dd3d9ff
--- /dev/null
+++ b/src/extension/internal/bitmap/enhance.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Enhance : public ImageMagick
+{
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/equalize.cpp b/src/extension/internal/bitmap/equalize.cpp
new file mode 100644
index 0000000..df0575e
--- /dev/null
+++ b/src/extension/internal/bitmap/equalize.cpp
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "equalize.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Equalize::applyEffect(Magick::Image *image) {
+ image->equalize();
+}
+
+void
+Equalize::refreshParameters(Inkscape::Extension::Effect */*module*/) { }
+
+#include "../clear-n_.h"
+
+void
+Equalize::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Equalize") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.equalize</id>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Equalize selected bitmap(s); histogram equalization") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Equalize());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/equalize.h b/src/extension/internal/bitmap/equalize.h
new file mode 100644
index 0000000..8259ffb
--- /dev/null
+++ b/src/extension/internal/bitmap/equalize.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Equalize : public ImageMagick
+{
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init ();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/gaussianBlur.cpp b/src/extension/internal/bitmap/gaussianBlur.cpp
new file mode 100644
index 0000000..1b8396b
--- /dev/null
+++ b/src/extension/internal/bitmap/gaussianBlur.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "gaussianBlur.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+GaussianBlur::applyEffect(Magick::Image* image) {
+ image->gaussianBlur(_width, _sigma);
+}
+
+void
+GaussianBlur::refreshParameters(Inkscape::Extension::Effect* module) {
+ _width = module->get_param_float("width");
+ _sigma = module->get_param_float("sigma");
+}
+
+#include "../clear-n_.h"
+
+void
+GaussianBlur::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Gaussian Blur") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.gaussianBlur</id>\n"
+ "<param name=\"width\" gui-text=\"" N_("Factor:") "\" type=\"float\" min=\"0\" max=\"100\">5.0</param>\n"
+ "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"100\">5.0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Gaussian blur selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new GaussianBlur());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/gaussianBlur.h b/src/extension/internal/bitmap/gaussianBlur.h
new file mode 100644
index 0000000..9c9c500
--- /dev/null
+++ b/src/extension/internal/bitmap/gaussianBlur.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class GaussianBlur : public ImageMagick
+{
+private:
+ double _width;
+ double _sigma;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/imagemagick.cpp b/src/extension/internal/bitmap/imagemagick.cpp
new file mode 100644
index 0000000..36176bc
--- /dev/null
+++ b/src/extension/internal/bitmap/imagemagick.cpp
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <libintl.h>
+
+#include <gtkmm/box.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/spinbutton.h>
+
+#include <glib/gstdio.h>
+
+#include "desktop.h"
+
+#include "selection.h"
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "imagemagick.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class ImageMagickDocCache: public Inkscape::Extension::Implementation::ImplementationDocumentCache {
+ friend class ImageMagick;
+private:
+ void readImage(char const *xlink, char const *id, Magick::Image *image);
+protected:
+ Inkscape::XML::Node** _nodes;
+
+ Magick::Image** _images;
+ int _imageCount;
+ char** _caches;
+ unsigned* _cacheLengths;
+ const char** _originals;
+ SPItem** _imageItems;
+public:
+ ImageMagickDocCache(Inkscape::UI::View::View * view);
+ ~ImageMagickDocCache ( ) override;
+};
+
+ImageMagickDocCache::ImageMagickDocCache(Inkscape::UI::View::View * view) :
+ Inkscape::Extension::Implementation::ImplementationDocumentCache(view),
+ _nodes(NULL),
+ _images(NULL),
+ _imageCount(0),
+ _caches(NULL),
+ _cacheLengths(NULL),
+ _originals(NULL),
+ _imageItems(NULL)
+{
+ SPDesktop *desktop = (SPDesktop*)view;
+ auto selectedItemList = desktop->selection->items();
+ int selectCount = (int) boost::distance(selectedItemList);
+
+ // Init the data-holders
+ _nodes = new Inkscape::XML::Node*[selectCount];
+ _originals = new const char*[selectCount];
+ _caches = new char*[selectCount];
+ _cacheLengths = new unsigned int[selectCount];
+ _images = new Magick::Image*[selectCount];
+ _imageCount = 0;
+ _imageItems = new SPItem*[selectCount];
+
+ // Loop through selected items
+ for (auto i = selectedItemList.begin(); i != selectedItemList.end(); ++i) {
+ SPItem *item = *i;
+ Inkscape::XML::Node *node = reinterpret_cast<Inkscape::XML::Node *>(item->getRepr());
+ if (!strcmp(node->name(), "image") || !strcmp(node->name(), "svg:image"))
+ {
+ _nodes[_imageCount] = node;
+ char const *xlink = node->attribute("xlink:href");
+ char const *id = node->attribute("id");
+ _originals[_imageCount] = xlink;
+ _caches[_imageCount] = (char*)"";
+ _cacheLengths[_imageCount] = 0;
+ _images[_imageCount] = new Magick::Image();
+ readImage(xlink, id, _images[_imageCount]);
+ _imageItems[_imageCount] = item;
+ _imageCount++;
+ }
+ }
+}
+
+ImageMagickDocCache::~ImageMagickDocCache ( ) {
+ if (_nodes)
+ delete _nodes;
+ if (_originals)
+ delete _originals;
+ if (_caches)
+ delete _caches;
+ if (_cacheLengths)
+ delete _cacheLengths;
+ if (_images)
+ delete _images;
+ if (_imageItems)
+ delete _imageItems;
+ return;
+}
+
+void
+ImageMagickDocCache::readImage(const char *xlink, const char *id, Magick::Image *image)
+{
+ // Find if the xlink:href is base64 data, i.e. if the image is embedded
+ gchar *search = g_strndup(xlink, 30);
+ if (strstr(search, "base64") != (char*)NULL) {
+ // 7 = strlen("base64") + strlen(",")
+ const char* pureBase64 = strstr(xlink, "base64") + 7;
+ Magick::Blob blob;
+ blob.base64(pureBase64);
+ try {
+ image->read(blob);
+ } catch (Magick::Exception &error_) {
+ g_warning("ImageMagick could not read '%s'\nDetails: %s", id, error_.what());
+ }
+ } else {
+ gchar *path;
+ if (strncmp (xlink,"file:", 5) == 0) {
+ path = g_filename_from_uri(xlink, NULL, NULL);
+ } else {
+ path = g_strdup(xlink);
+ }
+ try {
+ image->read(path);
+ } catch (Magick::Exception &error_) {
+ g_warning("ImageMagick could not read '%s' from '%s'\nDetails: %s", id, path, error_.what());
+ }
+ g_free(path);
+ }
+ g_free(search);
+}
+
+bool
+ImageMagick::load(Inkscape::Extension::Extension */*module*/)
+{
+ return true;
+}
+
+Inkscape::Extension::Implementation::ImplementationDocumentCache *
+ImageMagick::newDocCache (Inkscape::Extension::Extension * /*ext*/, Inkscape::UI::View::View * view) {
+ return new ImageMagickDocCache(view);
+}
+
+void
+ImageMagick::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache)
+{
+ refreshParameters(module);
+
+ if (docCache == NULL) { // should never happen
+ docCache = newDocCache(module, document);
+ }
+ ImageMagickDocCache * dc = dynamic_cast<ImageMagickDocCache *>(docCache);
+ if (dc == NULL) { // should really never happen
+ printf("AHHHHHHHHH!!!!!");
+ exit(1);
+ }
+
+ for (int i = 0; i < dc->_imageCount; i++)
+ {
+ try
+ {
+ Magick::Image effectedImage = *dc->_images[i]; // make a copy
+
+ applyEffect(&effectedImage);
+
+ // postEffect can be used to change things on the item itself
+ // e.g. resize the image element, after the effecti is applied
+ postEffect(&effectedImage, dc->_imageItems[i]);
+
+// dc->_nodes[i]->setAttribute("xlink:href", dc->_caches[i]);
+
+ Magick::Blob *blob = new Magick::Blob();
+ effectedImage.write(blob);
+
+ std::string raw_string = blob->base64();
+ const int raw_len = raw_string.length();
+ const char *raw_i = raw_string.c_str();
+
+ unsigned new_len = (int)(raw_len * (77.0 / 76.0) + 100);
+ if (new_len > dc->_cacheLengths[i]) {
+ dc->_cacheLengths[i] = (int)(new_len * 1.2);
+ dc->_caches[i] = new char[dc->_cacheLengths[i]];
+ }
+ char *formatted_i = dc->_caches[i];
+ const char *src;
+
+ for (src = "data:image/"; *src; )
+ *formatted_i++ = *src++;
+ for (src = effectedImage.magick().c_str(); *src ; )
+ *formatted_i++ = *src++;
+ for (src = ";base64, \n" ; *src; )
+ *formatted_i++ = *src++;
+
+ int col = 0;
+ while (*raw_i) {
+ *formatted_i++ = *raw_i++;
+ if (col++ > 76) {
+ *formatted_i++ = '\n';
+ col = 0;
+ }
+ }
+ if (col) {
+ *formatted_i++ = '\n';
+ }
+ *formatted_i = '\0';
+
+ dc->_nodes[i]->setAttribute("xlink:href", dc->_caches[i]);
+ dc->_nodes[i]->removeAttribute("sodipodi:absref");
+ delete blob;
+ }
+ catch (Magick::Exception &error_) {
+ printf("Caught exception: %s \n", error_.what());
+ }
+
+ //while(Gtk::Main::events_pending()) {
+ // Gtk::Main::iteration();
+ //}
+ }
+}
+
+/** \brief A function to get the preferences for the grid
+ \param module Module which holds the params
+ \param view Unused today - may get style information in the future.
+
+ Uses AutoGUI for creating the GUI.
+*/
+Gtk::Widget *
+ImageMagick::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ SPDocument * current_document = view->doc();
+
+ auto selected = ((SPDesktop *) view)->getSelection()->items();
+ Inkscape::XML::Node * first_select = NULL;
+ if (!selected.empty()) {
+ first_select = (selected.front())->getRepr();
+ }
+
+ return module->autogui(current_document, first_select, changeSignal);
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/imagemagick.h b/src/extension/internal/bitmap/imagemagick.h
new file mode 100644
index 0000000..754e195
--- /dev/null
+++ b/src/extension/internal/bitmap/imagemagick.h
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INKSCAPE_EXTENSION_INTERNAL_BITMAP_IMAGEMAGICK_H
+#define INKSCAPE_EXTENSION_INTERNAL_BITMAP_IMAGEMAGICK_H
+
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/implementation/implementation.h"
+
+class SPItem;
+
+namespace Magick {
+class Image;
+}
+
+namespace Inkscape {
+namespace Extension {
+
+class Effect;
+class Extension;
+
+namespace Internal {
+namespace Bitmap {
+
+class ImageMagick : public Inkscape::Extension::Implementation::Implementation {
+public:
+ /* Functions to be implemented by subclasses */
+ virtual void applyEffect(Magick::Image */*image*/) { };
+ virtual void refreshParameters(Inkscape::Extension::Effect */*module*/) { };
+ virtual void postEffect(Magick::Image */*image*/, SPItem */*item*/) { };
+
+ /* Functions implemented from ::Implementation */
+ bool load(Inkscape::Extension::Extension *module) override;
+ Inkscape::Extension::Implementation::ImplementationDocumentCache * newDocCache (Inkscape::Extension::Extension * ext, Inkscape::UI::View::View * doc) override;
+ void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+ Gtk::Widget* prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+#endif // INKSCAPE_EXTENSION_INTERNAL_BITMAP_IMAGEMAGICK_H
diff --git a/src/extension/internal/bitmap/implode.cpp b/src/extension/internal/bitmap/implode.cpp
new file mode 100644
index 0000000..4395e2a
--- /dev/null
+++ b/src/extension/internal/bitmap/implode.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "implode.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Implode::applyEffect(Magick::Image* image) {
+ image->implode(_factor);
+}
+
+void
+Implode::refreshParameters(Inkscape::Extension::Effect* module) {
+ _factor = module->get_param_float("factor");
+}
+
+#include "../clear-n_.h"
+
+void
+Implode::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Implode") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.implode</id>\n"
+ "<param name=\"factor\" gui-text=\"" N_("Factor:") "\" type=\"float\" min=\"0\" max=\"100\">10</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Implode selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Implode());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/implode.h b/src/extension/internal/bitmap/implode.h
new file mode 100644
index 0000000..d9c5adb
--- /dev/null
+++ b/src/extension/internal/bitmap/implode.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Implode : public ImageMagick
+{
+private:
+ float _factor;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/level.cpp b/src/extension/internal/bitmap/level.cpp
new file mode 100644
index 0000000..cf31744
--- /dev/null
+++ b/src/extension/internal/bitmap/level.cpp
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "level.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Level::applyEffect(Magick::Image* image) {
+ Magick::Quantum black_point = Magick::Color::scaleDoubleToQuantum(_black_point / 100.0);
+ Magick::Quantum white_point = Magick::Color::scaleDoubleToQuantum(_white_point / 100.0);
+ image->level(black_point, white_point, _mid_point);
+}
+
+void
+Level::refreshParameters(Inkscape::Extension::Effect* module) {
+ _black_point = module->get_param_float("blackPoint");
+ _white_point = module->get_param_float("whitePoint");
+ _mid_point = module->get_param_float("midPoint");
+}
+
+#include "../clear-n_.h"
+
+void
+Level::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Level") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.level</id>\n"
+ "<param name=\"blackPoint\" gui-text=\"" N_("Black Point:") "\" type=\"float\" min=\"0\" max=\"100\">0</param>\n"
+ "<param name=\"whitePoint\" gui-text=\"" N_("White Point:") "\" type=\"float\" min=\"0\" max=\"100\">100</param>\n"
+ "<param name=\"midPoint\" gui-text=\"" N_("Gamma Correction:") "\" type=\"float\" min=\"0\" max=\"10\">1</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Level selected bitmap(s) by scaling values falling between the given ranges to the full color range") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Level());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/level.h b/src/extension/internal/bitmap/level.h
new file mode 100644
index 0000000..a09f189
--- /dev/null
+++ b/src/extension/internal/bitmap/level.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Level : public ImageMagick
+{
+private:
+ double _black_point;
+ double _white_point;
+ double _mid_point;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/levelChannel.cpp b/src/extension/internal/bitmap/levelChannel.cpp
new file mode 100644
index 0000000..7280fb1
--- /dev/null
+++ b/src/extension/internal/bitmap/levelChannel.cpp
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "levelChannel.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+LevelChannel::applyEffect(Magick::Image* image) {
+ Magick::ChannelType channel = Magick::UndefinedChannel;
+ if (!strcmp(_channelName, "Red Channel")) channel = Magick::RedChannel;
+ else if (!strcmp(_channelName, "Green Channel")) channel = Magick::GreenChannel;
+ else if (!strcmp(_channelName, "Blue Channel")) channel = Magick::BlueChannel;
+ else if (!strcmp(_channelName, "Cyan Channel")) channel = Magick::CyanChannel;
+ else if (!strcmp(_channelName, "Magenta Channel")) channel = Magick::MagentaChannel;
+ else if (!strcmp(_channelName, "Yellow Channel")) channel = Magick::YellowChannel;
+ else if (!strcmp(_channelName, "Black Channel")) channel = Magick::BlackChannel;
+ else if (!strcmp(_channelName, "Opacity Channel")) channel = Magick::OpacityChannel;
+ else if (!strcmp(_channelName, "Matte Channel")) channel = Magick::MatteChannel;
+ Magick::Quantum black_point = Magick::Color::scaleDoubleToQuantum(_black_point / 100.0);
+ Magick::Quantum white_point = Magick::Color::scaleDoubleToQuantum(_white_point / 100.0);
+ image->levelChannel(channel, black_point, white_point, _mid_point);
+}
+
+void
+LevelChannel::refreshParameters(Inkscape::Extension::Effect* module) {
+ _channelName = module->get_param_optiongroup("channel");
+ _black_point = module->get_param_float("blackPoint");
+ _white_point = module->get_param_float("whitePoint");
+ _mid_point = module->get_param_float("midPoint");
+}
+
+#include "../clear-n_.h"
+
+void
+LevelChannel::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Level (with Channel)") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.levelChannel</id>\n"
+ "<param name=\"channel\" gui-text=\"" N_("Channel:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value='Red Channel'>" N_("Red Channel") "</option>\n"
+ "<option value='Green Channel'>" N_("Green Channel") "</option>\n"
+ "<option value='Blue Channel'>" N_("Blue Channel") "</option>\n"
+ "<option value='Cyan Channel'>" N_("Cyan Channel") "</option>\n"
+ "<option value='Magenta Channel'>" N_("Magenta Channel") "</option>\n"
+ "<option value='Yellow Channel'>" N_("Yellow Channel") "</option>\n"
+ "<option value='Black Channel'>" N_("Black Channel") "</option>\n"
+ "<option value='Opacity Channel'>" N_("Opacity Channel") "</option>\n"
+ "<option value='Matte Channel'>" N_("Matte Channel") "</option>\n"
+ "</param>\n"
+ "<param name=\"blackPoint\" gui-text=\"" N_("Black Point:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">0.0</param>\n"
+ "<param name=\"whitePoint\" gui-text=\"" N_("White Point:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">100.0</param>\n"
+ "<param name=\"midPoint\" gui-text=\"" N_("Gamma Correction:") "\" type=\"float\" min=\"0.0\" max=\"10.0\">1.0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Level the specified channel of selected bitmap(s) by scaling values falling between the given ranges to the full color range") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new LevelChannel());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/levelChannel.h b/src/extension/internal/bitmap/levelChannel.h
new file mode 100644
index 0000000..d1cc82a
--- /dev/null
+++ b/src/extension/internal/bitmap/levelChannel.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class LevelChannel : public ImageMagick
+{
+private:
+ double _black_point;
+ double _white_point;
+ double _mid_point;
+ const gchar * _channelName;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/medianFilter.cpp b/src/extension/internal/bitmap/medianFilter.cpp
new file mode 100644
index 0000000..80385ac
--- /dev/null
+++ b/src/extension/internal/bitmap/medianFilter.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "medianFilter.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+MedianFilter::applyEffect(Magick::Image* image) {
+ image->medianFilter(_radius);
+}
+
+void
+MedianFilter::refreshParameters(Inkscape::Extension::Effect* module) {
+ _radius = module->get_param_float("radius");
+}
+
+#include "../clear-n_.h"
+
+void
+MedianFilter::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Median") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.medianFilter</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"100\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Replace each pixel component with the median color in a circular neighborhood") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new MedianFilter());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/medianFilter.h b/src/extension/internal/bitmap/medianFilter.h
new file mode 100644
index 0000000..214ed2b
--- /dev/null
+++ b/src/extension/internal/bitmap/medianFilter.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class MedianFilter : public ImageMagick
+{
+private:
+ double _radius;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/modulate.cpp b/src/extension/internal/bitmap/modulate.cpp
new file mode 100644
index 0000000..a570da4
--- /dev/null
+++ b/src/extension/internal/bitmap/modulate.cpp
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "modulate.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Modulate::applyEffect(Magick::Image* image) {
+ double hue = (_hue * 200 / 360.0) + 100;
+ image->modulate(_brightness, _saturation, hue);
+}
+
+void
+Modulate::refreshParameters(Inkscape::Extension::Effect* module) {
+ _brightness = module->get_param_float("brightness");
+ _saturation = module->get_param_float("saturation");
+ _hue = module->get_param_float("hue");
+}
+
+#include "../clear-n_.h"
+
+void
+Modulate::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("HSB Adjust") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.modulate</id>\n"
+ "<param name=\"hue\" gui-text=\"" N_("Hue:") "\" type=\"float\" min=\"-360\" max=\"360\">0</param>\n"
+ "<param name=\"saturation\" gui-text=\"" N_("Saturation:") "\" type=\"float\" min=\"0\" max=\"200\">100</param>\n"
+ "<param name=\"brightness\" gui-text=\"" N_("Brightness:") "\" type=\"float\" min=\"0\" max=\"200\">100</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Adjust the amount of hue, saturation, and brightness in selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Modulate());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/modulate.h b/src/extension/internal/bitmap/modulate.h
new file mode 100644
index 0000000..327d3c4
--- /dev/null
+++ b/src/extension/internal/bitmap/modulate.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Modulate : public ImageMagick
+{
+private:
+ double _brightness;
+ double _saturation;
+ double _hue;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/negate.cpp b/src/extension/internal/bitmap/negate.cpp
new file mode 100644
index 0000000..6470556
--- /dev/null
+++ b/src/extension/internal/bitmap/negate.cpp
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "negate.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Negate::applyEffect(Magick::Image* image) {
+ image->negate();
+}
+
+void
+Negate::refreshParameters(Inkscape::Extension::Effect* /*module*/) {
+}
+
+#include "../clear-n_.h"
+
+void
+Negate::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Negate") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.negate</id>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Negate (take inverse) selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Negate());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/negate.h b/src/extension/internal/bitmap/negate.h
new file mode 100644
index 0000000..4cde402
--- /dev/null
+++ b/src/extension/internal/bitmap/negate.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Negate : public ImageMagick
+{
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/normalize.cpp b/src/extension/internal/bitmap/normalize.cpp
new file mode 100644
index 0000000..96cfe13
--- /dev/null
+++ b/src/extension/internal/bitmap/normalize.cpp
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "normalize.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Normalize::applyEffect(Magick::Image* image) {
+ image->normalize();
+}
+
+void
+Normalize::refreshParameters(Inkscape::Extension::Effect* /*module*/) {
+}
+
+#include "../clear-n_.h"
+
+void
+Normalize::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Normalize") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.normalize</id>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Normalize selected bitmap(s), expanding color range to the full possible range of color") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Normalize());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/normalize.h b/src/extension/internal/bitmap/normalize.h
new file mode 100644
index 0000000..2d4a9c2
--- /dev/null
+++ b/src/extension/internal/bitmap/normalize.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Normalize : public ImageMagick
+{
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/oilPaint.cpp b/src/extension/internal/bitmap/oilPaint.cpp
new file mode 100644
index 0000000..ee17964
--- /dev/null
+++ b/src/extension/internal/bitmap/oilPaint.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "oilPaint.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+OilPaint::applyEffect(Magick::Image* image) {
+ image->oilPaint(_radius);
+}
+
+void
+OilPaint::refreshParameters(Inkscape::Extension::Effect* module) {
+ _radius = module->get_param_int("radius");
+}
+
+#include "../clear-n_.h"
+
+void
+OilPaint::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Oil Paint") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.oilPaint</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"int\" min=\"0\" max=\"50\">3</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Stylize selected bitmap(s) so that they appear to be painted with oils") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new OilPaint());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/oilPaint.h b/src/extension/internal/bitmap/oilPaint.h
new file mode 100644
index 0000000..58b94b2
--- /dev/null
+++ b/src/extension/internal/bitmap/oilPaint.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class OilPaint : public ImageMagick
+{
+private:
+ int _radius;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/opacity.cpp b/src/extension/internal/bitmap/opacity.cpp
new file mode 100644
index 0000000..024dcb9
--- /dev/null
+++ b/src/extension/internal/bitmap/opacity.cpp
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "opacity.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Opacity::applyEffect(Magick::Image* image) {
+ Magick::Quantum opacity = Magick::Color::scaleDoubleToQuantum((100 - _opacity) / 100.0);
+ image->opacity(opacity);
+}
+
+void
+Opacity::refreshParameters(Inkscape::Extension::Effect* module) {
+ _opacity = module->get_param_float("opacity");
+}
+
+#include "../clear-n_.h"
+
+void
+Opacity::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Opacity") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.opacity</id>\n"
+ "<param name=\"opacity\" gui-text=\"" N_("Opacity:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">80.0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Modify opacity channel(s) of selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Opacity());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/opacity.h b/src/extension/internal/bitmap/opacity.h
new file mode 100644
index 0000000..e8cb548
--- /dev/null
+++ b/src/extension/internal/bitmap/opacity.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Opacity : public ImageMagick
+{
+private:
+ double _opacity;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/raise.cpp b/src/extension/internal/bitmap/raise.cpp
new file mode 100644
index 0000000..e209f2d
--- /dev/null
+++ b/src/extension/internal/bitmap/raise.cpp
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "raise.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Raise::applyEffect(Magick::Image* image) {
+ Magick::Geometry geometry(_width, _height, 0, 0);
+ image->raise(geometry, _raisedFlag);
+}
+
+void
+Raise::refreshParameters(Inkscape::Extension::Effect* module) {
+ _width = module->get_param_int("width");
+ _height = module->get_param_int("height");
+ _raisedFlag = module->get_param_bool("raisedFlag");
+}
+
+#include "../clear-n_.h"
+
+void
+Raise::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Raise") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.raise</id>\n"
+ "<param name=\"width\" gui-text=\"" N_("Width:") "\" type=\"int\" min=\"0\" max=\"800\">6</param>\n"
+ "<param name=\"height\" gui-text=\"" N_("Height:") "\" type=\"int\" min=\"0\" max=\"800\">6</param>\n"
+ "<param name=\"raisedFlag\" gui-text=\"" N_("Raised") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Alter lightness the edges of selected bitmap(s) to create a raised appearance") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Raise());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/raise.h b/src/extension/internal/bitmap/raise.h
new file mode 100644
index 0000000..90023fb
--- /dev/null
+++ b/src/extension/internal/bitmap/raise.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Raise : public ImageMagick
+{
+private:
+ int _width;
+ int _height;
+ bool _raisedFlag;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/reduceNoise.cpp b/src/extension/internal/bitmap/reduceNoise.cpp
new file mode 100644
index 0000000..e1e5e83
--- /dev/null
+++ b/src/extension/internal/bitmap/reduceNoise.cpp
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "reduceNoise.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+ReduceNoise::applyEffect(Magick::Image* image) {
+ if (_order > -1)
+ image->reduceNoise(_order);
+ else
+ image->reduceNoise();
+}
+
+void
+ReduceNoise::refreshParameters(Inkscape::Extension::Effect* module) {
+ _order = module->get_param_int("order");
+}
+
+#include "../clear-n_.h"
+
+void
+ReduceNoise::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Reduce Noise") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.reduceNoise</id>\n"
+ "<param name=\"order\" gui-text=\"" N_("Order:") "\" type=\"int\" min=\"-1\" max=\"100\">-1</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Reduce noise in selected bitmap(s) using a noise peak elimination filter") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ReduceNoise());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/reduceNoise.h b/src/extension/internal/bitmap/reduceNoise.h
new file mode 100644
index 0000000..3c9d2d6
--- /dev/null
+++ b/src/extension/internal/bitmap/reduceNoise.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class ReduceNoise : public ImageMagick
+{
+private:
+ int _order;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/sample.cpp b/src/extension/internal/bitmap/sample.cpp
new file mode 100644
index 0000000..47b0147
--- /dev/null
+++ b/src/extension/internal/bitmap/sample.cpp
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "sample.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Sample::applyEffect(Magick::Image* image) {
+ Magick::Geometry geometry(_width, _height, 0, 0);
+ image->sample(geometry);
+}
+
+void
+Sample::refreshParameters(Inkscape::Extension::Effect* module) {
+ _width = module->get_param_int("width");
+ _height = module->get_param_int("height");
+}
+
+#include "../clear-n_.h"
+
+void
+Sample::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Resample") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.sample</id>\n"
+ "<param name=\"width\" gui-text=\"" N_("Width:") "\" type=\"int\" min=\"0\" max=\"6400\">100</param>\n"
+ "<param name=\"height\" gui-text=\"" N_("Height:") "\" type=\"int\" min=\"0\" max=\"6400\">100</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Alter the resolution of selected image by resizing it to the given pixel size") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Sample());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/sample.h b/src/extension/internal/bitmap/sample.h
new file mode 100644
index 0000000..c93ab0a
--- /dev/null
+++ b/src/extension/internal/bitmap/sample.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Sample : public ImageMagick
+{
+private:
+ int _width;
+ int _height;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/shade.cpp b/src/extension/internal/bitmap/shade.cpp
new file mode 100644
index 0000000..4a4f907
--- /dev/null
+++ b/src/extension/internal/bitmap/shade.cpp
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "shade.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Shade::applyEffect(Magick::Image* image) {
+ image->shade(_azimuth, _elevation, !_colorShading);
+ // I don't know why, but I have to invert colorShading here
+}
+
+void
+Shade::refreshParameters(Inkscape::Extension::Effect* module) {
+ _azimuth = module->get_param_float("azimuth");
+ _elevation = module->get_param_float("elevation");
+ _colorShading = module->get_param_bool("colorShading");
+}
+
+#include "../clear-n_.h"
+
+void
+Shade::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Shade") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.shade</id>\n"
+ "<param name=\"azimuth\" gui-text=\"" N_("Azimuth:") "\" type=\"float\" min=\"-180\" max=\"180\">30</param>\n"
+ "<param name=\"elevation\" gui-text=\"" N_("Elevation:") "\" type=\"float\" min=\"-180\" max=\"180\">30</param>\n"
+ "<param name=\"colorShading\" gui-text=\"" N_("Colored Shading") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Shade selected bitmap(s) simulating distant light source") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Shade());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/shade.h b/src/extension/internal/bitmap/shade.h
new file mode 100644
index 0000000..928dc87
--- /dev/null
+++ b/src/extension/internal/bitmap/shade.h
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Shade : public ImageMagick
+{
+private:
+ double _azimuth;
+ double _elevation;
+ bool _colorShading;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/sharpen.cpp b/src/extension/internal/bitmap/sharpen.cpp
new file mode 100644
index 0000000..40e159c
--- /dev/null
+++ b/src/extension/internal/bitmap/sharpen.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "sharpen.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Sharpen::applyEffect(Magick::Image* image) {
+ image->sharpen(_radius, _sigma);
+}
+
+void
+Sharpen::refreshParameters(Inkscape::Extension::Effect* module) {
+ _radius = module->get_param_float("radius");
+ _sigma = module->get_param_float("sigma");
+}
+
+#include "../clear-n_.h"
+
+void
+Sharpen::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Sharpen") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.sharpen</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0\" max=\"50\">1.0</param>\n"
+ "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0\" max=\"10\">0.5</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Sharpen selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Sharpen());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/sharpen.h b/src/extension/internal/bitmap/sharpen.h
new file mode 100644
index 0000000..6bbae73
--- /dev/null
+++ b/src/extension/internal/bitmap/sharpen.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Sharpen : public ImageMagick
+{
+private:
+ double _radius;
+ double _sigma;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/solarize.cpp b/src/extension/internal/bitmap/solarize.cpp
new file mode 100644
index 0000000..41952d8
--- /dev/null
+++ b/src/extension/internal/bitmap/solarize.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "solarize.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Solarize::applyEffect(Magick::Image* image) {
+ // Image Magick Quantum depth = 16
+ // 655.35 = (2^16 - 1) / 100
+ image->solarize(_factor * 655.35);
+}
+
+void
+Solarize::refreshParameters(Inkscape::Extension::Effect* module) {
+ _factor = module->get_param_float("factor");
+}
+
+#include "../clear-n_.h"
+
+void
+Solarize::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Solarize") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.solarize</id>\n"
+ "<param name=\"factor\" gui-text=\"" N_("Factor:") "\" type=\"float\" min=\"0\" max=\"100\">50</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Solarize selected bitmap(s), like overexposing photographic film") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Solarize());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/solarize.h b/src/extension/internal/bitmap/solarize.h
new file mode 100644
index 0000000..0e6bbea
--- /dev/null
+++ b/src/extension/internal/bitmap/solarize.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Solarize : public ImageMagick
+{
+private:
+ double _factor;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/spread.cpp b/src/extension/internal/bitmap/spread.cpp
new file mode 100644
index 0000000..cb7aa5d
--- /dev/null
+++ b/src/extension/internal/bitmap/spread.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "spread.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Spread::applyEffect(Magick::Image* image) {
+ image->spread(_amount);
+}
+
+void
+Spread::refreshParameters(Inkscape::Extension::Effect* module) {
+ _amount = module->get_param_int("amount");
+}
+
+#include "../clear-n_.h"
+
+void
+Spread::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Dither") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.spread</id>\n"
+ "<param name=\"amount\" gui-text=\"" N_("Amount:") "\" type=\"int\" min=\"0\" max=\"100\">3</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Randomly scatter pixels in selected bitmap(s), within the given radius of the original position") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Spread());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/spread.h b/src/extension/internal/bitmap/spread.h
new file mode 100644
index 0000000..ad77544
--- /dev/null
+++ b/src/extension/internal/bitmap/spread.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Spread : public ImageMagick
+{
+private:
+ int _amount;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/swirl.cpp b/src/extension/internal/bitmap/swirl.cpp
new file mode 100644
index 0000000..50dc75d
--- /dev/null
+++ b/src/extension/internal/bitmap/swirl.cpp
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "swirl.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Swirl::applyEffect(Magick::Image* image) {
+ image->swirl(_degrees);
+}
+
+void
+Swirl::refreshParameters(Inkscape::Extension::Effect* module) {
+ _degrees = module->get_param_int("degrees");
+}
+
+#include "../clear-n_.h"
+
+void
+Swirl::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Swirl") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.swirl</id>\n"
+ "<param name=\"degrees\" gui-text=\"" N_("Degrees:") "\" type=\"int\" min=\"-360\" max=\"360\">30</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Swirl selected bitmap(s) around center point") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Swirl());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/swirl.h b/src/extension/internal/bitmap/swirl.h
new file mode 100644
index 0000000..d95c0c9
--- /dev/null
+++ b/src/extension/internal/bitmap/swirl.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Swirl : public ImageMagick
+{
+private:
+ int _degrees;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/threshold.cpp b/src/extension/internal/bitmap/threshold.cpp
new file mode 100644
index 0000000..235cf48
--- /dev/null
+++ b/src/extension/internal/bitmap/threshold.cpp
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "threshold.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Threshold::applyEffect(Magick::Image* image) {
+ image->threshold(_threshold);
+}
+
+void
+Threshold::refreshParameters(Inkscape::Extension::Effect* module) {
+ _threshold = module->get_param_float("threshold");
+}
+
+#include "../clear-n_.h"
+
+void
+Threshold::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ // TRANSLATORS: see http://docs.gimp.org/en/gimp-tool-threshold.html
+ "<name>" N_("Threshold") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.threshold</id>\n"
+ "<param name=\"threshold\" gui-text=\"" N_("Threshold:") "\" type=\"float\" min=\"-100.0\" max=\"100.0\"></param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Threshold selected bitmap(s)") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Threshold());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/threshold.h b/src/extension/internal/bitmap/threshold.h
new file mode 100644
index 0000000..93e15bc
--- /dev/null
+++ b/src/extension/internal/bitmap/threshold.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Threshold : public ImageMagick
+{
+private:
+ double _threshold;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/unsharpmask.cpp b/src/extension/internal/bitmap/unsharpmask.cpp
new file mode 100644
index 0000000..73940d7
--- /dev/null
+++ b/src/extension/internal/bitmap/unsharpmask.cpp
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "unsharpmask.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Unsharpmask::applyEffect(Magick::Image* image) {
+ double amount = _amount / 100.0;
+ image->unsharpmask(_radius, _sigma, amount, _threshold);
+}
+
+void
+Unsharpmask::refreshParameters(Inkscape::Extension::Effect* module) {
+ _radius = module->get_param_float("radius");
+ _sigma = module->get_param_float("sigma");
+ _amount = module->get_param_float("amount");
+ _threshold = module->get_param_float("threshold");
+}
+
+#include "../clear-n_.h"
+
+void
+Unsharpmask::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Unsharp Mask") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.unsharpmask</id>\n"
+ "<param name=\"radius\" gui-text=\"" N_("Radius:") "\" type=\"float\" min=\"0.0\" max=\"50.0\">5.0</param>\n"
+ "<param name=\"sigma\" gui-text=\"" N_("Sigma:") "\" type=\"float\" min=\"0.0\" max=\"50.0\">5.0</param>\n"
+ "<param name=\"amount\" gui-text=\"" N_("Amount:") "\" type=\"float\" min=\"0.0\" max=\"100.0\">50.0</param>\n"
+ "<param name=\"threshold\" gui-text=\"" N_("Threshold:") "\" type=\"float\" min=\"0.0\" max=\"50.0\">5.0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Sharpen selected bitmap(s) using unsharp mask algorithms") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Unsharpmask());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/unsharpmask.h b/src/extension/internal/bitmap/unsharpmask.h
new file mode 100644
index 0000000..2fb4679
--- /dev/null
+++ b/src/extension/internal/bitmap/unsharpmask.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Unsharpmask : public ImageMagick
+{
+private:
+ double _radius;
+ double _sigma;
+ double _amount;
+ double _threshold;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/wave.cpp b/src/extension/internal/bitmap/wave.cpp
new file mode 100644
index 0000000..80ae768
--- /dev/null
+++ b/src/extension/internal/bitmap/wave.cpp
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "wave.h"
+#include <Magick++.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+void
+Wave::applyEffect(Magick::Image* image) {
+ image->wave(_amplitude, _wavelength);
+}
+
+void
+Wave::refreshParameters(Inkscape::Extension::Effect* module) {
+ _amplitude = module->get_param_float("amplitude");
+ _wavelength = module->get_param_float("wavelength");
+}
+
+#include "../clear-n_.h"
+
+void
+Wave::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Wave") "</name>\n"
+ "<id>org.inkscape.effect.bitmap.wave</id>\n"
+ "<param name=\"amplitude\" gui-text=\"" N_("Amplitude:") "\" type=\"float\" min=\"-720.0\" max=\"720.0\">25</param>\n"
+ "<param name=\"wavelength\" gui-text=\"" N_("Wavelength:") "\" type=\"float\" min=\"-720.0\" max=\"720.0\">150</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Raster") "\" />\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Alter selected bitmap(s) along sine wave") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Wave());
+ // clang-format on
+}
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bitmap/wave.h b/src/extension/internal/bitmap/wave.h
new file mode 100644
index 0000000..65342ce
--- /dev/null
+++ b/src/extension/internal/bitmap/wave.h
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2007 Authors:
+ * Christopher Brown <audiere@gmail.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "imagemagick.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Bitmap {
+
+class Wave : public ImageMagick
+{
+private:
+ double _amplitude;
+ double _wavelength;
+public:
+ void applyEffect(Magick::Image *image) override;
+ void refreshParameters(Inkscape::Extension::Effect *module) override;
+ static void init();
+};
+
+}; /* namespace Bitmap */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/bluredge.cpp b/src/extension/internal/bluredge.cpp
new file mode 100644
index 0000000..ed7f15c
--- /dev/null
+++ b/src/extension/internal/bluredge.cpp
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ \file bluredge.cpp
+
+ A plug-in to add an effect to blur the edges of an object.
+*/
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "bluredge.h"
+
+#include <vector>
+#include "desktop.h"
+#include "document.h"
+#include "selection.h"
+
+#include "preferences.h"
+#include "path-chemistry.h"
+#include "object/sp-item.h"
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "path/path-offset.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+
+/**
+ \brief A function to allocated anything -- just an example here
+ \param module Unused
+ \return Whether the load was successful
+*/
+bool
+BlurEdge::load (Inkscape::Extension::Extension */*module*/)
+{
+ // std::cout << "Hey, I'm Blur Edge, I'm loading!" << std::endl;
+ return TRUE;
+}
+
+/**
+ \brief This actually blurs the edge.
+ \param module The effect that was called (unused)
+ \param desktop What should be edited.
+*/
+void
+BlurEdge::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *view, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ auto desktop = dynamic_cast<SPDesktop *>(view);
+ if (!desktop) {
+ std::cerr << "BlurEdge::effect: view is not desktop!" << std::endl;
+ return;
+ }
+ Inkscape::Selection * selection = desktop->selection;
+
+ double width = module->get_param_float("blur-width");
+ int steps = module->get_param_int("num-steps");
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ double old_offset = prefs->getDouble("/options/defaultoffsetwidth/value", 1.0, "px");
+
+ // TODO need to properly refcount the items, at least
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+ selection->clear();
+
+ for(auto spitem : items) {
+ std::vector<Inkscape::XML::Node *> new_items(steps);
+ Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
+ Inkscape::XML::Node * new_group = xml_doc->createElement("svg:g");
+ spitem->getRepr()->parent()->appendChild(new_group);
+
+ double orig_opacity = sp_repr_css_double_property(sp_repr_css_attr(spitem->getRepr(), "style"), "opacity", 1.0);
+ char opacity_string[64];
+ g_ascii_formatd(opacity_string, sizeof(opacity_string), "%f",
+ orig_opacity / (steps));
+
+ for (int i = 0; i < steps; i++) {
+ double offset = (width / (float)(steps - 1) * (float)i) - (width / 2.0);
+
+ new_items[i] = spitem->getRepr()->duplicate(xml_doc);
+
+ SPCSSAttr * css = sp_repr_css_attr(new_items[i], "style");
+ sp_repr_css_set_property(css, "opacity", opacity_string);
+ sp_repr_css_change(new_items[i], css, "style");
+
+ new_group->appendChild(new_items[i]);
+ selection->add(new_items[i]);
+ selection->toCurves();
+ selection->removeLPESRecursive(true);
+ selection->unlinkRecursive(true);
+
+ if (offset < 0.0) {
+ /* Doing an inset here folks */
+ offset *= -1.0;
+ prefs->setDoubleUnit("/options/defaultoffsetwidth/value", offset, "px");
+ sp_selected_path_inset(desktop);
+ } else if (offset > 0.0) {
+ prefs->setDoubleUnit("/options/defaultoffsetwidth/value", offset, "px");
+ sp_selected_path_offset(desktop);
+ }
+
+ selection->clear();
+ }
+
+ Inkscape::GC::release(new_group);
+ }
+
+ prefs->setDoubleUnit("/options/defaultoffsetwidth/value", old_offset, "px");
+
+ selection->clear();
+ selection->add(items.begin(), items.end());
+
+ return;
+}
+
+Gtk::Widget *
+BlurEdge::prefs_effect(Inkscape::Extension::Effect * module, Inkscape::UI::View::View * /*view*/, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ return module->autogui(nullptr, nullptr, changeSignal);
+}
+
+#include "clear-n_.h"
+
+void
+BlurEdge::init ()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Inset/Outset Halo") "</name>\n"
+ "<id>org.inkscape.effect.bluredge</id>\n"
+ "<param name=\"blur-width\" gui-text=\"" N_("Width:") "\" gui-description=\"" N_("Width in px of the halo") "\" type=\"float\" min=\"1.0\" max=\"50.0\">1.0</param>\n"
+ "<param name=\"num-steps\" gui-text=\"" N_("Number of steps:") "\" gui-description=\"" N_("Number of inset/outset copies of the object to make") "\" type=\"int\" min=\"5\" max=\"100\">11</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Generate from Path") "\" />\n"
+ "</effects-menu>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n" , new BlurEdge());
+ // clang-format on
+ return;
+}
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/bluredge.h b/src/extension/internal/bluredge.h
new file mode 100644
index 0000000..b74b753
--- /dev/null
+++ b/src/extension/internal/bluredge.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+
+class Effect;
+class Extension;
+
+namespace Internal {
+
+/** \brief Implementation class of the GIMP gradient plugin. This mostly
+ just creates a namespace for the GIMP gradient plugin today.
+*/
+class BlurEdge : public Inkscape::Extension::Implementation::Implementation {
+
+public:
+ bool load(Inkscape::Extension::Extension *module) override;
+ void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+ Gtk::Widget * prefs_effect(Inkscape::Extension::Effect * module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+
+ static void init ();
+};
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/cairo-ps-out.cpp b/src/extension/internal/cairo-ps-out.cpp
new file mode 100644
index 0000000..abf76a9
--- /dev/null
+++ b/src/extension/internal/cairo-ps-out.cpp
@@ -0,0 +1,415 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A quick hack to use the Cairo renderer to write out a file. This
+ * then makes 'save as...' PS.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * Adib Taraben <theAdib@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004-2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cairo.h>
+#ifdef CAIRO_HAS_PS_SURFACE
+
+#include "cairo-ps.h"
+#include "cairo-ps-out.h"
+#include "cairo-render-context.h"
+#include "cairo-renderer.h"
+#include "latex-text-renderer.h"
+#include "path-chemistry.h"
+#include <print.h>
+#include "extension/system.h"
+#include "extension/print.h"
+#include "extension/db.h"
+#include "extension/output.h"
+#include "display/drawing.h"
+
+#include "display/curve.h"
+
+#include "object/sp-item.h"
+#include "object/sp-root.h"
+
+#include "io/sys.h"
+#include "document.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+bool CairoPsOutput::check (Inkscape::Extension::Extension * /*module*/)
+{
+ if (nullptr == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_PS)) {
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+bool CairoEpsOutput::check (Inkscape::Extension::Extension * /*module*/)
+{
+ if (nullptr == Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_EPS)) {
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+static bool
+ps_print_document_to_file(SPDocument *doc, gchar const *filename, unsigned int level, bool texttopath, bool omittext,
+ bool filtertobitmap, int resolution, const gchar * const exportId, bool exportDrawing, bool exportCanvas, double bleedmargin_px, bool eps = false)
+{
+ if (texttopath) {
+ assert(!omittext);
+ // Cairo's text-to-path method has numerical precision and font matching
+ // issues (https://gitlab.com/inkscape/inkscape/-/issues/1979).
+ // We get better results by using Inkscape's Object-to-Path method.
+ Inkscape::convert_text_to_curves(doc);
+ }
+
+ doc->ensureUpToDate();
+
+ SPRoot *root = doc->getRoot();
+ SPItem *base = nullptr;
+
+ bool pageBoundingBox = TRUE;
+ if (exportId && strcmp(exportId, "")) {
+ // we want to export the given item only
+ base = SP_ITEM(doc->getObjectById(exportId));
+ if (!base) {
+ throw Inkscape::Extension::Output::export_id_not_found(exportId);
+ }
+ root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only)
+ pageBoundingBox = exportCanvas;
+ }
+ else {
+ // we want to export the entire document from root
+ base = root;
+ pageBoundingBox = !exportDrawing;
+ }
+
+ if (!base)
+ return false;
+
+ Inkscape::Drawing drawing;
+ unsigned dkey = SPItem::display_key_new(1);
+ root->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
+
+ /* Create renderer and context */
+ CairoRenderer *renderer = new CairoRenderer();
+ CairoRenderContext *ctx = renderer->createContext();
+ ctx->setPSLevel(level);
+ ctx->setEPS(eps);
+ ctx->setTextToPath(texttopath);
+ ctx->setOmitText(omittext);
+ ctx->setFilterToBitmap(filtertobitmap);
+ ctx->setBitmapResolution(resolution);
+
+ bool ret = ctx->setPsTarget(filename);
+ if(ret) {
+ /* Render document */
+ ret = renderer->setupDocument(ctx, doc, pageBoundingBox, bleedmargin_px, base);
+ if (ret) {
+ renderer->renderItem(ctx, root);
+ ret = ctx->finish();
+ }
+ }
+
+ root->invoke_hide(dkey);
+
+ renderer->destroyContext(ctx);
+ delete renderer;
+
+ return ret;
+}
+
+
+/**
+ \brief This function calls the output module with the filename
+ \param mod unused
+ \param doc Document to be saved
+ \param filename Filename to save to (probably will end in .ps)
+*/
+void
+CairoPsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
+{
+ Inkscape::Extension::Extension * ext;
+ unsigned int ret;
+
+ ext = Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_PS);
+ if (ext == nullptr)
+ return;
+
+ int level = CAIRO_PS_LEVEL_2;
+ try {
+ const gchar *new_level = mod->get_param_optiongroup("PSlevel");
+ if((new_level != nullptr) && (g_ascii_strcasecmp("PS3", new_level) == 0)) {
+ level = CAIRO_PS_LEVEL_3;
+ }
+ } catch(...) {}
+
+ bool new_textToPath = FALSE;
+ try {
+ new_textToPath = (strcmp(mod->get_param_optiongroup("textToPath"), "paths") == 0);
+ } catch(...) {}
+
+ bool new_textToLaTeX = FALSE;
+ try {
+ new_textToLaTeX = (strcmp(mod->get_param_optiongroup("textToPath"), "LaTeX") == 0);
+ }
+ catch(...) {
+ g_warning("Parameter <textToLaTeX> might not exist");
+ }
+
+ bool new_blurToBitmap = FALSE;
+ try {
+ new_blurToBitmap = mod->get_param_bool("blurToBitmap");
+ } catch(...) {}
+
+ int new_bitmapResolution = 72;
+ try {
+ new_bitmapResolution = mod->get_param_int("resolution");
+ } catch(...) {}
+
+ bool new_areaPage = true;
+ try {
+ new_areaPage = (strcmp(mod->get_param_optiongroup("area"), "page") == 0);
+ } catch(...) {}
+
+ bool new_areaDrawing = !new_areaPage;
+
+ double bleedmargin_px = 0.;
+ try {
+ bleedmargin_px = mod->get_param_float("bleed");
+ } catch(...) {}
+
+ const gchar *new_exportId = nullptr;
+ try {
+ new_exportId = mod->get_param_string("exportId");
+ } catch(...) {}
+
+ // Create PS
+ {
+ gchar * final_name;
+ final_name = g_strdup_printf("> %s", filename);
+ ret = ps_print_document_to_file(doc, final_name, level, new_textToPath,
+ new_textToLaTeX, new_blurToBitmap,
+ new_bitmapResolution, new_exportId,
+ new_areaDrawing, new_areaPage,
+ bleedmargin_px);
+ g_free(final_name);
+
+ if (!ret)
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ // Create LaTeX file (if requested)
+ if (new_textToLaTeX) {
+ ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_areaDrawing, new_areaPage, 0., false);
+
+ if (!ret)
+ throw Inkscape::Extension::Output::save_failed();
+ }
+}
+
+
+/**
+ \brief This function calls the output module with the filename
+ \param mod unused
+ \param doc Document to be saved
+ \param filename Filename to save to (probably will end in .ps)
+*/
+void
+CairoEpsOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
+{
+ Inkscape::Extension::Extension * ext;
+ unsigned int ret;
+
+ ext = Inkscape::Extension::db.get(SP_MODULE_KEY_PRINT_CAIRO_EPS);
+ if (ext == nullptr)
+ return;
+
+ int level = CAIRO_PS_LEVEL_2;
+ try {
+ const gchar *new_level = mod->get_param_optiongroup("PSlevel");
+ if((new_level != nullptr) && (g_ascii_strcasecmp("PS3", new_level) == 0)) {
+ level = CAIRO_PS_LEVEL_3;
+ }
+ } catch(...) {}
+
+ bool new_textToPath = FALSE;
+ try {
+ new_textToPath = (strcmp(mod->get_param_optiongroup("textToPath"), "paths") == 0);
+ } catch(...) {}
+
+ bool new_textToLaTeX = FALSE;
+ try {
+ new_textToLaTeX = (strcmp(mod->get_param_optiongroup("textToPath"), "LaTeX") == 0);
+ }
+ catch(...) {
+ g_warning("Parameter <textToLaTeX> might not exist");
+ }
+
+ bool new_blurToBitmap = FALSE;
+ try {
+ new_blurToBitmap = mod->get_param_bool("blurToBitmap");
+ } catch(...) {}
+
+ int new_bitmapResolution = 72;
+ try {
+ new_bitmapResolution = mod->get_param_int("resolution");
+ } catch(...) {}
+
+ bool new_areaPage = true;
+ try {
+ new_areaPage = (strcmp(mod->get_param_optiongroup("area"), "page") == 0);
+ } catch(...) {}
+
+ bool new_areaDrawing = !new_areaPage;
+
+ double bleedmargin_px = 0.;
+ try {
+ bleedmargin_px = mod->get_param_float("bleed");
+ } catch(...) {}
+
+ const gchar *new_exportId = nullptr;
+ try {
+ new_exportId = mod->get_param_string("exportId");
+ } catch(...) {}
+
+ // Create EPS
+ {
+ gchar * final_name;
+ final_name = g_strdup_printf("> %s", filename);
+ ret = ps_print_document_to_file(doc, final_name, level, new_textToPath,
+ new_textToLaTeX, new_blurToBitmap,
+ new_bitmapResolution, new_exportId,
+ new_areaDrawing, new_areaPage,
+ bleedmargin_px, true);
+ g_free(final_name);
+
+ if (!ret)
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ // Create LaTeX file (if requested)
+ if (new_textToLaTeX) {
+ ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_areaDrawing, new_areaPage, 0., false);
+
+ if (!ret)
+ throw Inkscape::Extension::Output::save_failed();
+ }
+}
+
+
+bool
+CairoPsOutput::textToPath(Inkscape::Extension::Print * ext)
+{
+ return ext->get_param_bool("textToPath");
+}
+
+bool
+CairoEpsOutput::textToPath(Inkscape::Extension::Print * ext)
+{
+ return ext->get_param_bool("textToPath");
+}
+
+#include "clear-n_.h"
+
+/**
+ \brief A function allocate a copy of this function.
+
+ This is the definition of Cairo PS out. This function just
+ calls the extension system with the memory allocated XML that
+ describes the data.
+*/
+void
+CairoPsOutput::init ()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("PostScript") "</name>\n"
+ "<id>" SP_MODULE_KEY_PRINT_CAIRO_PS "</id>\n"
+ "<param name=\"PSlevel\" gui-text=\"" N_("Restrict to PS level:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value='PS3'>" N_("PostScript level 3") "</option>\n"
+ "<option value='PS2'>" N_("PostScript level 2") "</option>\n"
+ "</param>\n"
+ "<param name=\"textToPath\" gui-text=\"" N_("Text output options:") "\" type=\"optiongroup\" appearance=\"radio\">\n"
+ "<option value=\"embed\">" N_("Embed fonts") "</option>\n"
+ "<option value=\"paths\">" N_("Convert text to paths") "</option>\n"
+ "<option value=\"LaTeX\">" N_("Omit text in PDF and create LaTeX file") "</option>\n"
+ "</param>\n"
+ "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"bool\">true</param>\n"
+ "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">96</param>\n"
+ "<param name=\"area\" gui-text=\"" N_("Output page size") "\" type=\"optiongroup\" appearance=\"radio\" >\n"
+ "<option value=\"page\">" N_("Use document's page size") "</option>"
+ "<option value=\"drawing\">" N_("Use exported object's size") "</option>"
+ "</param>"
+ "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm):") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n"
+ "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n"
+ "<output>\n"
+ "<extension>.ps</extension>\n"
+ "<mimetype>image/x-postscript</mimetype>\n"
+ "<filetypename>" N_("PostScript (*.ps)") "</filetypename>\n"
+ "<filetypetooltip>" N_("PostScript File") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>", new CairoPsOutput());
+ // clang-format on
+
+ return;
+}
+
+/**
+ \brief A function allocate a copy of this function.
+
+ This is the definition of Cairo EPS out. This function just
+ calls the extension system with the memory allocated XML that
+ describes the data.
+*/
+void
+CairoEpsOutput::init ()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Encapsulated PostScript") "</name>\n"
+ "<id>" SP_MODULE_KEY_PRINT_CAIRO_EPS "</id>\n"
+ "<param name=\"PSlevel\" gui-text=\"" N_("Restrict to PS level:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value='PS3'>" N_("PostScript level 3") "</option>\n"
+ "<option value='PS2'>" N_("PostScript level 2") "</option>\n"
+ "</param>\n"
+ "<param name=\"textToPath\" gui-text=\"" N_("Text output options:") "\" type=\"optiongroup\" appearance=\"radio\">\n"
+ "<option value=\"embed\">" N_("Embed fonts") "</option>\n"
+ "<option value=\"paths\">" N_("Convert text to paths") "</option>\n"
+ "<option value=\"LaTeX\">" N_("Omit text in PDF and create LaTeX file") "</option>\n"
+ "</param>\n"
+ "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"bool\">true</param>\n"
+ "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">96</param>\n"
+ "<param name=\"area\" gui-text=\"" N_("Output page size") "\" type=\"optiongroup\" appearance=\"radio\" >\n"
+ "<option value=\"page\">" N_("Use document's page size") "</option>"
+ "<option value=\"drawing\">" N_("Use exported object's size") "</option>"
+ "</param>"
+ "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm)") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n"
+ "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n"
+ "<output>\n"
+ "<extension>.eps</extension>\n"
+ "<mimetype>image/x-e-postscript</mimetype>\n"
+ "<filetypename>" N_("Encapsulated PostScript (*.eps)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Encapsulated PostScript File") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>", new CairoEpsOutput());
+ // clang-format on
+
+ return;
+}
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* HAVE_CAIRO_PDF */
diff --git a/src/extension/internal/cairo-ps-out.h b/src/extension/internal/cairo-ps-out.h
new file mode 100644
index 0000000..ee58179
--- /dev/null
+++ b/src/extension/internal/cairo-ps-out.h
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A quick hack to use the print output to write out a file. This
+ * then makes 'save as...' PS.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * Adib Taraben <theAdib@gmail.com>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004-2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef EXTENSION_INTERNAL_CAIRO_PS_OUT_H
+#define EXTENSION_INTERNAL_CAIRO_PS_OUT_H
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class CairoPsOutput : Inkscape::Extension::Implementation::Implementation {
+
+public:
+ bool check(Inkscape::Extension::Extension *module) override;
+ void save(Inkscape::Extension::Output *mod,
+ SPDocument *doc,
+ gchar const *filename) override;
+ static void init();
+ bool textToPath(Inkscape::Extension::Print *ext) override;
+
+};
+
+class CairoEpsOutput : Inkscape::Extension::Implementation::Implementation {
+
+public:
+ bool check(Inkscape::Extension::Extension *module) override;
+ void save(Inkscape::Extension::Output *mod,
+ SPDocument *doc,
+ gchar const *uri) override;
+ static void init();
+ bool textToPath(Inkscape::Extension::Print *ext) override;
+
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* !EXTENSION_INTERNAL_CAIRO_PS_OUT_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/cairo-render-context.cpp b/src/extension/internal/cairo-render-context.cpp
new file mode 100644
index 0000000..063938b
--- /dev/null
+++ b/src/extension/internal/cairo-render-context.cpp
@@ -0,0 +1,2040 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Rendering with Cairo.
+ */
+/*
+ * Author:
+ * Miklos Erdelyi <erdelyim@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Miklos Erdelyi
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifndef PANGO_ENABLE_BACKEND
+#define PANGO_ENABLE_BACKEND
+#endif
+
+#ifndef PANGO_ENABLE_ENGINE
+#define PANGO_ENABLE_ENGINE
+#endif
+
+#include "cairo-render-context.h"
+
+#include <csignal>
+#include <cerrno>
+#include <2geom/pathvector.h>
+
+#include <glib.h>
+#include <glibmm/i18n.h>
+
+#include "display/drawing.h"
+#include "display/curve.h"
+#include "display/cairo-utils.h"
+
+#include "object/sp-item.h"
+#include "object/sp-item-group.h"
+#include "object/sp-hatch.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-mesh-gradient.h"
+#include "object/sp-pattern.h"
+#include "object/sp-mask.h"
+#include "object/sp-clippath.h"
+
+#include "util/units.h"
+#ifdef _WIN32
+#include "libnrtype/FontFactory.h" // USE_PANGO_WIN32
+#endif
+
+#include "cairo-renderer.h"
+#include "extension/system.h"
+
+#include "io/sys.h"
+
+#include "svg/stringstream.h"
+
+#include <cairo.h>
+
+// include support for only the compiled-in surface types
+#ifdef CAIRO_HAS_PDF_SURFACE
+#include <cairo-pdf.h>
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+#include <cairo-ps.h>
+#endif
+
+
+#ifdef CAIRO_HAS_FT_FONT
+#include <cairo-ft.h>
+#endif
+#ifdef CAIRO_HAS_WIN32_FONT
+#include <pango/pangowin32.h>
+#include <cairo-win32.h>
+#endif
+
+#include <pango/pangofc-fontmap.h>
+
+//#define TRACE(_args) g_printf _args
+//#define TRACE(_args) g_message _args
+#define TRACE(_args)
+//#define TEST(_args) _args
+#define TEST(_args)
+
+// FIXME: expose these from sp-clippath/mask.cpp
+/*struct SPClipPathView {
+ SPClipPathView *next;
+ unsigned int key;
+ Inkscape::DrawingItem *arenaitem;
+ Geom::OptRect bbox;
+};
+
+struct SPMaskView {
+ SPMaskView *next;
+ unsigned int key;
+ Inkscape::DrawingItem *arenaitem;
+ Geom::OptRect bbox;
+};*/
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+static cairo_status_t _write_callback(void *closure, const unsigned char *data, unsigned int length);
+
+CairoRenderContext::CairoRenderContext(CairoRenderer *parent) :
+ _dpi(72),
+ _pdf_level(1),
+ _ps_level(1),
+ _eps(false),
+ _is_texttopath(FALSE),
+ _is_omittext(FALSE),
+ _is_filtertobitmap(FALSE),
+ _is_show_page(false),
+ _bitmapresolution(72),
+ _stream(nullptr),
+ _is_valid(FALSE),
+ _vector_based_target(FALSE),
+ _cr(nullptr), // Cairo context
+ _surface(nullptr),
+ _target(CAIRO_SURFACE_TYPE_IMAGE),
+ _target_format(CAIRO_FORMAT_ARGB32),
+ _layout(nullptr),
+ _state(nullptr),
+ _renderer(parent),
+ _render_mode(RENDER_MODE_NORMAL),
+ _clip_mode(CLIP_MODE_MASK),
+ _omittext_state(EMPTY)
+{
+}
+
+CairoRenderContext::~CairoRenderContext()
+{
+ for (std::map<gpointer, cairo_font_face_t *>::const_iterator iter = font_table.begin(); iter != font_table.end(); ++iter)
+ font_data_free(iter->second);
+
+ if (_cr) cairo_destroy(_cr);
+ if (_surface) cairo_surface_destroy(_surface);
+ if (_layout) g_object_unref(_layout);
+}
+void CairoRenderContext::font_data_free(gpointer data)
+{
+ cairo_font_face_t *font_face = (cairo_font_face_t *)data;
+ if (font_face) {
+ cairo_font_face_destroy(font_face);
+ }
+}
+
+CairoRenderer* CairoRenderContext::getRenderer() const
+{
+ return _renderer;
+}
+
+CairoRenderState* CairoRenderContext::getCurrentState() const
+{
+ return _state;
+}
+
+CairoRenderState* CairoRenderContext::getParentState() const
+{
+ // if this is the root node just return it
+ if (_state_stack.size() == 1) {
+ return _state;
+ } else {
+ return _state_stack[_state_stack.size()-2];
+ }
+}
+
+void CairoRenderContext::setStateForStyle(SPStyle const *style)
+{
+ // only opacity & overflow is stored for now
+ _state->opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
+ _state->has_overflow = (style->overflow.set && style->overflow.value != SP_CSS_OVERFLOW_VISIBLE);
+ _state->has_filtereffect = (style->filter.set != 0) ? TRUE : FALSE;
+
+ if (style->fill.isPaintserver() || style->stroke.isPaintserver())
+ _state->merge_opacity = FALSE;
+
+ // disable rendering of opacity if there's a stroke on the fill
+ if (_state->merge_opacity
+ && !style->fill.isNone()
+ && !style->stroke.isNone())
+ _state->merge_opacity = FALSE;
+}
+
+/**
+ * \brief Creates a new render context which will be compatible with the given context's Cairo surface
+ *
+ * \param width width of the surface to be created
+ * \param height height of the surface to be created
+ */
+CairoRenderContext*
+CairoRenderContext::cloneMe(double width, double height) const
+{
+ g_assert( _is_valid );
+ g_assert( width > 0.0 && height > 0.0 );
+
+ CairoRenderContext *new_context = _renderer->createContext();
+ cairo_surface_t *surface = cairo_surface_create_similar(cairo_get_target(_cr), CAIRO_CONTENT_COLOR_ALPHA,
+ (int)ceil(width), (int)ceil(height));
+ new_context->_cr = cairo_create(surface);
+ new_context->_surface = surface;
+ new_context->_width = width;
+ new_context->_height = height;
+ new_context->_is_valid = TRUE;
+
+ return new_context;
+}
+
+CairoRenderContext* CairoRenderContext::cloneMe() const
+{
+ g_assert( _is_valid );
+
+ return cloneMe(_width, _height);
+}
+
+bool CairoRenderContext::setImageTarget(cairo_format_t format)
+{
+ // format cannot be set on an already initialized surface
+ if (_is_valid)
+ return false;
+
+ switch (format) {
+ case CAIRO_FORMAT_ARGB32:
+ case CAIRO_FORMAT_RGB24:
+ case CAIRO_FORMAT_A8:
+ case CAIRO_FORMAT_A1:
+ _target_format = format;
+ _target = CAIRO_SURFACE_TYPE_IMAGE;
+ return true;
+ break;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool CairoRenderContext::setPdfTarget(gchar const *utf8_fn)
+{
+#ifndef CAIRO_HAS_PDF_SURFACE
+ return false;
+#else
+ _target = CAIRO_SURFACE_TYPE_PDF;
+ _vector_based_target = TRUE;
+#endif
+
+ FILE *osf = nullptr;
+ FILE *osp = nullptr;
+
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError *error = nullptr;
+ gchar *local_fn = g_filename_from_utf8(utf8_fn,
+ -1, &bytesRead, &bytesWritten, &error);
+ gchar const *fn = local_fn;
+
+ /* TODO: Replace the below fprintf's with something that does the right thing whether in
+ * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of
+ * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the
+ * return code.
+ */
+ if (fn != nullptr) {
+ if (*fn == '|') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+#ifndef _WIN32
+ osp = popen(fn, "w");
+#else
+ osp = _popen(fn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osp;
+ } else if (*fn == '>') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+ Inkscape::IO::dump_fopen_call(fn, "K");
+ osf = Inkscape::IO::fopen_utf8name(fn, "w+");
+ if (!osf) {
+ fprintf(stderr, "inkscape: fopen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osf;
+ } else {
+ /* put cwd stuff in here */
+ gchar *qn = ( *fn
+ ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */
+ : g_strdup("lpr") );
+#ifndef _WIN32
+ osp = popen(qn, "w");
+#else
+ osp = _popen(qn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ qn, strerror(errno));
+ return false;
+ }
+ g_free(qn);
+ _stream = osp;
+ }
+ }
+
+ g_free(local_fn);
+
+ if (_stream) {
+ /* fixme: this is kinda icky */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_IGN);
+#endif
+ }
+
+ return true;
+}
+
+bool CairoRenderContext::setPsTarget(gchar const *utf8_fn)
+{
+#ifndef CAIRO_HAS_PS_SURFACE
+ return false;
+#else
+ _target = CAIRO_SURFACE_TYPE_PS;
+ _vector_based_target = TRUE;
+#endif
+
+ FILE *osf = nullptr;
+ FILE *osp = nullptr;
+
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError *error = nullptr;
+ gchar *local_fn = g_filename_from_utf8(utf8_fn,
+ -1, &bytesRead, &bytesWritten, &error);
+ gchar const *fn = local_fn;
+
+ /* TODO: Replace the below fprintf's with something that does the right thing whether in
+ * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of
+ * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the
+ * return code.
+ */
+ if (fn != nullptr) {
+ if (*fn == '|') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+#ifndef _WIN32
+ osp = popen(fn, "w");
+#else
+ osp = _popen(fn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osp;
+ } else if (*fn == '>') {
+ fn += 1;
+ while (isspace(*fn)) fn += 1;
+ Inkscape::IO::dump_fopen_call(fn, "K");
+ osf = Inkscape::IO::fopen_utf8name(fn, "w+");
+ if (!osf) {
+ fprintf(stderr, "inkscape: fopen(%s): %s\n",
+ fn, strerror(errno));
+ return false;
+ }
+ _stream = osf;
+ } else {
+ /* put cwd stuff in here */
+ gchar *qn = ( *fn
+ ? g_strdup_printf("lpr -P %s", fn) /* FIXME: quote fn */
+ : g_strdup("lpr") );
+#ifndef _WIN32
+ osp = popen(qn, "w");
+#else
+ osp = _popen(qn, "w");
+#endif
+ if (!osp) {
+ fprintf(stderr, "inkscape: popen(%s): %s\n",
+ qn, strerror(errno));
+ return false;
+ }
+ g_free(qn);
+ _stream = osp;
+ }
+ }
+
+ g_free(local_fn);
+
+ if (_stream) {
+ /* fixme: this is kinda icky */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_IGN);
+#endif
+ }
+
+ return true;
+}
+
+void CairoRenderContext::setPSLevel(unsigned int level)
+{
+ _ps_level = level;
+}
+
+void CairoRenderContext::setEPS(bool eps)
+{
+ _eps = eps;
+}
+
+unsigned int CairoRenderContext::getPSLevel()
+{
+ return _ps_level;
+}
+
+void CairoRenderContext::setPDFLevel(unsigned int level)
+{
+ _pdf_level = level;
+}
+
+void CairoRenderContext::setTextToPath(bool texttopath)
+{
+ _is_texttopath = texttopath;
+}
+
+void CairoRenderContext::setOmitText(bool omittext)
+{
+ _is_omittext = omittext;
+}
+
+bool CairoRenderContext::getOmitText()
+{
+ return _is_omittext;
+}
+
+void CairoRenderContext::setFilterToBitmap(bool filtertobitmap)
+{
+ _is_filtertobitmap = filtertobitmap;
+}
+
+bool CairoRenderContext::getFilterToBitmap()
+{
+ return _is_filtertobitmap;
+}
+
+void CairoRenderContext::setBitmapResolution(int resolution)
+{
+ _bitmapresolution = resolution;
+}
+
+int CairoRenderContext::getBitmapResolution()
+{
+ return _bitmapresolution;
+}
+
+cairo_surface_t*
+CairoRenderContext::getSurface()
+{
+ g_assert( _is_valid );
+
+ return _surface;
+}
+
+bool
+CairoRenderContext::saveAsPng(const char *file_name)
+{
+ cairo_status_t status = cairo_surface_write_to_png(_surface, file_name);
+ if (status)
+ return false;
+ else
+ return true;
+}
+
+void
+CairoRenderContext::setRenderMode(CairoRenderMode mode)
+{
+ switch (mode) {
+ case RENDER_MODE_NORMAL:
+ case RENDER_MODE_CLIP:
+ _render_mode = mode;
+ break;
+ default:
+ _render_mode = RENDER_MODE_NORMAL;
+ break;
+ }
+}
+
+CairoRenderContext::CairoRenderMode
+CairoRenderContext::getRenderMode() const
+{
+ return _render_mode;
+}
+
+void
+CairoRenderContext::setClipMode(CairoClipMode mode)
+{
+ switch (mode) {
+ case CLIP_MODE_PATH: // Clip is rendered as a path for vector output
+ case CLIP_MODE_MASK: // Clip is rendered as a bitmap for raster output.
+ _clip_mode = mode;
+ break;
+ default:
+ _clip_mode = CLIP_MODE_PATH;
+ break;
+ }
+}
+
+CairoRenderContext::CairoClipMode
+CairoRenderContext::getClipMode() const
+{
+ return _clip_mode;
+}
+
+CairoRenderState* CairoRenderContext::_createState()
+{
+ CairoRenderState *state = static_cast<CairoRenderState*>(g_try_malloc(sizeof(CairoRenderState)));
+ g_assert( state != nullptr );
+
+ state->has_filtereffect = FALSE;
+ state->merge_opacity = TRUE;
+ state->opacity = 1.0;
+ state->need_layer = FALSE;
+ state->has_overflow = FALSE;
+ state->parent_has_userspace = FALSE;
+ state->clip_path = nullptr;
+ state->mask = nullptr;
+
+ return state;
+}
+
+void CairoRenderContext::pushLayer()
+{
+ g_assert( _is_valid );
+
+ TRACE(("--pushLayer\n"));
+ cairo_push_group(_cr);
+
+ // clear buffer
+ if (!_vector_based_target) {
+ cairo_save(_cr);
+ cairo_set_operator(_cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint(_cr);
+ cairo_restore(_cr);
+ }
+}
+
+void
+CairoRenderContext::popLayer(cairo_operator_t composite)
+{
+ g_assert( _is_valid );
+
+ float opacity = _state->opacity;
+ TRACE(("--popLayer w/ opacity %f\n", opacity));
+
+ /*
+ At this point, the Cairo source is ready. A Cairo mask must be created if required.
+ Care must be taken of transformatons as Cairo, like PS and PDF, treats clip paths and
+ masks independently of the objects they effect while in SVG the clip paths and masks
+ are defined relative to the objects they are attached to.
+ Notes:
+ 1. An SVG object may have both a clip path and a mask!
+ 2. An SVG clip path can be composed of an object with a clip path. This is not handled properly.
+ 3. An SVG clipped or masked object may be first drawn off the page and then translated onto
+ the page (document). This is also not handled properly.
+ 4. The code converts all SVG masks to bitmaps. This shouldn't be necessary.
+ 5. Cairo expects a mask to use only the alpha channel. SVG masks combine the RGB luminance with
+ alpha. This is handled here by doing a pixel by pixel conversion.
+ */
+
+ SPClipPath *clip_path = _state->clip_path;
+ SPMask *mask = _state->mask;
+ if (clip_path || mask) {
+
+ CairoRenderContext *clip_ctx = nullptr;
+ cairo_surface_t *clip_mask = nullptr;
+
+ // Apply any clip path first
+ if (clip_path) {
+ TRACE((" Applying clip\n"));
+ if (_render_mode == RENDER_MODE_CLIP)
+ mask = nullptr; // disable mask when performing nested clipping
+
+ if (_vector_based_target) {
+ setClipMode(CLIP_MODE_PATH); // Vector
+ if (!mask) {
+ cairo_pop_group_to_source(_cr);
+ _renderer->applyClipPath(this, clip_path); // Uses cairo_clip()
+ if (opacity == 1.0)
+ cairo_paint(_cr);
+ else
+ cairo_paint_with_alpha(_cr, opacity);
+
+ } else {
+ // the clipPath will be applied before masking
+ }
+ } else {
+
+ // setup a new rendering context
+ clip_ctx = _renderer->createContext();
+ clip_ctx->setImageTarget(CAIRO_FORMAT_A8);
+ clip_ctx->setClipMode(CLIP_MODE_MASK); // Raster
+ // This code ties the clipping to the document coordinates. It doesn't allow
+ // for a clipped object initially drawn off the page and then translated onto
+ // the page.
+ if (!clip_ctx->setupSurface(_width, _height)) {
+ TRACE(("clip: setupSurface failed\n"));
+ _renderer->destroyContext(clip_ctx);
+ return;
+ }
+
+ // clear buffer
+ cairo_save(clip_ctx->_cr);
+ cairo_set_operator(clip_ctx->_cr, CAIRO_OPERATOR_CLEAR);
+ cairo_paint(clip_ctx->_cr);
+ cairo_restore(clip_ctx->_cr);
+
+ // If a mask won't be applied set opacity too. (The clip is represented by a solid Cairo mask.)
+ if (!mask)
+ cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, opacity);
+ else
+ cairo_set_source_rgba(clip_ctx->_cr, 1.0, 1.0, 1.0, 1.0);
+
+ // copy over the correct CTM
+ // It must be stored in item_transform of current state after pushState.
+ Geom::Affine item_transform;
+ if (_state->parent_has_userspace)
+ item_transform = getParentState()->transform * _state->item_transform;
+ else
+ item_transform = _state->item_transform;
+
+ // apply the clip path
+ clip_ctx->pushState();
+ clip_ctx->getCurrentState()->item_transform = item_transform;
+ _renderer->applyClipPath(clip_ctx, clip_path);
+ clip_ctx->popState();
+
+ clip_mask = clip_ctx->getSurface();
+ TEST(clip_ctx->saveAsPng("clip_mask.png"));
+
+ if (!mask) {
+ cairo_pop_group_to_source(_cr);
+ if (composite != CAIRO_OPERATOR_CLEAR){
+ cairo_set_operator(_cr, composite);
+ }
+ cairo_mask_surface(_cr, clip_mask, 0, 0);
+ _renderer->destroyContext(clip_ctx);
+ }
+ }
+ }
+
+ // Apply any mask second
+ if (mask) {
+ TRACE((" Applying mask\n"));
+ // create rendering context for mask
+ CairoRenderContext *mask_ctx = _renderer->createContext();
+
+ // Fix Me: This is a kludge. PDF and PS output is set to 72 dpi but the
+ // Cairo surface is expecting the mask to be 96 dpi.
+ float surface_width = _width;
+ float surface_height = _height;
+ if( _vector_based_target ) {
+ surface_width *= 4.0/3.0;
+ surface_height *= 4.0/3.0;
+ }
+ if (!mask_ctx->setupSurface( surface_width, surface_height )) {
+ TRACE(("mask: setupSurface failed\n"));
+ _renderer->destroyContext(mask_ctx);
+ return;
+ }
+ TRACE(("mask surface: %f x %f at %i dpi\n", surface_width, surface_height, _dpi ));
+
+ // Mask should start black, but it is created white.
+ cairo_set_source_rgba(mask_ctx->_cr, 0.0, 0.0, 0.0, 1.0);
+ cairo_rectangle(mask_ctx->_cr, 0, 0, surface_width, surface_height);
+ cairo_fill(mask_ctx->_cr);
+
+ // set rendering mode to normal
+ setRenderMode(RENDER_MODE_NORMAL);
+
+ // copy the correct CTM to mask context
+ /*
+ if (_state->parent_has_userspace)
+ mask_ctx->setTransform(getParentState()->transform);
+ else
+ mask_ctx->setTransform(_state->transform);
+ */
+ // This is probably not correct... but it seems to do the trick.
+ mask_ctx->setTransform(_state->item_transform);
+
+ // render mask contents to mask_ctx
+ _renderer->applyMask(mask_ctx, mask);
+
+ TEST(mask_ctx->saveAsPng("mask.png"));
+
+ // composite with clip mask
+ if (clip_path && _clip_mode == CLIP_MODE_MASK) {
+ cairo_mask_surface(mask_ctx->_cr, clip_mask, 0, 0);
+ _renderer->destroyContext(clip_ctx);
+ }
+
+ cairo_surface_t *mask_image = mask_ctx->getSurface();
+ int width = cairo_image_surface_get_width(mask_image);
+ int height = cairo_image_surface_get_height(mask_image);
+ int stride = cairo_image_surface_get_stride(mask_image);
+ unsigned char *pixels = cairo_image_surface_get_data(mask_image);
+
+ // In SVG, the rgb channels as well as the alpha channel is used in masking.
+ // In Cairo, only the alpha channel is used thus requiring this conversion.
+ // SVG specifies that RGB be converted to alpha using luminance-to-alpha.
+ // Notes: This calculation assumes linear RGB values. VERIFY COLOR SPACE!
+ // The incoming pixel values already include alpha, fill-opacity, etc.,
+ // however, opacity must still be applied.
+ TRACE(("premul w/ %f\n", opacity));
+ const float coeff_r = 0.2125 / 255.0;
+ const float coeff_g = 0.7154 / 255.0;
+ const float coeff_b = 0.0721 / 255.0;
+ for (int row = 0 ; row < height; row++) {
+ unsigned char *row_data = pixels + (row * stride);
+ for (int i = 0 ; i < width; i++) {
+ guint32 *pixel = reinterpret_cast<guint32 *>(row_data) + i;
+ float lum_alpha = (((*pixel & 0x00ff0000) >> 16) * coeff_r +
+ ((*pixel & 0x0000ff00) >> 8) * coeff_g +
+ ((*pixel & 0x000000ff) ) * coeff_b );
+ // lum_alpha can be slightly greater than 1 due to rounding errors...
+ // but this should be OK since it doesn't matter what the lower
+ // six hexadecimal numbers of *pixel are.
+ *pixel = (guint32)(0xff000000 * lum_alpha * opacity);
+ }
+ }
+
+ cairo_pop_group_to_source(_cr);
+ if (composite != CAIRO_OPERATOR_CLEAR){
+ cairo_set_operator(_cr, composite);
+ }
+ if (_clip_mode == CLIP_MODE_PATH) {
+ // we have to do the clipping after cairo_pop_group_to_source
+ _renderer->applyClipPath(this, clip_path);
+ }
+ // apply the mask onto the layer
+ cairo_mask_surface(_cr, mask_image, 0, 0);
+ _renderer->destroyContext(mask_ctx);
+ }
+ } else {
+ // No clip path or mask
+ cairo_pop_group_to_source(_cr);
+ if (composite != CAIRO_OPERATOR_CLEAR){
+ cairo_set_operator(_cr, composite);
+ }
+ if (opacity == 1.0)
+ cairo_paint(_cr);
+ else
+ cairo_paint_with_alpha(_cr, opacity);
+ }
+}
+void CairoRenderContext::tagBegin(const char* l){
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4)
+ char* link = g_strdup_printf("uri='%s'", l);
+ cairo_tag_begin(_cr, CAIRO_TAG_LINK, link);
+ g_free(link);
+#endif
+}
+
+void CairoRenderContext::tagEnd(){
+#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4)
+ cairo_tag_end(_cr, CAIRO_TAG_LINK);
+#endif
+}
+
+
+void
+CairoRenderContext::addClipPath(Geom::PathVector const &pv, SPIEnum<SPWindRule> const *fill_rule)
+{
+ g_assert( _is_valid );
+
+ // here it should be checked whether the current clip winding changed
+ // so we could switch back to masked clipping
+ if (fill_rule->value == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ addPathVector(pv);
+}
+
+void
+CairoRenderContext::addClippingRect(double x, double y, double width, double height)
+{
+ g_assert( _is_valid );
+
+ cairo_rectangle(_cr, x, y, width, height);
+ cairo_clip(_cr);
+}
+
+bool
+CairoRenderContext::setupSurface(double width, double height)
+{
+ // Is the surface already set up?
+ if (_is_valid)
+ return true;
+
+ if (_vector_based_target && _stream == nullptr)
+ return false;
+
+ _width = width;
+ _height = height;
+
+ cairo_surface_t *surface = nullptr;
+ cairo_matrix_t ctm;
+ cairo_matrix_init_identity (&ctm);
+ switch (_target) {
+ case CAIRO_SURFACE_TYPE_IMAGE:
+ surface = cairo_image_surface_create(_target_format, (int)ceil(width), (int)ceil(height));
+ break;
+#ifdef CAIRO_HAS_PDF_SURFACE
+ case CAIRO_SURFACE_TYPE_PDF:
+ surface = cairo_pdf_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height);
+ cairo_pdf_surface_restrict_to_version(surface, (cairo_pdf_version_t)_pdf_level);
+ break;
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+ case CAIRO_SURFACE_TYPE_PS:
+ surface = cairo_ps_surface_create_for_stream(Inkscape::Extension::Internal::_write_callback, _stream, width, height);
+ if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) {
+ return FALSE;
+ }
+ cairo_ps_surface_restrict_to_level(surface, (cairo_ps_level_t)_ps_level);
+ cairo_ps_surface_set_eps(surface, (cairo_bool_t) _eps);
+ break;
+#endif
+ default:
+ return false;
+ break;
+ }
+
+ _setSurfaceMetadata(surface);
+
+ return _finishSurfaceSetup (surface, &ctm);
+}
+
+bool
+CairoRenderContext::setSurfaceTarget(cairo_surface_t *surface, bool is_vector, cairo_matrix_t *ctm)
+{
+ if (_is_valid || !surface)
+ return false;
+
+ _vector_based_target = is_vector;
+ bool ret = _finishSurfaceSetup (surface, ctm);
+ if (ret)
+ cairo_surface_reference (surface);
+ return ret;
+}
+
+bool
+CairoRenderContext::_finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t *ctm)
+{
+ if(surface == nullptr) {
+ return false;
+ }
+ if(CAIRO_STATUS_SUCCESS != cairo_surface_status(surface)) {
+ return false;
+ }
+
+ _cr = cairo_create(surface);
+ if(CAIRO_STATUS_SUCCESS != cairo_status(_cr)) {
+ return false;
+ }
+ if (ctm)
+ cairo_set_matrix(_cr, ctm);
+ _surface = surface;
+
+ if (_vector_based_target) {
+ cairo_scale(_cr, Inkscape::Util::Quantity::convert(1, "px", "pt"), Inkscape::Util::Quantity::convert(1, "px", "pt"));
+ } else if (cairo_surface_get_content(_surface) != CAIRO_CONTENT_ALPHA) {
+ // set background color on non-alpha surfaces
+ // TODO: bgcolor should be derived from SPDocument (see IconImpl)
+ cairo_set_source_rgb(_cr, 1.0, 1.0, 1.0);
+ cairo_rectangle(_cr, 0, 0, _width, _height);
+ cairo_fill(_cr);
+ }
+
+ _is_valid = TRUE;
+
+ return true;
+}
+
+void
+CairoRenderContext::_setSurfaceMetadata(cairo_surface_t *surface)
+{
+ switch (_target) {
+#if defined CAIRO_HAS_PDF_SURFACE && CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 15, 4)
+ case CAIRO_SURFACE_TYPE_PDF:
+ if (!_metadata.title.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_TITLE, _metadata.title.c_str());
+ }
+ if (!_metadata.author.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_AUTHOR, _metadata.author.c_str());
+ }
+ if (!_metadata.subject.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_SUBJECT, _metadata.subject.c_str());
+ }
+ if (!_metadata.keywords.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_KEYWORDS, _metadata.keywords.c_str());
+ }
+ if (!_metadata.creator.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATOR, _metadata.creator.c_str());
+ }
+ if (!_metadata.cdate.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_CREATE_DATE, _metadata.cdate.c_str());
+ }
+ if (!_metadata.mdate.empty()) {
+ cairo_pdf_surface_set_metadata(surface, CAIRO_PDF_METADATA_MOD_DATE, _metadata.mdate.c_str());
+ }
+ break;
+#endif
+#if defined CAIRO_HAS_PS_SURFACE
+ case CAIRO_SURFACE_TYPE_PS:
+ if (!_metadata.title.empty()) {
+ cairo_ps_surface_dsc_comment(surface, (Glib::ustring("%%Title: ") + _metadata.title).c_str());
+ }
+ if (!_metadata.copyright.empty()) {
+ cairo_ps_surface_dsc_comment(surface, (Glib::ustring("%%Copyright: ") + _metadata.copyright).c_str());
+ }
+ break;
+#endif
+ default:
+ g_warning("unsupported target %d\n", _target);
+ }
+}
+
+/**
+ * Each page that's made should call finishPage to complete it.
+ */
+bool
+CairoRenderContext::finishPage()
+{
+ g_assert(_is_valid);
+ if (!_vector_based_target)
+ return false;
+
+ // Protect against finish() showing one too many pages.
+ if (!_is_show_page) {
+ cairo_show_page(_cr);
+ _is_show_page = true;
+ }
+
+ auto status = cairo_status(_cr);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ g_critical("error while rendering page: %s", cairo_status_to_string(status));
+ return false;
+ }
+ return true;
+}
+
+/**
+ * When writing multiple pages, resize the next page.
+ */
+bool
+CairoRenderContext::nextPage(double width, double height, char const *label)
+{
+ g_assert(_is_valid);
+ if (!_vector_based_target)
+ return false;
+
+ _width = width;
+ _height = height;
+ _is_show_page = false;
+ cairo_pdf_surface_set_size(_surface, width, height);
+
+ if (label) {
+ cairo_pdf_surface_set_page_label(_surface, label);
+ }
+
+ auto status = cairo_surface_status(_surface);
+ if (status != CAIRO_STATUS_SUCCESS) {
+ g_critical("error while sizing page: %s", cairo_status_to_string(status));
+ return false;
+ }
+ return true;
+}
+
+
+bool
+CairoRenderContext::finish(bool finish_surface)
+{
+ g_assert( _is_valid );
+
+ if (_vector_based_target && !_is_show_page && finish_surface)
+ cairo_show_page(_cr);
+
+ cairo_status_t status = cairo_status(_cr);
+ if (status != CAIRO_STATUS_SUCCESS)
+ g_critical("error while rendering output: %s", cairo_status_to_string(status));
+
+ cairo_destroy(_cr);
+ _cr = nullptr;
+
+ if (finish_surface)
+ cairo_surface_finish(_surface);
+ status = cairo_surface_status(_surface);
+ cairo_surface_destroy(_surface);
+ _surface = nullptr;
+
+ if (_layout)
+ g_object_unref(_layout);
+
+ _is_valid = FALSE;
+
+ if (_vector_based_target && _stream) {
+ /* Flush stream to be sure. */
+ (void) fflush(_stream);
+
+ fclose(_stream);
+ _stream = nullptr;
+ }
+
+ if (status == CAIRO_STATUS_SUCCESS)
+ return true;
+ else
+ return false;
+}
+
+void
+CairoRenderContext::transform(Geom::Affine const &transform)
+{
+ g_assert( _is_valid );
+
+ cairo_matrix_t matrix;
+ _initCairoMatrix(&matrix, transform);
+ cairo_transform(_cr, &matrix);
+
+ // store new CTM
+ _state->transform = getTransform();
+}
+
+void
+CairoRenderContext::setTransform(Geom::Affine const &transform)
+{
+ g_assert( _is_valid );
+
+ cairo_matrix_t matrix;
+ _initCairoMatrix(&matrix, transform);
+ cairo_set_matrix(_cr, &matrix);
+ _state->transform = transform;
+}
+
+Geom::Affine CairoRenderContext::getTransform() const
+{
+ g_assert( _is_valid );
+
+ cairo_matrix_t ctm;
+ cairo_get_matrix(_cr, &ctm);
+ Geom::Affine ret;
+ ret[0] = ctm.xx;
+ ret[1] = ctm.yx;
+ ret[2] = ctm.xy;
+ ret[3] = ctm.yy;
+ ret[4] = ctm.x0;
+ ret[5] = ctm.y0;
+ return ret;
+}
+
+Geom::Affine CairoRenderContext::getParentTransform() const
+{
+ g_assert( _is_valid );
+
+ CairoRenderState *parent_state = getParentState();
+ return parent_state->transform;
+}
+
+void CairoRenderContext::pushState()
+{
+ g_assert( _is_valid );
+
+ cairo_save(_cr);
+
+ CairoRenderState *new_state = _createState();
+ // copy current state's transform
+ new_state->transform = _state->transform;
+ _state_stack.push_back(new_state);
+ _state = new_state;
+}
+
+void CairoRenderContext::popState()
+{
+ g_assert( _is_valid );
+
+ cairo_restore(_cr);
+
+ g_free(_state_stack.back());
+ _state_stack.pop_back();
+
+ g_assert( !_state_stack.empty());
+ _state = _state_stack.back();
+}
+
+static bool pattern_hasItemChildren(SPPattern *pat)
+{
+ for (auto& child: pat->children) {
+ if (SP_IS_ITEM (&child)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+cairo_pattern_t*
+CairoRenderContext::_createPatternPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox)
+{
+ g_assert( SP_IS_PATTERN(paintserver) );
+
+ SPPattern *pat = SP_PATTERN (paintserver);
+
+ Geom::Affine ps2user, pcs2dev;
+ ps2user = Geom::identity();
+ pcs2dev = Geom::identity();
+
+ double x = pat->x();
+ double y = pat->y();
+ double width = pat->width();
+ double height = pat->height();
+ double bbox_width_scaler;
+ double bbox_height_scaler;
+
+ TRACE(("%f x %f pattern\n", width, height));
+
+ if (pbox && pat->patternUnits() == SPPattern::UNITS_OBJECTBOUNDINGBOX) {
+ bbox_width_scaler = pbox->width();
+ bbox_height_scaler = pbox->height();
+ ps2user[4] = x * bbox_width_scaler + pbox->left();
+ ps2user[5] = y * bbox_height_scaler + pbox->top();
+ } else {
+ bbox_width_scaler = 1.0;
+ bbox_height_scaler = 1.0;
+ ps2user[4] = x;
+ ps2user[5] = y;
+ }
+
+ // apply pattern transformation
+ Geom::Affine pattern_transform(pat->getTransform());
+ ps2user *= pattern_transform;
+ Geom::Point ori (ps2user[4], ps2user[5]);
+
+ // create pattern contents coordinate system
+ if (pat->viewBox_set) {
+ Geom::Rect view_box = *pat->viewbox();
+
+ double x, y, w, h;
+ x = 0;
+ y = 0;
+ w = width * bbox_width_scaler;
+ h = height * bbox_height_scaler;
+
+ //calculatePreserveAspectRatio(pat->aspect_align, pat->aspect_clip, view_width, view_height, &x, &y, &w, &h);
+ pcs2dev[0] = w / view_box.width();
+ pcs2dev[3] = h / view_box.height();
+ pcs2dev[4] = x - view_box.left() * pcs2dev[0];
+ pcs2dev[5] = y - view_box.top() * pcs2dev[3];
+ } else if (pbox && pat->patternContentUnits() == SPPattern::UNITS_OBJECTBOUNDINGBOX) {
+ pcs2dev[0] = pbox->width();
+ pcs2dev[3] = pbox->height();
+ }
+
+ // Calculate the size of the surface which has to be created
+#define SUBPIX_SCALE 100
+ // Cairo requires an integer pattern surface width/height.
+ // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel.
+ // Multiply by SUBPIX_SCALE to allow for less than a pixel precision
+ double surface_width = MAX(ceil(SUBPIX_SCALE * bbox_width_scaler * width - 0.5), 1);
+ double surface_height = MAX(ceil(SUBPIX_SCALE * bbox_height_scaler * height - 0.5), 1);
+ TRACE(("pattern surface size: %f x %f\n", surface_width, surface_height));
+ // create new rendering context
+ CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height);
+
+ // adjust the size of the painted pattern to fit exactly the created surface
+ // this has to be done because of the rounding to obtain an integer pattern surface width/height
+ double scale_width = surface_width / (bbox_width_scaler * width);
+ double scale_height = surface_height / (bbox_height_scaler * height);
+ if (scale_width != 1.0 || scale_height != 1.0 || _vector_based_target) {
+ TRACE(("needed to scale with %f %f\n", scale_width, scale_height));
+ pcs2dev *= Geom::Scale(SUBPIX_SCALE,SUBPIX_SCALE);
+ ps2user *= Geom::Scale(1.0/SUBPIX_SCALE,1.0/SUBPIX_SCALE);
+ }
+
+ // despite scaling up/down by subpixel scaler, the origin point of the pattern must be the same
+ ps2user[4] = ori[Geom::X];
+ ps2user[5] = ori[Geom::Y];
+
+ pattern_ctx->setTransform(pcs2dev);
+ pattern_ctx->pushState();
+
+ // create drawing and group
+ Inkscape::Drawing drawing;
+ unsigned dkey = SPItem::display_key_new(1);
+
+ // show items and render them
+ for (SPPattern *pat_i = pat; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
+ if (pat_i && pattern_hasItemChildren(pat_i)) { // find the first one with item children
+ for (auto& child: pat_i->children) {
+ if (SP_IS_ITEM(&child)) {
+ SP_ITEM(&child)->invoke_show(drawing, dkey, SP_ITEM_REFERENCE_FLAGS);
+ _renderer->renderItem(pattern_ctx, SP_ITEM(&child));
+ }
+ }
+ break; // do not go further up the chain if children are found
+ }
+ }
+
+ pattern_ctx->popState();
+
+ // setup a cairo_pattern_t
+ cairo_surface_t *pattern_surface = pattern_ctx->getSurface();
+ TEST(pattern_ctx->saveAsPng("pattern.png"));
+ cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface);
+ cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT);
+
+ // set pattern transformation
+ cairo_matrix_t pattern_matrix;
+ _initCairoMatrix(&pattern_matrix, ps2user);
+ cairo_matrix_invert(&pattern_matrix);
+ cairo_pattern_set_matrix(result, &pattern_matrix);
+
+ delete pattern_ctx;
+
+ // hide all items
+ for (SPPattern *pat_i = pat; pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
+ if (pat_i && pattern_hasItemChildren(pat_i)) { // find the first one with item children
+ for (auto& child: pat_i->children) {
+ if (SP_IS_ITEM(&child)) {
+ SP_ITEM(&child)->invoke_hide(dkey);
+ }
+ }
+ break; // do not go further up the chain if children are found
+ }
+ }
+
+ return result;
+}
+
+cairo_pattern_t*
+CairoRenderContext::_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox) {
+ SPHatch const *hatch = dynamic_cast<SPHatch const *>(paintserver);
+ g_assert( hatch );
+
+ g_assert(hatch->pitch() > 0);
+
+ // create drawing and group
+ Inkscape::Drawing drawing;
+ unsigned dkey = SPItem::display_key_new(1);
+
+ // TODO need to refactor 'evil' referenced code for const correctness.
+ SPHatch *evil = const_cast<SPHatch *>(hatch);
+ evil->show(drawing, dkey, pbox);
+
+ SPHatch::RenderInfo render_info = hatch->calculateRenderInfo(dkey);
+ Geom::Rect tile_rect = render_info.tile_rect;
+
+ // Cairo requires an integer pattern surface width/height.
+ // Subtract 0.5 to prevent small rounding errors from increasing pattern size by one pixel.
+ // Multiply by SUBPIX_SCALE to allow for less than a pixel precision
+ const int subpix_scale = 10;
+ double surface_width = MAX(ceil(subpix_scale * tile_rect.width() - 0.5), 1);
+ double surface_height = MAX(ceil(subpix_scale * tile_rect.height() - 0.5), 1);
+ Geom::Affine drawing_scale = Geom::Scale(surface_width / tile_rect.width(), surface_height / tile_rect.height());
+ Geom::Affine drawing_transform = Geom::Translate(-tile_rect.min()) * drawing_scale;
+
+ Geom::Affine child_transform = render_info.child_transform;
+ child_transform *= drawing_transform;
+
+ //The rendering of hatch overflow is implemented by repeated drawing
+ //of hatch paths over one strip. Within each iteration paths are moved by pitch value.
+ //The movement progresses from right to left. This gives the same result
+ //as drawing whole strips in left-to-right order.
+ gdouble overflow_right_strip = 0.0;
+ int overflow_steps = 1;
+ Geom::Affine overflow_transform;
+ if (hatch->style->overflow.computed == SP_CSS_OVERFLOW_VISIBLE) {
+ Geom::Interval bounds = hatch->bounds();
+ overflow_right_strip = floor(bounds.max() / hatch->pitch()) * hatch->pitch();
+ overflow_steps = ceil((overflow_right_strip - bounds.min()) / hatch->pitch()) + 1;
+ overflow_transform = Geom::Translate(hatch->pitch(), 0.0);
+ }
+
+ CairoRenderContext *pattern_ctx = cloneMe(surface_width, surface_height);
+ pattern_ctx->setTransform(child_transform);
+ pattern_ctx->transform(Geom::Translate(-overflow_right_strip, 0.0));
+ pattern_ctx->pushState();
+
+ std::vector<SPHatchPath *> children(evil->hatchPaths());
+
+ for (int i = 0; i < overflow_steps; i++) {
+ for (auto path : children) {
+ _renderer->renderHatchPath(pattern_ctx, *path, dkey);
+ }
+ pattern_ctx->transform(overflow_transform);
+ }
+
+ pattern_ctx->popState();
+
+ // setup a cairo_pattern_t
+ cairo_surface_t *pattern_surface = pattern_ctx->getSurface();
+ TEST(pattern_ctx->saveAsPng("hatch.png"));
+ cairo_pattern_t *result = cairo_pattern_create_for_surface(pattern_surface);
+ cairo_pattern_set_extend(result, CAIRO_EXTEND_REPEAT);
+
+ Geom::Affine pattern_transform;
+ pattern_transform = render_info.pattern_to_user_transform.inverse() * drawing_transform;
+ ink_cairo_pattern_set_matrix(result, pattern_transform);
+
+ evil->hide(dkey);
+
+ delete pattern_ctx;
+ return result;
+}
+
+cairo_pattern_t*
+CairoRenderContext::_createPatternForPaintServer(SPPaintServer const *const paintserver,
+ Geom::OptRect const &pbox, float alpha)
+{
+ cairo_pattern_t *pattern = nullptr;
+ bool apply_bbox2user = FALSE;
+
+ auto const paintserver_mutable = const_cast<SPPaintServer *>(paintserver);
+
+ if (auto lg = dynamic_cast<SPLinearGradient *>(paintserver_mutable)) {
+
+ lg->ensureVector(); // when exporting from commandline, vector is not built
+
+ Geom::Point p1 (lg->x1.computed, lg->y1.computed);
+ Geom::Point p2 (lg->x2.computed, lg->y2.computed);
+ if (pbox && lg->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX) {
+ // convert to userspace
+ Geom::Affine bbox2user(pbox->width(), 0, 0, pbox->height(), pbox->left(), pbox->top());
+ p1 *= bbox2user;
+ p2 *= bbox2user;
+ }
+
+ // create linear gradient pattern
+ pattern = cairo_pattern_create_linear(p1[Geom::X], p1[Geom::Y], p2[Geom::X], p2[Geom::Y]);
+
+ // add stops
+ for (gint i = 0; unsigned(i) < lg->vector.stops.size(); i++) {
+ float rgb[3];
+ lg->vector.stops[i].color.get_rgb_floatv(rgb);
+ cairo_pattern_add_color_stop_rgba(pattern, lg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], lg->vector.stops[i].opacity * alpha);
+ }
+ } else if (auto rg = dynamic_cast<SPRadialGradient *>(paintserver_mutable)) {
+
+ rg->ensureVector(); // when exporting from commandline, vector is not built
+
+ Geom::Point c (rg->cx.computed, rg->cy.computed);
+ Geom::Point f (rg->fx.computed, rg->fy.computed);
+ double r = rg->r.computed;
+ double fr = rg->fr.computed;
+ if (pbox && rg->getUnits() == SP_GRADIENT_UNITS_OBJECTBOUNDINGBOX)
+ apply_bbox2user = true;
+
+ // create radial gradient pattern
+ pattern = cairo_pattern_create_radial(f[Geom::X], f[Geom::Y], fr, c[Geom::X], c[Geom::Y], r);
+
+ // add stops
+ for (gint i = 0; unsigned(i) < rg->vector.stops.size(); i++) {
+ float rgb[3];
+ rg->vector.stops[i].color.get_rgb_floatv(rgb);
+ cairo_pattern_add_color_stop_rgba(pattern, rg->vector.stops[i].offset, rgb[0], rgb[1], rgb[2], rg->vector.stops[i].opacity * alpha);
+ }
+ } else if (auto mg = dynamic_cast<SPMeshGradient *>(paintserver_mutable)) {
+ pattern = mg->pattern_new(_cr, pbox, 1.0);
+ } else if (SP_IS_PATTERN (paintserver)) {
+ pattern = _createPatternPainter(paintserver, pbox);
+ } else if ( dynamic_cast<SPHatch const *>(paintserver) ) {
+ pattern = _createHatchPainter(paintserver, pbox);
+ } else {
+ return nullptr;
+ }
+
+ if (pattern && SP_IS_GRADIENT(paintserver)) {
+ auto g = dynamic_cast<SPGradient *>(paintserver_mutable);
+
+ // set extend type
+ SPGradientSpread spread = g->fetchSpread();
+ switch (spread) {
+ case SP_GRADIENT_SPREAD_REPEAT: {
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
+ break;
+ }
+ case SP_GRADIENT_SPREAD_REFLECT: { // not supported by cairo-pdf yet
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REFLECT);
+ break;
+ }
+ case SP_GRADIENT_SPREAD_PAD: { // not supported by cairo-pdf yet
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD);
+ break;
+ }
+ default: {
+ cairo_pattern_set_extend(pattern, CAIRO_EXTEND_NONE);
+ break;
+ }
+ }
+
+ cairo_matrix_t pattern_matrix;
+ if (g->gradientTransform_set) {
+ // apply gradient transformation
+ cairo_matrix_init(&pattern_matrix,
+ g->gradientTransform[0], g->gradientTransform[1],
+ g->gradientTransform[2], g->gradientTransform[3],
+ g->gradientTransform[4], g->gradientTransform[5]);
+ } else {
+ cairo_matrix_init_identity (&pattern_matrix);
+ }
+
+ if (apply_bbox2user) {
+ // convert to userspace
+ cairo_matrix_t bbox2user;
+ cairo_matrix_init (&bbox2user, pbox->width(), 0, 0, pbox->height(), pbox->left(), pbox->top());
+ cairo_matrix_multiply (&pattern_matrix, &bbox2user, &pattern_matrix);
+ }
+ cairo_matrix_invert(&pattern_matrix); // because Cairo expects a userspace->patternspace matrix
+ cairo_pattern_set_matrix(pattern, &pattern_matrix);
+ }
+
+ return pattern;
+}
+
+void
+CairoRenderContext::_setFillStyle(SPStyle const *const style, Geom::OptRect const &pbox)
+{
+ g_return_if_fail( !style->fill.set
+ || style->fill.isColor()
+ || style->fill.isPaintserver() );
+
+ float alpha = SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+ if (_state->merge_opacity) {
+ alpha *= _state->opacity;
+ TRACE(("merged op=%f\n", alpha));
+ }
+
+ SPPaintServer const *paint_server = style->getFillPaintServer();
+ if (paint_server && paint_server->isValid()) {
+
+ g_assert(SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))
+ || SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style))
+ || dynamic_cast<SPHatch *>(SP_STYLE_FILL_SERVER(style)));
+
+ cairo_pattern_t *pattern = _createPatternForPaintServer(paint_server, pbox, alpha);
+ if (pattern) {
+ cairo_set_source(_cr, pattern);
+ cairo_pattern_destroy(pattern);
+ }
+ } else if (style->fill.colorSet) {
+ float rgb[3];
+ style->fill.value.color.get_rgb_floatv(rgb);
+
+ cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha);
+
+ } else { // unset fill is black
+ g_assert(!style->fill.set
+ || (paint_server && !paint_server->isValid()));
+
+ cairo_set_source_rgba(_cr, 0, 0, 0, alpha);
+ }
+}
+
+void
+CairoRenderContext::_setStrokeStyle(SPStyle const *style, Geom::OptRect const &pbox)
+{
+ float alpha = SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
+ if (_state->merge_opacity)
+ alpha *= _state->opacity;
+
+ if (style->stroke.isColor() || (style->stroke.isPaintserver() && !style->getStrokePaintServer()->isValid())) {
+ float rgb[3];
+ style->stroke.value.color.get_rgb_floatv(rgb);
+
+ cairo_set_source_rgba(_cr, rgb[0], rgb[1], rgb[2], alpha);
+ } else {
+ g_assert( style->stroke.isPaintserver()
+ || SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))
+ || SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style))
+ || dynamic_cast<SPHatch *>(SP_STYLE_STROKE_SERVER(style)));
+
+ cairo_pattern_t *pattern = _createPatternForPaintServer(SP_STYLE_STROKE_SERVER(style), pbox, alpha);
+
+ if (pattern) {
+ cairo_set_source(_cr, pattern);
+ cairo_pattern_destroy(pattern);
+ }
+ }
+
+ if (!style->stroke_dasharray.values.empty())
+ {
+ size_t ndashes = style->stroke_dasharray.values.size();
+ double* dashes =(double*)malloc(ndashes*sizeof(double));
+ for( unsigned i = 0; i < ndashes; ++i ) {
+ dashes[i] = style->stroke_dasharray.values[i].value;
+ }
+ cairo_set_dash(_cr, dashes, ndashes, style->stroke_dashoffset.value);
+ free(dashes);
+ } else {
+ cairo_set_dash(_cr, nullptr, 0, 0.0); // disable dashing
+ }
+
+ // This allows hairlines to be drawn properly in PDF, PS, Win32-Print, etc.
+ // It requires the following pull request in Cairo:
+ // https://gitlab.freedesktop.org/cairo/cairo/merge_requests/21
+ if (style->stroke_extensions.hairline) {
+ ink_cairo_set_hairline(_cr);
+ } else {
+ cairo_set_line_width(_cr, style->stroke_width.computed);
+ }
+
+ // set line join type
+ cairo_line_join_t join = CAIRO_LINE_JOIN_MITER;
+ switch (style->stroke_linejoin.computed) {
+ case SP_STROKE_LINEJOIN_MITER:
+ join = CAIRO_LINE_JOIN_MITER;
+ break;
+ case SP_STROKE_LINEJOIN_ROUND:
+ join = CAIRO_LINE_JOIN_ROUND;
+ break;
+ case SP_STROKE_LINEJOIN_BEVEL:
+ join = CAIRO_LINE_JOIN_BEVEL;
+ break;
+ }
+ cairo_set_line_join(_cr, join);
+
+ // set line cap type
+ cairo_line_cap_t cap = CAIRO_LINE_CAP_BUTT;
+ switch (style->stroke_linecap.computed) {
+ case SP_STROKE_LINECAP_BUTT:
+ cap = CAIRO_LINE_CAP_BUTT;
+ break;
+ case SP_STROKE_LINECAP_ROUND:
+ cap = CAIRO_LINE_CAP_ROUND;
+ break;
+ case SP_STROKE_LINECAP_SQUARE:
+ cap = CAIRO_LINE_CAP_SQUARE;
+ break;
+ }
+ cairo_set_line_cap(_cr, cap);
+ cairo_set_miter_limit(_cr, MAX(1, style->stroke_miterlimit.value));
+}
+
+void
+CairoRenderContext::_prepareRenderGraphic()
+{
+ // Only PDFLaTeX supports importing a single page of a graphics file,
+ // so only PDF backend gets interleaved text/graphics
+ if (_is_omittext && _target == CAIRO_SURFACE_TYPE_PDF && _render_mode != RENDER_MODE_CLIP) {
+ if (_omittext_state == NEW_PAGE_ON_GRAPHIC) {
+ // better set this immediately (not sure if masks applied during "popLayer" could call
+ // this function, too, triggering the same code again in error
+ _omittext_state = GRAPHIC_ON_TOP;
+
+ // As we can not emit the page in the middle of a layer (aka group) - it will not be fully painted yet! -
+ // the following basically mirrors the calls in CairoRenderer::renderItem (but in reversed order)
+ // - first traverse all saved states in reversed order (i.e. from deepest nesting to the top)
+ // and apply clipping / masking to layers on the way (this is done in popLayer)
+ // - then emit the page using cairo_show_page()
+ // - finally restore the previous state with proper transforms and appropriate layers again
+ //
+ // TODO: While this appears to be an ugly hack it seems to work
+ // Somebody with a more intimate understanding of cairo and the renderer implementation might
+ // be able to implement this in a cleaner way, though.
+ int stack_size = _state_stack.size();
+ for (int i = stack_size-1; i > 0; i--) {
+ if (_state_stack[i]->need_layer)
+ popLayer();
+ cairo_restore(_cr);
+ _state = _state_stack[i-1];
+ }
+
+ cairo_show_page(_cr);
+
+ for (int i = 1; i < stack_size; i++) {
+ cairo_save(_cr);
+ _state = _state_stack[i];
+ if (_state->need_layer)
+ pushLayer();
+ setTransform(_state->transform);
+ }
+ }
+ _omittext_state = GRAPHIC_ON_TOP;
+ }
+}
+
+void
+CairoRenderContext::_prepareRenderText()
+{
+ // Only PDFLaTeX supports importing a single page of a graphics file,
+ // so only PDF backend gets interleaved text/graphics
+ if (_is_omittext && _target == CAIRO_SURFACE_TYPE_PDF) {
+ if (_omittext_state == GRAPHIC_ON_TOP)
+ _omittext_state = NEW_PAGE_ON_GRAPHIC;
+ }
+}
+
+/* We need CairoPaintOrder as markers are rendered in a separate step and may be rendered
+ * in between fill and stroke.
+ */
+bool
+CairoRenderContext::renderPathVector(Geom::PathVector const & pathv, SPStyle const *style, Geom::OptRect const &pbox, CairoPaintOrder order)
+{
+ g_assert( _is_valid );
+
+ _prepareRenderGraphic();
+
+ if (_render_mode == RENDER_MODE_CLIP) {
+ if (_clip_mode == CLIP_MODE_PATH) {
+ addClipPath(pathv, &style->fill_rule);
+ } else {
+ setPathVector(pathv);
+ if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value) {
+ cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ }
+ cairo_fill(_cr);
+ TEST(cairo_surface_write_to_png (_surface, "pathmask.png"));
+ }
+ return true;
+ }
+
+ bool no_fill = style->fill.isNone() || style->fill_opacity.value == 0 ||
+ order == STROKE_ONLY;
+ bool no_stroke = style->stroke.isNone() || (!style->stroke_extensions.hairline && style->stroke_width.computed < 1e-9) ||
+ style->stroke_opacity.value == 0 || order == FILL_ONLY;
+
+ if (no_fill && no_stroke)
+ return true;
+
+ bool need_layer = ( !_state->merge_opacity && !_state->need_layer &&
+ ( _state->opacity != 1.0 || _state->clip_path != nullptr || _state->mask != nullptr ) );
+ bool blend = false;
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value != SP_CSS_BLEND_NORMAL) {
+ need_layer = true;
+ blend = true;
+ }
+ if (!need_layer)
+ cairo_save(_cr);
+ else
+ pushLayer();
+
+ if (!no_fill) {
+ if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ }
+
+ setPathVector(pathv);
+
+ if (!no_fill && (order == STROKE_OVER_FILL || order == FILL_ONLY)) {
+ _setFillStyle(style, pbox);
+
+ if (no_stroke)
+ cairo_fill(_cr);
+ else
+ cairo_fill_preserve(_cr);
+ }
+
+ if (!no_stroke) {
+ _setStrokeStyle(style, pbox);
+
+ if (no_fill || order == STROKE_OVER_FILL)
+ cairo_stroke(_cr);
+ else
+ cairo_stroke_preserve(_cr);
+ }
+
+ if (!no_fill && order == FILL_OVER_STROKE) {
+ _setFillStyle(style, pbox);
+
+ cairo_fill(_cr);
+ }
+
+ if (need_layer) {
+ if (blend) {
+ popLayer(ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ } else {
+ popLayer();
+ }
+ } else {
+ cairo_restore(_cr);
+ }
+
+ return true;
+}
+
+bool CairoRenderContext::renderImage(Inkscape::Pixbuf *pb,
+ Geom::Affine const &image_transform, SPStyle const *style)
+{
+ g_assert( _is_valid );
+
+ if (_render_mode == RENDER_MODE_CLIP) {
+ return true;
+ }
+
+ _prepareRenderGraphic();
+
+ int w = pb->width();
+ int h = pb->height();
+
+ // TODO: reenable merge_opacity if useful
+
+ cairo_surface_t *image_surface = pb->getSurfaceRaw();
+ if (cairo_surface_status(image_surface)) {
+ TRACE(("Image surface creation failed:\n%s\n", cairo_status_to_string(cairo_surface_status(image_surface))));
+ return false;
+ }
+
+ cairo_save(_cr);
+
+ // scaling by width & height is not needed because it will be done by Cairo
+ transform(image_transform);
+
+ cairo_set_source_surface(_cr, image_surface, 0.0, 0.0);
+
+ // set clip region so that the pattern will not be repeated (bug in Cairo-PDF)
+ if (_vector_based_target) {
+ cairo_new_path(_cr);
+ cairo_rectangle(_cr, 0, 0, w, h);
+ cairo_clip(_cr);
+ }
+
+ // Cairo filter method will be mapped to PS/PDF 'interpolate' true/false).
+ // See cairo-pdf-surface.c
+ if (style) {
+ // See: http://www.w3.org/TR/SVG/painting.html#ImageRenderingProperty
+ // https://drafts.csswg.org/css-images-3/#the-image-rendering
+ // style.h/style.cpp, drawing-image.cpp
+ //
+ // CSS 3 defines:
+ // 'optimizeSpeed' as alias for "pixelated"
+ // 'optimizeQuality' as alias for "smooth"
+ switch (style->image_rendering.computed) {
+ case SP_CSS_IMAGE_RENDERING_OPTIMIZESPEED:
+ case SP_CSS_IMAGE_RENDERING_PIXELATED:
+ // we don't have an implementation for crisp-edges, but it should *not* smooth or blur
+ case SP_CSS_IMAGE_RENDERING_CRISPEDGES:
+ cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_NEAREST);
+ break;
+ case SP_CSS_IMAGE_RENDERING_OPTIMIZEQUALITY:
+ case SP_CSS_IMAGE_RENDERING_AUTO:
+ default:
+ cairo_pattern_set_filter(cairo_get_source(_cr), CAIRO_FILTER_BEST);
+ break;
+ }
+ }
+
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value) {
+ cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ }
+
+ cairo_paint(_cr);
+
+ cairo_restore(_cr);
+ return true;
+}
+
+#define GLYPH_ARRAY_SIZE 64
+
+// TODO investigate why the font is being ignored:
+unsigned int CairoRenderContext::_showGlyphs(cairo_t *cr, PangoFont * /*font*/, std::vector<CairoGlyphInfo> const &glyphtext, bool path)
+{
+ cairo_glyph_t glyph_array[GLYPH_ARRAY_SIZE];
+ cairo_glyph_t *glyphs = glyph_array;
+ unsigned int num_glyphs = glyphtext.size();
+ if (num_glyphs > GLYPH_ARRAY_SIZE) {
+ glyphs = (cairo_glyph_t*)g_try_malloc(sizeof(cairo_glyph_t) * num_glyphs);
+ if(glyphs == nullptr) {
+ g_warning("CairorenderContext::_showGlyphs: can not allocate memory for %d glyphs.", num_glyphs);
+ return 0;
+ }
+ }
+
+ unsigned int num_invalid_glyphs = 0;
+ unsigned int i = 0; // is a counter for indexing the glyphs array, only counts the valid glyphs
+ for (const auto & it_info : glyphtext) {
+ // skip glyphs which are PANGO_GLYPH_EMPTY (0x0FFFFFFF)
+ // or have the PANGO_GLYPH_UNKNOWN_FLAG (0x10000000) set
+ if (it_info.index == 0x0FFFFFFF || it_info.index & 0x10000000) {
+ TRACE(("INVALID GLYPH found\n"));
+ g_message("Invalid glyph found, continuing...");
+ num_invalid_glyphs++;
+ continue;
+ }
+ glyphs[i].index = it_info.index;
+ glyphs[i].x = it_info.x;
+ glyphs[i].y = it_info.y;
+ i++;
+ }
+
+ if (path) {
+ cairo_glyph_path(cr, glyphs, num_glyphs - num_invalid_glyphs);
+ } else {
+ cairo_show_glyphs(cr, glyphs, num_glyphs - num_invalid_glyphs);
+ }
+
+ if (num_glyphs > GLYPH_ARRAY_SIZE) {
+ g_free(glyphs);
+ }
+
+ return num_glyphs - num_invalid_glyphs;
+}
+
+bool
+CairoRenderContext::renderGlyphtext(PangoFont *font, Geom::Affine const &font_matrix,
+ std::vector<CairoGlyphInfo> const &glyphtext, SPStyle const *style)
+{
+
+ _prepareRenderText();
+ if (_is_omittext)
+ return true;
+
+ // create a cairo_font_face from PangoFont
+ // double size = style->font_size.computed; /// \fixme why is this variable never used?
+ gpointer fonthash = (gpointer)font;
+ cairo_font_face_t *font_face = nullptr;
+ if(font_table.find(fonthash)!=font_table.end())
+ font_face = font_table[fonthash];
+
+ FcPattern *fc_pattern = nullptr;
+
+#ifdef USE_PANGO_WIN32
+# ifdef CAIRO_HAS_WIN32_FONT
+ LOGFONTA *lfa = pango_win32_font_logfont(font);
+ LOGFONTW lfw;
+
+ ZeroMemory(&lfw, sizeof(LOGFONTW));
+ memcpy(&lfw, lfa, sizeof(LOGFONTA));
+ MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, lfa->lfFaceName, LF_FACESIZE, lfw.lfFaceName, LF_FACESIZE);
+
+ if(font_face == NULL) {
+ font_face = cairo_win32_font_face_create_for_logfontw(&lfw);
+ font_table[fonthash] = font_face;
+ }
+# endif
+#else
+# ifdef CAIRO_HAS_FT_FONT
+ PangoFcFont *fc_font = PANGO_FC_FONT(font);
+ fc_pattern = fc_font->font_pattern;
+ if(font_face == nullptr) {
+ font_face = cairo_ft_font_face_create_for_pattern(fc_pattern);
+ font_table[fonthash] = font_face;
+ }
+# endif
+#endif
+
+ cairo_save(_cr);
+ cairo_set_font_face(_cr, font_face);
+
+ //if (fc_pattern && FcPatternGetDouble(fc_pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch)
+ // size = 12.0;
+
+ // set the given font matrix
+ cairo_matrix_t matrix;
+ _initCairoMatrix(&matrix, font_matrix);
+ cairo_set_font_matrix(_cr, &matrix);
+
+ if (_render_mode == RENDER_MODE_CLIP) {
+ if (_clip_mode == CLIP_MODE_MASK) {
+ if (style->fill_rule.computed == SP_WIND_RULE_EVENODD) {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_EVEN_ODD);
+ } else {
+ cairo_set_fill_rule(_cr, CAIRO_FILL_RULE_WINDING);
+ }
+ _showGlyphs(_cr, font, glyphtext, FALSE);
+ } else {
+ // just add the glyph paths to the current context
+ _showGlyphs(_cr, font, glyphtext, TRUE);
+ }
+ } else {
+
+ bool fill = false;
+ if (style->fill.isColor() || style->fill.isPaintserver()) {
+ fill = true;
+ }
+
+ bool stroke = false;
+ if (style->stroke.isColor() || style->stroke.isPaintserver()) {
+ stroke = true;
+ }
+
+ if (style->mix_blend_mode.set && style->mix_blend_mode.value) {
+ cairo_set_operator(_cr, ink_css_blend_to_cairo_operator(style->mix_blend_mode.value));
+ }
+
+ // Text never has markers
+ bool stroke_over_fill = true;
+ if ( (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL) ||
+
+ (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL) ||
+
+ (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL) ) {
+ stroke_over_fill = false;
+ }
+
+ bool have_path = false;
+ if (fill && stroke_over_fill) {
+ _setFillStyle(style, Geom::OptRect());
+ if (_is_texttopath) {
+ _showGlyphs(_cr, font, glyphtext, true);
+ if (stroke) {
+ cairo_fill_preserve(_cr);
+ have_path = true;
+ } else {
+ cairo_fill(_cr);
+ }
+ } else {
+ _showGlyphs(_cr, font, glyphtext, false);
+ }
+ }
+
+ if (stroke) {
+ _setStrokeStyle(style, Geom::OptRect());
+ if (!have_path) {
+ _showGlyphs(_cr, font, glyphtext, true);
+ }
+ if (fill && _is_texttopath && !stroke_over_fill) {
+ cairo_stroke_preserve(_cr);
+ have_path = true;
+ } else {
+ cairo_stroke(_cr);
+ }
+ }
+
+ if (fill && !stroke_over_fill) {
+ _setFillStyle(style, Geom::OptRect());
+ if (_is_texttopath) {
+ if (!have_path) {
+ // Could happen if both 'stroke' and 'stroke_over_fill' are false
+ _showGlyphs(_cr, font, glyphtext, true);
+ }
+ cairo_fill(_cr);
+ } else {
+ _showGlyphs(_cr, font, glyphtext, false);
+ }
+ }
+
+ }
+
+ cairo_restore(_cr);
+
+// if (font_face)
+// cairo_font_face_destroy(font_face);
+
+ return true;
+}
+
+/* Helper functions */
+
+void
+CairoRenderContext::setPathVector(Geom::PathVector const &pv)
+{
+ cairo_new_path(_cr);
+ addPathVector(pv);
+}
+
+void
+CairoRenderContext::addPathVector(Geom::PathVector const &pv)
+{
+ feed_pathvector_to_cairo(_cr, pv);
+}
+
+void
+CairoRenderContext::_concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0)
+{
+ cairo_matrix_t matrix;
+
+ cairo_matrix_init(&matrix, xx, yx, xy, yy, x0, y0);
+ cairo_transform(cr, &matrix);
+}
+
+void
+CairoRenderContext::_initCairoMatrix(cairo_matrix_t *matrix, Geom::Affine const &transform)
+{
+ matrix->xx = transform[0];
+ matrix->yx = transform[1];
+ matrix->xy = transform[2];
+ matrix->yy = transform[3];
+ matrix->x0 = transform[4];
+ matrix->y0 = transform[5];
+}
+
+void
+CairoRenderContext::_concatTransform(cairo_t *cr, Geom::Affine const &transform)
+{
+ _concatTransform(cr, transform[0], transform[1],
+ transform[2], transform[3],
+ transform[4], transform[5]);
+}
+
+static cairo_status_t
+_write_callback(void *closure, const unsigned char *data, unsigned int length)
+{
+ size_t written;
+ FILE *file = (FILE*)closure;
+
+ written = fwrite (data, 1, length, file);
+
+ if (written == length)
+ return CAIRO_STATUS_SUCCESS;
+ else
+ return CAIRO_STATUS_WRITE_ERROR;
+}
+
+#include "clear-n_.h"
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#undef TRACE
+#undef TEST
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/cairo-render-context.h b/src/extension/internal/cairo-render-context.h
new file mode 100644
index 0000000..ccb46db
--- /dev/null
+++ b/src/extension/internal/cairo-render-context.h
@@ -0,0 +1,269 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef EXTENSION_INTERNAL_CAIRO_RENDER_CONTEXT_H_SEEN
+#define EXTENSION_INTERNAL_CAIRO_RENDER_CONTEXT_H_SEEN
+
+/** \file
+ * Declaration of CairoRenderContext, a class used for rendering with Cairo.
+ */
+/*
+ * Authors:
+ * Miklos Erdelyi <erdelyim@gmail.com>
+ *
+ * Copyright (C) 2006 Miklos Erdelyi
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/extension.h"
+#include <set>
+#include <string>
+
+#include <2geom/forward.h>
+#include <2geom/affine.h>
+
+#include "style-internal.h" // SPIEnum
+
+#include <cairo.h>
+
+class SPClipPath;
+class SPMask;
+
+typedef struct _PangoFont PangoFont;
+typedef struct _PangoLayout PangoLayout;
+
+namespace Inkscape {
+class Pixbuf;
+
+namespace Extension {
+namespace Internal {
+
+class CairoRenderer;
+class CairoRenderContext;
+struct CairoRenderState;
+struct CairoGlyphInfo;
+
+// Holds info for rendering a glyph
+struct CairoGlyphInfo {
+ unsigned long index;
+ double x;
+ double y;
+};
+
+struct CairoRenderState {
+ unsigned int merge_opacity : 1; // whether fill/stroke opacity can be mul'd with item opacity
+ unsigned int need_layer : 1; // whether object is masked, clipped, and/or has a non-zero opacity
+ unsigned int has_overflow : 1;
+ unsigned int parent_has_userspace : 1; // whether the parent's ctm should be applied
+ float opacity;
+ bool has_filtereffect;
+ Geom::Affine item_transform; // this item's item->transform, for correct clipping
+
+ SPClipPath *clip_path;
+ SPMask* mask;
+
+ Geom::Affine transform; // the CTM
+};
+
+// Metadata to set on the cairo surface (if the surface supports it)
+struct CairoRenderContextMetadata {
+ Glib::ustring title = "";
+ Glib::ustring author = "";
+ Glib::ustring subject = "";
+ Glib::ustring keywords = "";
+ Glib::ustring copyright = "";
+ Glib::ustring creator = "";
+ Glib::ustring cdate = ""; // currently unused
+ Glib::ustring mdate = ""; // currently unused
+};
+
+class CairoRenderContext {
+ friend class CairoRenderer;
+public:
+ CairoRenderContext *cloneMe() const;
+ CairoRenderContext *cloneMe(double width, double height) const;
+ bool finish(bool finish_surface = true);
+ bool finishPage();
+ bool nextPage(double width, double height, char const *label);
+
+ CairoRenderer *getRenderer() const;
+ cairo_t *getCairoContext() const;
+
+ enum CairoRenderMode {
+ RENDER_MODE_NORMAL,
+ RENDER_MODE_CLIP
+ };
+
+ enum CairoClipMode {
+ CLIP_MODE_PATH,
+ CLIP_MODE_MASK
+ };
+
+ bool setImageTarget(cairo_format_t format);
+ bool setPdfTarget(gchar const *utf8_fn);
+ bool setPsTarget(gchar const *utf8_fn);
+ /** Set the cairo_surface_t from an external source */
+ bool setSurfaceTarget(cairo_surface_t *surface, bool is_vector, cairo_matrix_t *ctm=nullptr);
+
+ void setPSLevel(unsigned int level);
+ void setEPS(bool eps);
+ unsigned int getPSLevel();
+ void setPDFLevel(unsigned int level);
+ void setTextToPath(bool texttopath);
+ bool getTextToPath();
+ void setOmitText(bool omittext);
+ bool getOmitText();
+ void setFilterToBitmap(bool filtertobitmap);
+ bool getFilterToBitmap();
+ void setBitmapResolution(int resolution);
+ int getBitmapResolution();
+
+ /** Creates the cairo_surface_t for the context with the
+ given width, height and with the currently set target
+ surface type. Also sets supported metadata on the surface. */
+ bool setupSurface(double width, double height);
+
+ cairo_surface_t *getSurface();
+
+ /** Saves the contents of the context to a PNG file. */
+ bool saveAsPng(const char *file_name);
+
+ /** On targets supporting multiple pages, sends subsequent rendering to a new page*/
+ void newPage();
+
+ /* Render/clip mode setting/query */
+ void setRenderMode(CairoRenderMode mode);
+ CairoRenderMode getRenderMode() const;
+ void setClipMode(CairoClipMode mode);
+ CairoClipMode getClipMode() const;
+
+ void addPathVector(Geom::PathVector const &pv);
+ void setPathVector(Geom::PathVector const &pv);
+
+ void pushLayer();
+ void popLayer(cairo_operator_t composite = CAIRO_OPERATOR_CLEAR);
+
+ void tagBegin(const char* link);
+ void tagEnd();
+
+ /* Graphics state manipulation */
+ void pushState();
+ void popState();
+ CairoRenderState *getCurrentState() const;
+ CairoRenderState *getParentState() const;
+ void setStateForStyle(SPStyle const *style);
+
+ void transform(Geom::Affine const &transform);
+ void setTransform(Geom::Affine const &transform);
+ Geom::Affine getTransform() const;
+ Geom::Affine getParentTransform() const;
+
+ /* Clipping methods */
+ void addClipPath(Geom::PathVector const &pv, SPIEnum<SPWindRule> const *fill_rule);
+ void addClippingRect(double x, double y, double width, double height);
+
+ /* Rendering methods */
+ enum CairoPaintOrder {
+ STROKE_OVER_FILL,
+ FILL_OVER_STROKE,
+ FILL_ONLY,
+ STROKE_ONLY
+ };
+
+ bool renderPathVector(Geom::PathVector const &pathv, SPStyle const *style, Geom::OptRect const &pbox, CairoPaintOrder order = STROKE_OVER_FILL);
+ bool renderImage(Inkscape::Pixbuf *pb,
+ Geom::Affine const &image_transform, SPStyle const *style);
+ bool renderGlyphtext(PangoFont *font, Geom::Affine const &font_matrix,
+ std::vector<CairoGlyphInfo> const &glyphtext, SPStyle const *style);
+
+ /* More general rendering methods will have to be added (like fill, stroke) */
+
+protected:
+ CairoRenderContext(CairoRenderer *renderer);
+ virtual ~CairoRenderContext();
+
+ enum CairoOmitTextPageState {
+ EMPTY,
+ GRAPHIC_ON_TOP,
+ NEW_PAGE_ON_GRAPHIC
+ };
+
+ float _width;
+ float _height;
+ unsigned short _dpi;
+ unsigned int _pdf_level;
+ unsigned int _ps_level;
+ bool _eps;
+ bool _is_texttopath;
+ bool _is_omittext;
+ bool _is_filtertobitmap;
+ bool _is_show_page;
+ int _bitmapresolution;
+
+ FILE *_stream;
+
+ unsigned int _is_valid : 1;
+ unsigned int _vector_based_target : 1;
+
+ cairo_t *_cr; // Cairo context
+ cairo_surface_t *_surface;
+ cairo_surface_type_t _target;
+ cairo_format_t _target_format;
+ PangoLayout *_layout;
+
+ unsigned int _clip_rule : 8;
+ unsigned int _clip_winding_failed : 1;
+
+ std::vector<CairoRenderState *> _state_stack;
+ CairoRenderState *_state; // the current state
+
+ CairoRenderer *_renderer;
+
+ CairoRenderMode _render_mode;
+ CairoClipMode _clip_mode;
+
+ CairoOmitTextPageState _omittext_state;
+
+ CairoRenderContextMetadata _metadata;
+
+ cairo_pattern_t *_createPatternForPaintServer(SPPaintServer const *const paintserver,
+ Geom::OptRect const &pbox, float alpha);
+ cairo_pattern_t *_createPatternPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox);
+ cairo_pattern_t *_createHatchPainter(SPPaintServer const *const paintserver, Geom::OptRect const &pbox);
+
+ unsigned int _showGlyphs(cairo_t *cr, PangoFont *font, std::vector<CairoGlyphInfo> const &glyphtext, bool is_stroke);
+
+ bool _finishSurfaceSetup(cairo_surface_t *surface, cairo_matrix_t *ctm = nullptr);
+ void _setSurfaceMetadata(cairo_surface_t *surface);
+
+ void _setFillStyle(SPStyle const *style, Geom::OptRect const &pbox);
+ void _setStrokeStyle(SPStyle const *style, Geom::OptRect const &pbox);
+
+ void _initCairoMatrix(cairo_matrix_t *matrix, Geom::Affine const &transform);
+ void _concatTransform(cairo_t *cr, double xx, double yx, double xy, double yy, double x0, double y0);
+ void _concatTransform(cairo_t *cr, Geom::Affine const &transform);
+
+ void _prepareRenderGraphic();
+ void _prepareRenderText();
+
+ std::map<gpointer, cairo_font_face_t *> font_table;
+ static void font_data_free(gpointer data);
+
+ CairoRenderState *_createState();
+};
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* !EXTENSION_INTERNAL_CAIRO_RENDER_CONTEXT_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/cairo-renderer-pdf-out.cpp b/src/extension/internal/cairo-renderer-pdf-out.cpp
new file mode 100644
index 0000000..f5adc09
--- /dev/null
+++ b/src/extension/internal/cairo-renderer-pdf-out.cpp
@@ -0,0 +1,335 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A quick hack to use the Cairo renderer to write out a file. This
+ * then makes 'save as...' PDF.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004-2010 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cairo.h>
+#ifdef CAIRO_HAS_PDF_SURFACE
+
+#include "cairo-renderer-pdf-out.h"
+#include "cairo-render-context.h"
+#include "cairo-renderer.h"
+#include "latex-text-renderer.h"
+#include "path-chemistry.h"
+#include <print.h>
+#include "extension/system.h"
+#include "extension/print.h"
+#include "extension/db.h"
+#include "extension/output.h"
+
+#include "display/drawing.h"
+#include "display/curve.h"
+
+#include "object/sp-item.h"
+#include "object/sp-root.h"
+#include "object/sp-page.h"
+
+#include <2geom/affine.h>
+#include "page-manager.h"
+#include "document.h"
+
+#include "util/units.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+bool CairoRendererPdfOutput::check(Inkscape::Extension::Extension * /*module*/)
+{
+ bool result = true;
+
+ if (nullptr == Inkscape::Extension::db.get("org.inkscape.output.pdf.cairorenderer")) {
+ result = false;
+ }
+
+ return result;
+}
+
+static bool
+pdf_render_document_to_file(SPDocument *doc, gchar const *filename, unsigned int level,
+ bool texttopath, bool omittext, bool filtertobitmap, int resolution,
+ const gchar * const exportId, bool exportDrawing, bool exportCanvas, double bleedmargin_px)
+{
+ if (texttopath) {
+ assert(!omittext);
+ // Cairo's text-to-path method has numerical precision and font matching
+ // issues (https://gitlab.com/inkscape/inkscape/-/issues/1979).
+ // We get better results by using Inkscape's Object-to-Path method.
+ Inkscape::convert_text_to_curves(doc);
+ }
+
+ doc->ensureUpToDate();
+
+ SPRoot *root = doc->getRoot();
+ SPItem *base = nullptr;
+
+ bool pageBoundingBox = TRUE;
+ if (exportId && strcmp(exportId, "")) {
+ // we want to export the given item only
+ base = SP_ITEM(doc->getObjectById(exportId));
+ if (!base) {
+ throw Inkscape::Extension::Output::export_id_not_found(exportId);
+ }
+ root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only)
+ pageBoundingBox = exportCanvas;
+ }
+ else {
+ // we want to export the entire document from root
+ base = root;
+ pageBoundingBox = !exportDrawing;
+ }
+
+ if (!base) {
+ return false;
+ }
+
+ /* Create new arena */
+ Inkscape::Drawing drawing;
+ drawing.setExact(true);
+ unsigned dkey = SPItem::display_key_new(1);
+ root->invoke_show(drawing, dkey, SP_ITEM_SHOW_DISPLAY);
+
+ /* Create renderer and context */
+ CairoRenderer *renderer = new CairoRenderer();
+ CairoRenderContext *ctx = renderer->createContext();
+ ctx->setPDFLevel(level);
+ ctx->setTextToPath(texttopath);
+ ctx->setOmitText(omittext);
+ ctx->setFilterToBitmap(filtertobitmap);
+ ctx->setBitmapResolution(resolution);
+
+ bool ret = ctx->setPdfTarget (filename);
+ if(ret) {
+ /* Render document */
+ ret = renderer->setupDocument(ctx, doc, pageBoundingBox, bleedmargin_px, base);
+
+ auto pages = doc->getPageManager().getPages();
+ if (pages.size() == 0) {
+ // Output the page bounding box as already set up in the initial setupDocument.
+ renderer->renderItem(ctx, root);
+ ret = ctx->finish();
+ } else {
+ auto scale = doc->getDocumentScale();
+
+ // Set root transformation, which copies a bit of what happens above the
+ // reason for this manual process is because we need to slide in the page
+ // offset transformations so need fine-control over placing the children.
+ ctx->transform(scale);
+ ctx->transform(root->transform);
+
+ int index = 1;
+ for (auto &page : pages) {
+ ctx->pushState();
+
+ auto pt = Inkscape::Util::Quantity::convert(1, "px", "pt");
+ auto rect = page->getRect();
+ // Conclude previous page and set new page width and height.
+ auto big_rect = rect * scale;
+ ctx->nextPage(big_rect.width() * pt, big_rect.height() * pt, page->label());
+
+ // Set up page transformation which pushes objects back into the 0,0 location
+ ctx->transform(Geom::Translate(rect.corner(0)).inverse());
+
+ for (auto &child : page->getOverlappingItems(false)) {
+ ctx->pushState();
+
+ // This process does not return layers, so those affines are added manually.
+ for (auto anc : child->ancestorList(true)) {
+ if (auto layer = dynamic_cast<SPItem *>(anc)) {
+ if (layer != child && layer != root) {
+ ctx->transform(layer->transform);
+ }
+ }
+ }
+
+ // Render the page into the context in the new location.
+ renderer->renderItem(ctx, child, nullptr, page);
+ ctx->popState();
+ }
+ ret = ctx->finishPage();
+ index += 1;
+
+ ctx->popState();
+ }
+ ret = ctx->finish();
+ }
+ }
+
+ root->invoke_hide(dkey);
+
+ renderer->destroyContext(ctx);
+ delete renderer;
+
+ return ret;
+}
+
+/**
+ \brief This function calls the output module with the filename
+ \param mod unused
+ \param doc Document to be saved
+ \param filename Filename to save to (probably will end in .pdf)
+
+ The most interesting thing that this function does is just attach
+ an '>' on the front of the filename. This is the syntax used to
+ tell the printing system to save to file.
+*/
+void
+CairoRendererPdfOutput::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
+{
+ Inkscape::Extension::Extension * ext;
+ unsigned int ret;
+
+ ext = Inkscape::Extension::db.get("org.inkscape.output.pdf.cairorenderer");
+ if (ext == nullptr)
+ return;
+
+ int level = 0;
+ try {
+ const gchar *new_level = mod->get_param_optiongroup("PDFversion");
+ if((new_level != nullptr) && (g_ascii_strcasecmp("PDF-1.5", new_level) == 0)) {
+ level = 1;
+ }
+ }
+ catch(...) {
+ g_warning("Parameter <PDFversion> might not exist");
+ }
+
+ bool new_textToPath = FALSE;
+ try {
+ new_textToPath = (strcmp(mod->get_param_optiongroup("textToPath"), "paths") == 0);
+ }
+ catch(...) {
+ g_warning("Parameter <textToPath> might not exist");
+ }
+
+ bool new_textToLaTeX = FALSE;
+ try {
+ new_textToLaTeX = (strcmp(mod->get_param_optiongroup("textToPath"), "LaTeX") == 0);
+ }
+ catch(...) {
+ g_warning("Parameter <textToLaTeX> might not exist");
+ }
+
+ bool new_blurToBitmap = FALSE;
+ try {
+ new_blurToBitmap = mod->get_param_bool("blurToBitmap");
+ }
+ catch(...) {
+ g_warning("Parameter <blurToBitmap> might not exist");
+ }
+
+ int new_bitmapResolution = 72;
+ try {
+ new_bitmapResolution = mod->get_param_int("resolution");
+ }
+ catch(...) {
+ g_warning("Parameter <resolution> might not exist");
+ }
+
+ const gchar *new_exportId = nullptr;
+ try {
+ new_exportId = mod->get_param_string("exportId");
+ }
+ catch(...) {
+ g_warning("Parameter <exportId> might not exist");
+ }
+
+ bool new_exportCanvas = true;
+ try {
+ new_exportCanvas = (strcmp(ext->get_param_optiongroup("area"), "page") == 0);
+ } catch(...) {
+ g_warning("Parameter <area> might not exist");
+ }
+ bool new_exportDrawing = !new_exportCanvas;
+
+ double new_bleedmargin_px = 0.;
+ try {
+ new_bleedmargin_px = Inkscape::Util::Quantity::convert(mod->get_param_float("bleed"), "mm", "px");
+ }
+ catch(...) {
+ g_warning("Parameter <bleed> might not exist");
+ }
+
+ // Create PDF file
+ {
+ gchar * final_name;
+ final_name = g_strdup_printf("> %s", filename);
+ ret = pdf_render_document_to_file(doc, final_name, level,
+ new_textToPath, new_textToLaTeX, new_blurToBitmap, new_bitmapResolution,
+ new_exportId, new_exportDrawing, new_exportCanvas, new_bleedmargin_px);
+ g_free(final_name);
+
+ if (!ret)
+ throw Inkscape::Extension::Output::save_failed();
+ }
+
+ // Create LaTeX file (if requested)
+ if (new_textToLaTeX) {
+ ret = latex_render_document_text_to_file(doc, filename, new_exportId, new_exportDrawing, new_exportCanvas, new_bleedmargin_px, true);
+
+ if (!ret)
+ throw Inkscape::Extension::Output::save_failed();
+ }
+}
+
+#include "clear-n_.h"
+
+/**
+ \brief A function allocate a copy of this function.
+
+ This is the definition of Cairo PDF out. This function just
+ calls the extension system with the memory allocated XML that
+ describes the data.
+*/
+void
+CairoRendererPdfOutput::init ()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>Portable Document Format</name>\n"
+ "<id>org.inkscape.output.pdf.cairorenderer</id>\n"
+ "<param name=\"PDFversion\" gui-text=\"" N_("Restrict to PDF version:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value='PDF-1.5'>" N_("PDF 1.5") "</option>\n"
+ "<option value='PDF-1.4'>" N_("PDF 1.4") "</option>\n"
+ "</param>\n"
+ "<param name=\"textToPath\" gui-text=\"" N_("Text output options:") "\" type=\"optiongroup\" appearance=\"radio\">\n"
+ "<option value=\"embed\">" N_("Embed fonts") "</option>\n"
+ "<option value=\"paths\">" N_("Convert text to paths") "</option>\n"
+ "<option value=\"LaTeX\">" N_("Omit text in PDF and create LaTeX file") "</option>\n"
+ "</param>\n"
+ "<param name=\"blurToBitmap\" gui-text=\"" N_("Rasterize filter effects") "\" type=\"bool\">true</param>\n"
+ "<param name=\"resolution\" gui-text=\"" N_("Resolution for rasterization (dpi):") "\" type=\"int\" min=\"1\" max=\"10000\">96</param>\n"
+ "<param name=\"area\" gui-text=\"" N_("Output page size:") "\" type=\"optiongroup\" appearance=\"radio\" >\n"
+ "<option value=\"page\">" N_("Use document's page size") "</option>"
+ "<option value=\"drawing\">" N_("Use exported object's size") "</option>"
+ "</param>"
+ "<param name=\"bleed\" gui-text=\"" N_("Bleed/margin (mm):") "\" type=\"float\" min=\"-10000\" max=\"10000\">0</param>\n"
+ "<param name=\"exportId\" gui-text=\"" N_("Limit export to the object with ID:") "\" type=\"string\"></param>\n"
+ "<output is_exported='true'>\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());
+ // clang-format on
+
+ return;
+}
+
+} } } /* namespace Inkscape, Extension, Internal */
+
+#endif /* HAVE_CAIRO_PDF */
diff --git a/src/extension/internal/cairo-renderer-pdf-out.h b/src/extension/internal/cairo-renderer-pdf-out.h
new file mode 100644
index 0000000..9f99012
--- /dev/null
+++ b/src/extension/internal/cairo-renderer-pdf-out.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A quick hack to use the Cairo renderer to write out a file. This
+ * then makes 'save as...' PDF.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Ulf Erikson <ulferikson@users.sf.net>
+ *
+ * Copyright (C) 2004-2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef EXTENSION_INTERNAL_CAIRO_RENDERER_PDF_OUT_H
+#define EXTENSION_INTERNAL_CAIRO_RENDERER_PDF_OUT_H
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class CairoRendererPdfOutput : Inkscape::Extension::Implementation::Implementation {
+
+public:
+ bool check(Inkscape::Extension::Extension *module) override;
+ void save(Inkscape::Extension::Output *mod,
+ SPDocument *doc,
+ gchar const *filename) override;
+ static void init();
+};
+
+} } } /* namespace Inkscape, Extension, Internal */
+
+#endif /* !EXTENSION_INTERNAL_CAIRO_RENDERER_PDF_OUT_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/cairo-renderer.cpp b/src/extension/internal/cairo-renderer.cpp
new file mode 100644
index 0000000..5898b8e
--- /dev/null
+++ b/src/extension/internal/cairo-renderer.cpp
@@ -0,0 +1,1012 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Rendering with Cairo.
+ */
+/*
+ * Author:
+ * Miklos Erdelyi <erdelyim@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Miklos Erdelyi
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifndef PANGO_ENABLE_BACKEND
+#define PANGO_ENABLE_BACKEND
+#endif
+
+#ifndef PANGO_ENABLE_ENGINE
+#define PANGO_ENABLE_ENGINE
+#endif
+
+
+#include <csignal>
+#include <cerrno>
+
+
+#include <2geom/transforms.h>
+#include <2geom/pathvector.h>
+#include <cairo.h>
+#include <glib.h>
+#include <glibmm/i18n.h>
+
+// include support for only the compiled-in surface types
+#ifdef CAIRO_HAS_PDF_SURFACE
+#include <cairo-pdf.h>
+#endif
+#ifdef CAIRO_HAS_PS_SURFACE
+#include <cairo-ps.h>
+#endif
+
+#include "cairo-render-context.h"
+#include "cairo-renderer.h"
+#include "document.h"
+#include "inkscape-version.h"
+#include "rdf.h"
+#include "style-internal.h"
+#include "display/cairo-utils.h"
+#include "display/curve.h"
+#include "extension/system.h"
+#include "filter-chemistry.h"
+#include "helper/pixbuf-ops.h"
+#include "helper/png-write.h"
+
+#include "io/sys.h"
+
+#include "include/source_date_epoch.h"
+
+#include "libnrtype/Layout-TNG.h"
+
+#include "object/sp-anchor.h"
+#include "object/sp-clippath.h"
+#include "object/sp-defs.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-hatch-path.h"
+#include "object/sp-image.h"
+#include "object/sp-item-group.h"
+#include "object/sp-item.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-marker.h"
+#include "object/sp-mask.h"
+#include "object/sp-page.h"
+#include "object/sp-pattern.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-root.h"
+#include "object/sp-shape.h"
+#include "object/sp-symbol.h"
+#include "object/sp-text.h"
+#include "object/sp-use.h"
+
+#include "util/units.h"
+
+//#define TRACE(_args) g_printf _args
+#define TRACE(_args)
+//#define TEST(_args) _args
+#define TEST(_args)
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+CairoRenderer::CairoRenderer(void)
+= default;
+
+CairoRenderer::~CairoRenderer()
+{
+ /* restore default signal handling for SIGPIPE */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_DFL);
+#endif
+
+ return;
+}
+
+CairoRenderContext*
+CairoRenderer::createContext()
+{
+ CairoRenderContext *new_context = new CairoRenderContext(this);
+ g_assert( new_context != nullptr );
+
+ new_context->_state = nullptr;
+
+ // create initial render state
+ CairoRenderState *state = new_context->_createState();
+ state->transform = Geom::identity();
+ new_context->_state_stack.push_back(state);
+ new_context->_state = state;
+
+ return new_context;
+}
+
+void
+CairoRenderer::destroyContext(CairoRenderContext *ctx)
+{
+ delete ctx;
+}
+
+/*
+
+Here comes the rendering part which could be put into the 'render' methods of SPItems'
+
+*/
+
+/* The below functions are copy&pasted plus slightly modified from *_invoke_print functions. */
+static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin = nullptr, SPPage *page = nullptr);
+static void sp_group_render(SPGroup *group, CairoRenderContext *ctx, SPItem *origin = nullptr, SPPage *page = nullptr);
+static void sp_anchor_render(SPAnchor *a, CairoRenderContext *ctx);
+static void sp_use_render(SPUse *use, CairoRenderContext *ctx, SPPage *page = nullptr);
+static void sp_shape_render(SPShape *shape, CairoRenderContext *ctx, SPItem *origin = nullptr);
+static void sp_text_render(SPText *text, CairoRenderContext *ctx);
+static void sp_flowtext_render(SPFlowtext *flowtext, CairoRenderContext *ctx);
+static void sp_image_render(SPImage *image, CairoRenderContext *ctx);
+static void sp_symbol_render(SPSymbol *symbol, CairoRenderContext *ctx, SPItem *origin, SPPage *page);
+static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx, SPPage *page = nullptr);
+
+static void sp_shape_render_invoke_marker_rendering(SPMarker* marker, Geom::Affine tr, SPStyle* style, CairoRenderContext *ctx, SPItem *origin)
+{
+ bool render = true;
+ if (marker->markerUnits == SP_MARKER_UNITS_STROKEWIDTH) {
+ if (style->stroke_width.computed > 1e-9) {
+ tr = Geom::Scale(style->stroke_width.computed) * tr;
+ } else {
+ render = false; // stroke width zero and marker is thus scaled down to zero, skip
+ }
+ }
+
+ if (render) {
+ SPItem* marker_item = sp_item_first_item_child(marker);
+ if (marker_item) {
+ tr = (Geom::Affine)marker_item->transform * (Geom::Affine)marker->c2p * tr;
+ Geom::Affine old_tr = marker_item->transform;
+ marker_item->transform = tr;
+ ctx->getRenderer()->renderItem (ctx, marker_item, origin);
+ marker_item->transform = old_tr;
+ }
+ }
+}
+
+static void sp_shape_render(SPShape *shape, CairoRenderContext *ctx, SPItem *origin)
+{
+ if (!shape->curve()) {
+ return;
+ }
+
+ Geom::OptRect pbox = shape->geometricBounds();
+
+ SPStyle* style = shape->style;
+ auto fill_origin = style->fill.paintOrigin;
+ auto stroke_origin = style->stroke.paintOrigin;
+
+ if (origin) {
+ // If the shape is a child of a marker, we must set styles from the origin.
+ SPMarker *marker = nullptr;
+ auto parentobj = shape->parent;
+ while (parentobj) {
+ if (marker = dynamic_cast<SPMarker *>(parentobj)) {
+ break;
+ }
+ parentobj = parentobj->parent;
+ }
+ if (marker) {
+ // Origin will ultimately be either the original object the marker
+ // was added to OR a use tag. See sp_use_render for where this object
+ // comes from when it's a clone.
+ SPStyle *styleorig = origin->style;
+ bool iscolorfill = styleorig->fill.isColor() || (styleorig->fill.isPaintserver() && !styleorig->getFillPaintServer()->isValid());
+ bool iscolorstroke = styleorig->stroke.isColor() || (styleorig->stroke.isPaintserver() && !styleorig->getStrokePaintServer()->isValid());
+ bool fillctxfill = style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL;
+ bool fillctxstroke = style->fill.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE;
+ bool strokectxfill = style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_FILL;
+ bool strokectxstroke = style->stroke.paintOrigin == SP_CSS_PAINT_ORIGIN_CONTEXT_STROKE;
+
+ if (fillctxfill || fillctxstroke) {
+ if (fillctxfill ? iscolorfill : iscolorstroke) {
+ style->fill.setColor(fillctxfill ? styleorig->fill.value.color : styleorig->stroke.value.color);
+ } else if (fillctxfill ? styleorig->fill.isPaintserver() : styleorig->stroke.isPaintserver()) {
+ style->fill.value.href = fillctxfill ? styleorig->fill.value.href : styleorig->stroke.value.href;
+ } else {
+ style->fill.setNone();
+ }
+ }
+ if (strokectxfill || strokectxstroke) {
+ if (strokectxfill ? iscolorfill : iscolorstroke) {
+ style->stroke.setColor(strokectxfill ? styleorig->fill.value.color : styleorig->stroke.value.color);
+ } else if (strokectxfill ? styleorig->fill.isPaintserver() : styleorig->stroke.isPaintserver()) {
+ style->stroke.value.href = strokectxfill ? styleorig->fill.value.href : styleorig->stroke.value.href;
+ } else {
+ style->stroke.setNone();
+ }
+ }
+ style->fill.paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL;
+ style->stroke.paintOrigin = SP_CSS_PAINT_ORIGIN_NORMAL;
+ }
+ }
+
+ Geom::PathVector const &pathv = shape->curve()->get_pathvector();
+ if (pathv.empty()) {
+ return;
+ }
+
+ if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_NORMAL ||
+ (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE)) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_OVER_FILL);
+ } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_OVER_STROKE);
+ } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY);
+ } else if (style->paint_order.layer[0] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY);
+ }
+
+ // START marker
+ for (int i = 0; i < 2; i++) { // SP_MARKER_LOC and SP_MARKER_LOC_START
+ if ( shape->_marker[i] ) {
+ SPMarker* marker = shape->_marker[i];
+ Geom::Affine tr;
+ if (marker->orient_mode == MARKER_ORIENT_AUTO) {
+ tr = sp_shape_marker_get_transform_at_start(pathv.begin()->front());
+ } else if (marker->orient_mode == MARKER_ORIENT_AUTO_START_REVERSE) {
+ tr = Geom::Rotate::from_degrees( 180.0 ) * sp_shape_marker_get_transform_at_start(pathv.begin()->front());
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(pathv.begin()->front().pointAt(0));
+ }
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ }
+ // MID marker
+ for (int i = 0; i < 3; i += 2) { // SP_MARKER_LOC and SP_MARKER_LOC_MID
+ if ( !shape->_marker[i] ) continue;
+ SPMarker* marker = shape->_marker[i];
+ for(Geom::PathVector::const_iterator path_it = pathv.begin(); path_it != pathv.end(); ++path_it) {
+ // START position
+ if ( path_it != pathv.begin()
+ && ! ((path_it == (pathv.end()-1)) && (path_it->size_default() == 0)) ) // if this is the last path and it is a moveto-only, there is no mid marker there
+ {
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform_at_start(path_it->front());
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(path_it->front().pointAt(0));
+ }
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ // MID position
+ if (path_it->size_default() > 1) {
+ Geom::Path::const_iterator curve_it1 = path_it->begin(); // incoming curve
+ Geom::Path::const_iterator curve_it2 = ++(path_it->begin()); // outgoing curve
+ while (curve_it2 != path_it->end_default())
+ {
+ /* Put marker between curve_it1 and curve_it2.
+ * Loop to end_default (so including closing segment), because when a path is closed,
+ * there should be a midpoint marker between last segment and closing straight line segment */
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform(*curve_it1, *curve_it2);
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(curve_it1->pointAt(1));
+ }
+
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+
+ ++curve_it1;
+ ++curve_it2;
+ }
+ }
+ // END position
+ if ( path_it != (pathv.end()-1) && !path_it->empty()) {
+ Geom::Curve const &lastcurve = path_it->back_default();
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform_at_end(lastcurve);
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(lastcurve.pointAt(1));
+ }
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ }
+ }
+ // END marker
+ for (int i = 0; i < 4; i += 3) { // SP_MARKER_LOC and SP_MARKER_LOC_END
+ if ( shape->_marker[i] ) {
+ SPMarker* marker = shape->_marker[i];
+
+ /* Get reference to last curve in the path.
+ * For moveto-only path, this returns the "closing line segment". */
+ Geom::Path const &path_last = pathv.back();
+ unsigned int index = path_last.size_default();
+ if (index > 0) {
+ index--;
+ }
+ Geom::Curve const &lastcurve = path_last[index];
+
+ Geom::Affine tr;
+ if (marker->orient_mode != MARKER_ORIENT_ANGLE) {
+ tr = sp_shape_marker_get_transform_at_end(lastcurve);
+ } else {
+ tr = Geom::Rotate::from_degrees(marker->orient.computed) * Geom::Translate(lastcurve.pointAt(1));
+ }
+
+ sp_shape_render_invoke_marker_rendering(marker, tr, style, ctx, origin ? origin : shape);
+ }
+ }
+
+ if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_OVER_FILL);
+ } else if (style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_OVER_STROKE);
+ } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_STROKE &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::STROKE_ONLY);
+ } else if (style->paint_order.layer[2] == SP_CSS_PAINT_ORDER_FILL &&
+ style->paint_order.layer[1] == SP_CSS_PAINT_ORDER_MARKER ) {
+ ctx->renderPathVector(pathv, style, pbox, CairoRenderContext::FILL_ONLY);
+ }
+
+ // Put the style's paint origin back, in case this shape is a marker
+ // which is rendered multple times.
+ style->fill.paintOrigin = fill_origin;
+ style->stroke.paintOrigin = stroke_origin;
+}
+
+static void sp_group_render(SPGroup *group, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ CairoRenderer *renderer = ctx->getRenderer();
+ for (auto obj : group->childList(false)) {
+ if (SPItem *item = dynamic_cast<SPItem *>(obj)) {
+ renderer->renderItem(ctx, item, origin, page);
+ }
+ }
+}
+
+static void sp_use_render(SPUse *use, CairoRenderContext *ctx, SPPage *page)
+{
+ bool translated = false;
+ CairoRenderer *renderer = ctx->getRenderer();
+
+ if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
+ // FIXME: This translation sometimes isn't in the correct units; e.g.
+ // x="0" y="42" has a different effect than transform="translate(0,42)".
+ Geom::Affine tp(Geom::Translate(use->x.computed, use->y.computed));
+ ctx->pushState();
+ ctx->transform(tp);
+ translated = true;
+ }
+
+ if (use->child) {
+ // Padding in the use object as the origin here ensures markers
+ // are rendered with their correct context-fill.
+ renderer->renderItem(ctx, use->child, use, page);
+ }
+
+ if (translated) {
+ ctx->popState();
+ }
+}
+
+static void sp_text_render(SPText *text, CairoRenderContext *ctx)
+{
+ text->layout.showGlyphs(ctx);
+}
+
+static void sp_flowtext_render(SPFlowtext *flowtext, CairoRenderContext *ctx)
+{
+ flowtext->layout.showGlyphs(ctx);
+}
+
+static void sp_image_render(SPImage *image, CairoRenderContext *ctx)
+{
+ if (!image->pixbuf) {
+ return;
+ }
+ if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) {
+ return;
+ }
+
+ int w = image->pixbuf->width();
+ int h = image->pixbuf->height();
+
+ double x = image->x.computed;
+ double y = image->y.computed;
+ double width = image->width.computed;
+ double height = image->height.computed;
+
+ if (image->aspect_align != SP_ASPECT_NONE) {
+ calculatePreserveAspectRatio (image->aspect_align, image->aspect_clip, (double)w, (double)h,
+ &x, &y, &width, &height);
+ }
+
+ if (image->aspect_clip == SP_ASPECT_SLICE && !ctx->getCurrentState()->has_overflow) {
+ ctx->addClippingRect(image->x.computed, image->y.computed, image->width.computed, image->height.computed);
+ }
+
+ Geom::Translate tp(x, y);
+ Geom::Scale s(width / (double)w, height / (double)h);
+ Geom::Affine t(s * tp);
+
+ ctx->renderImage(image->pixbuf, t, image->style);
+}
+
+static void sp_anchor_render(SPAnchor *a, CairoRenderContext *ctx)
+{
+ CairoRenderer *renderer = ctx->getRenderer();
+
+ std::vector<SPObject*> l(a->childList(false));
+ if (a->href)
+ ctx->tagBegin(a->href);
+ for(auto x : l){
+ SPItem *item = dynamic_cast<SPItem*>(x);
+ if (item) {
+ renderer->renderItem(ctx, item);
+ }
+ }
+ if (a->href)
+ ctx->tagEnd();
+}
+
+static void sp_symbol_render(SPSymbol *symbol, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ if (!symbol->cloned) {
+ return;
+ }
+
+ /* Cloned <symbol> is actually renderable */
+ ctx->pushState();
+ ctx->transform(symbol->c2p);
+
+ // apply viewbox if set
+ if (false /*symbol->viewBox_set*/) {
+ Geom::Affine vb2user;
+ double x, y, width, height;
+ double view_width, view_height;
+ x = 0.0;
+ y = 0.0;
+ width = 1.0;
+ height = 1.0;
+
+ view_width = symbol->viewBox.width();
+ view_height = symbol->viewBox.height();
+
+ calculatePreserveAspectRatio(symbol->aspect_align, symbol->aspect_clip, view_width, view_height,
+ &x, &y,&width, &height);
+
+ // [itemTransform *] translate(x, y) * scale(w/vw, h/vh) * translate(-vx, -vy);
+ vb2user = Geom::identity();
+ vb2user[0] = width / view_width;
+ vb2user[3] = height / view_height;
+ vb2user[4] = x - symbol->viewBox.left() * vb2user[0];
+ vb2user[5] = y - symbol->viewBox.top() * vb2user[3];
+
+ ctx->transform(vb2user);
+ }
+
+ sp_group_render(symbol, ctx, origin, page);
+ ctx->popState();
+}
+
+static void sp_root_render(SPRoot *root, CairoRenderContext *ctx)
+{
+ CairoRenderer *renderer = ctx->getRenderer();
+
+ if (!ctx->getCurrentState()->has_overflow && root->parent)
+ ctx->addClippingRect(root->x.computed, root->y.computed, root->width.computed, root->height.computed);
+
+ ctx->pushState();
+ renderer->setStateForItem(ctx, root);
+ ctx->transform(root->c2p);
+ sp_group_render(root, ctx);
+ ctx->popState();
+}
+
+/**
+ This function converts the item to a raster image and includes the image into the cairo renderer.
+ It is only used for filters and then only when rendering filters as bitmaps is requested.
+*/
+static void sp_asbitmap_render(SPItem *item, CairoRenderContext *ctx, SPPage *page)
+{
+
+ // The code was adapted from sp_selection_create_bitmap_copy in selection-chemistry.cpp
+
+ // Calculate resolution
+ double res;
+ /** @TODO reimplement the resolution stuff (WHY?)
+ */
+ res = ctx->getBitmapResolution();
+ if(res == 0) {
+ res = Inkscape::Util::Quantity::convert(1, "in", "px");
+ }
+ TRACE(("sp_asbitmap_render: resolution: %f\n", res ));
+
+ // Get the bounding box of the selection in document coordinates.
+ Geom::OptRect bbox = item->documentVisualBounds();
+
+ bbox &= (page ? page->getDocumentRect() : item->document->preferredBounds());
+
+ // no bbox, e.g. empty group or item not overlapping its page
+ if (!bbox) {
+ return;
+ }
+
+ // The width and height of the bitmap in pixels
+ unsigned width = ceil(bbox->width() * Inkscape::Util::Quantity::convert(res, "px", "in"));
+ unsigned height = ceil(bbox->height() * Inkscape::Util::Quantity::convert(res, "px", "in"));
+
+ if (width == 0 || height == 0) return;
+
+ // Scale to exactly fit integer bitmap inside bounding box
+ double scale_x = bbox->width() / width;
+ double scale_y = bbox->height() / height;
+
+ // Location of bounding box in document coordinates.
+ double shift_x = bbox->min()[Geom::X];
+ double shift_y = bbox->top();
+
+ // For default 96 dpi, snap bitmap to pixel grid
+ if (res == Inkscape::Util::Quantity::convert(1, "in", "px")) {
+ shift_x = round (shift_x);
+ shift_y = round (shift_y);
+ }
+
+ // Calculate the matrix that will be applied to the image so that it exactly overlaps the source objects
+
+ // Matrix to put bitmap in correct place on document
+ Geom::Affine t_on_document = (Geom::Affine)(Geom::Scale (scale_x, scale_y)) *
+ (Geom::Affine)(Geom::Translate (shift_x, shift_y));
+
+ // ctx matrix already includes item transformation. We must substract.
+ Geom::Affine t_item = item->i2doc_affine();
+ Geom::Affine t = t_on_document * t_item.inverse();
+
+ // Do the export
+ SPDocument *document = item->document;
+
+ std::vector<SPItem*> items;
+ items.push_back(item);
+
+ std::unique_ptr<Inkscape::Pixbuf> pb(sp_generate_internal_bitmap(document, *bbox, res, items, true));
+
+ if (pb) {
+ //TEST(gdk_pixbuf_save( pb, "bitmap.png", "png", NULL, NULL ));
+
+ ctx->renderImage(pb.get(), t, item->style);
+ }
+}
+
+
+static void sp_item_invoke_render(SPItem *item, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ SPRoot *root = dynamic_cast<SPRoot *>(item);
+ if (root) {
+ TRACE(("root\n"));
+ sp_root_render(root, ctx);
+ } else {
+ SPSymbol *symbol = dynamic_cast<SPSymbol *>(item);
+ if (symbol) {
+ TRACE(("symbol\n"));
+ sp_symbol_render(symbol, ctx, origin, page);
+ } else {
+ SPAnchor *anchor = dynamic_cast<SPAnchor *>(item);
+ if (anchor) {
+ TRACE(("<a>\n"));
+ sp_anchor_render(anchor, ctx);
+ } else {
+ SPShape *shape = dynamic_cast<SPShape *>(item);
+ if (shape) {
+ TRACE(("shape\n"));
+ sp_shape_render(shape, ctx, origin);
+ } else {
+ SPUse *use = dynamic_cast<SPUse *>(item);
+ if (use) {
+ TRACE(("use begin---\n"));
+ sp_use_render(use, ctx, page);
+ TRACE(("---use end\n"));
+ } else {
+ SPText *text = dynamic_cast<SPText *>(item);
+ if (text) {
+ TRACE(("text\n"));
+ sp_text_render(text, ctx);
+ } else {
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(item);
+ if (flowtext) {
+ TRACE(("flowtext\n"));
+ sp_flowtext_render(flowtext, ctx);
+ } else {
+ SPImage *image = dynamic_cast<SPImage *>(item);
+ if (image) {
+ TRACE(("image\n"));
+ sp_image_render(image, ctx);
+ } else if (dynamic_cast<SPMarker *>(item)) {
+ // Marker contents shouldn't be rendered, even outside of <defs>.
+ return;
+ } else {
+ SPGroup *group = dynamic_cast<SPGroup *>(item);
+ if (group) {
+ TRACE(("<g>\n"));
+ sp_group_render(group, ctx, origin, page);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+CairoRenderer::setStateForItem(CairoRenderContext *ctx, SPItem const *item)
+{
+ ctx->setStateForStyle(item->style);
+
+ CairoRenderState *state = ctx->getCurrentState();
+ state->clip_path = item->getClipObject();
+ state->mask = item->getMaskObject();
+ state->item_transform = Geom::Affine (item->transform);
+
+ // If parent_has_userspace is true the parent state's transform
+ // has to be used for the mask's/clippath's context.
+ // This is so because we use the image's/(flow)text's transform for positioning
+ // instead of explicitly specifying it and letting the renderer do the
+ // transformation before rendering the item.
+ if (dynamic_cast<SPText const *>(item) || dynamic_cast<SPFlowtext const *>(item) || dynamic_cast<SPImage const *>(item)) {
+ state->parent_has_userspace = TRUE;
+ }
+ TRACE(("setStateForItem opacity: %f\n", state->opacity));
+}
+
+bool CairoRenderer::_shouldRasterize(CairoRenderContext *ctx, SPItem const *item)
+{
+ // rasterize filtered items as per user setting
+ // however, clipPaths ignore any filters, so do *not* rasterize
+ // TODO: might apply to some degree to masks with filtered elements as well;
+ // we need to figure out where in the stack it would be safe to rasterize
+ if (ctx->getFilterToBitmap() && !item->isInClipPath()) {
+ if (auto const *clone = dynamic_cast<SPUse const *>(item)) {
+ return clone->anyInChain([](SPItem const *i) { return i && i->isFiltered(); });
+ } else {
+ return item->isFiltered();
+ }
+ }
+ return false;
+}
+
+void CairoRenderer::_doRender(SPItem *item, CairoRenderContext *ctx, SPItem *origin, SPPage *page)
+{
+ // Check item's visibility
+ if (item->isHidden() || has_hidder_filter(item)) {
+ return;
+ }
+
+ if (_shouldRasterize(ctx, item)) {
+ sp_asbitmap_render(item, ctx, page);
+ } else {
+ sp_item_invoke_render(item, ctx, origin, page);
+ }
+}
+
+// TODO change this to accept a const SPItem:
+void CairoRenderer::renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *origin, SPPage *page)
+{
+ ctx->pushState();
+ setStateForItem(ctx, item);
+
+ CairoRenderState *state = ctx->getCurrentState();
+ state->need_layer = ( state->mask || state->clip_path || state->opacity != 1.0 );
+ SPStyle* style = item->style;
+ SPGroup * group = dynamic_cast<SPGroup *>(item);
+ bool blend = false;
+ if (group && style->mix_blend_mode.set && style->mix_blend_mode.value != SP_CSS_BLEND_NORMAL) {
+ state->need_layer = true;
+ blend = true;
+ }
+ // Draw item on a temporary surface so a mask, clip-path, or opacity can be applied to it.
+ if (state->need_layer) {
+ state->merge_opacity = FALSE;
+ ctx->pushLayer();
+ }
+
+ ctx->transform(item->transform);
+
+ _doRender(item, ctx, origin, page);
+
+ if (state->need_layer) {
+ if (blend) {
+ ctx->popLayer(ink_css_blend_to_cairo_operator(style->mix_blend_mode.value)); // This applies clipping/masking
+ } else {
+ ctx->popLayer(); // This applies clipping/masking
+ }
+ }
+ ctx->popState();
+}
+
+void CairoRenderer::renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key) {
+ ctx->pushState();
+ ctx->setStateForStyle(hatchPath.style);
+ ctx->transform(Geom::Translate(hatchPath.offset.computed, 0));
+
+ std::unique_ptr<SPCurve> curve = hatchPath.calculateRenderCurve(key);
+ Geom::PathVector const & pathv =curve->get_pathvector();
+ if (!pathv.empty()) {
+ ctx->renderPathVector(pathv, hatchPath.style, Geom::OptRect());
+ }
+
+ ctx->popState();
+}
+
+void CairoRenderer::setMetadata(CairoRenderContext *ctx, SPDocument *doc) {
+ // title
+ const gchar *title = rdf_get_work_entity(doc, rdf_find_entity("title"));
+ if (title) {
+ ctx->_metadata.title = title;
+ }
+
+ // author
+ const gchar *author = rdf_get_work_entity(doc, rdf_find_entity("creator"));
+ if (author) {
+ ctx->_metadata.author = author;
+ }
+
+ // subject
+ const gchar *subject = rdf_get_work_entity(doc, rdf_find_entity("description"));
+ if (subject) {
+ ctx->_metadata.subject = subject;
+ }
+
+ // keywords
+ const gchar *keywords = rdf_get_work_entity(doc, rdf_find_entity("subject"));
+ if (keywords) {
+ ctx->_metadata.keywords = keywords;
+ }
+
+ // copyright
+ const gchar *copyright = rdf_get_work_entity(doc, rdf_find_entity("rights"));
+ if (copyright) {
+ ctx->_metadata.copyright = copyright;
+ }
+
+ // creator
+ ctx->_metadata.creator = Glib::ustring::compose("Inkscape %1 (https://inkscape.org)",
+ Inkscape::version_string_without_revision);
+
+ // cdate (only used for for reproducible builds hack)
+ Glib::ustring cdate = ReproducibleBuilds::now_iso_8601();
+ if (!cdate.empty()) {
+ ctx->_metadata.cdate = cdate;
+ }
+
+ // mdate (currently unused)
+}
+
+bool
+CairoRenderer::setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, double bleedmargin_px, SPItem *base)
+{
+// PLEASE note when making changes to the boundingbox and transform calculation, corresponding changes should be made to LaTeXTextRenderer::setupDocument !!!
+
+ g_assert( ctx != nullptr );
+
+ if (!base) {
+ base = doc->getRoot();
+ }
+
+ Geom::Rect d;
+ if (pageBoundingBox) {
+ d = Geom::Rect::from_xywh(Geom::Point(0,0), doc->getDimensions());
+ } else {
+ Geom::OptRect bbox = base->documentVisualBounds();
+ if (!bbox) {
+ g_message("CairoRenderer: empty bounding box.");
+ return false;
+ }
+ d = *bbox;
+ }
+ d.expandBy(bleedmargin_px);
+
+ double px_to_ctx_units = 1.0;
+ if (ctx->_vector_based_target) {
+ // convert from px to pt
+ px_to_ctx_units = Inkscape::Util::Quantity::convert(1, "px", "pt");
+ }
+
+ auto width = d.width() * px_to_ctx_units;
+ auto height = d.height() * px_to_ctx_units;
+
+ setMetadata(ctx, doc);
+
+ TRACE(("setupDocument: %f x %f\n", width, height));
+ bool ret = ctx->setupSurface(width, height);
+
+ if (ret) {
+ if (pageBoundingBox) {
+ // translate to set bleed/margin
+ Geom::Affine tp( Geom::Translate( bleedmargin_px, bleedmargin_px ) );
+ ctx->transform(tp);
+ } else {
+ // this transform translates the export drawing to a virtual page (0,0)-(width,height)
+ Geom::Affine tp(Geom::Translate(-d.min()));
+ ctx->transform(tp);
+ }
+ }
+
+ return ret;
+}
+
+// Apply an SVG clip path
+void
+CairoRenderer::applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp)
+{
+ g_assert( ctx != nullptr && ctx->_is_valid );
+
+ if (cp == nullptr)
+ return;
+
+ CairoRenderContext::CairoRenderMode saved_mode = ctx->getRenderMode();
+ ctx->setRenderMode(CairoRenderContext::RENDER_MODE_CLIP);
+
+ // FIXME: the access to the first clippath view to obtain the bbox is completely bogus
+ Geom::Affine saved_ctm;
+ if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && cp->display->bbox) {
+ Geom::Rect clip_bbox = *cp->display->bbox;
+ Geom::Affine t(Geom::Scale(clip_bbox.dimensions()));
+ t[4] = clip_bbox.left();
+ t[5] = clip_bbox.top();
+ t *= ctx->getCurrentState()->transform;
+ saved_ctm = ctx->getTransform();
+ ctx->setTransform(t);
+ }
+
+ TRACE(("BEGIN clip\n"));
+ SPObject const *co = cp;
+ for (auto& child: co->children) {
+ SPItem const *item = dynamic_cast<SPItem const *>(&child);
+ if (item) {
+
+ // combine transform of the item in clippath and the item using clippath:
+ Geom::Affine tempmat = item->transform * ctx->getCurrentState()->item_transform;
+
+ // render this item in clippath
+ ctx->pushState();
+ ctx->transform(tempmat);
+ setStateForItem(ctx, item);
+ // TODO fix this call to accept const items
+ _doRender(const_cast<SPItem *>(item), ctx);
+ ctx->popState();
+ }
+ }
+ TRACE(("END clip\n"));
+
+ // do clipping only if this was the first call to applyClipPath
+ if (ctx->getClipMode() == CairoRenderContext::CLIP_MODE_PATH
+ && saved_mode == CairoRenderContext::RENDER_MODE_NORMAL)
+ cairo_clip(ctx->_cr);
+
+ if (cp->clipPathUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX)
+ ctx->setTransform(saved_ctm);
+
+ ctx->setRenderMode(saved_mode);
+}
+
+// Apply an SVG mask
+void
+CairoRenderer::applyMask(CairoRenderContext *ctx, SPMask const *mask)
+{
+ g_assert( ctx != nullptr && ctx->_is_valid );
+
+ if (mask == nullptr)
+ return;
+
+ // FIXME: the access to the first mask view to obtain the bbox is completely bogus
+ // TODO: should the bbox be transformed if maskUnits != userSpaceOnUse ?
+ if (mask->maskContentUnits == SP_CONTENT_UNITS_OBJECTBOUNDINGBOX && mask->display->bbox) {
+ Geom::Rect mask_bbox = *mask->display->bbox;
+ Geom::Affine t(Geom::Scale(mask_bbox.dimensions()));
+ t[4] = mask_bbox.left();
+ t[5] = mask_bbox.top();
+ t *= ctx->getCurrentState()->transform;
+ ctx->setTransform(t);
+ }
+
+ // Clip mask contents... but...
+ // The mask's bounding box is the "geometric bounding box" which doesn't allow for
+ // filters which extend outside the bounding box. So don't clip.
+ // ctx->addClippingRect(mask_bbox.x0, mask_bbox.y0, mask_bbox.x1 - mask_bbox.x0, mask_bbox.y1 - mask_bbox.y0);
+
+ ctx->pushState();
+
+ TRACE(("BEGIN mask\n"));
+ SPObject const *co = mask;
+ for (auto& child: co->children) {
+ SPItem const *item = dynamic_cast<SPItem const *>(&child);
+ if (item) {
+ // TODO fix const correctness:
+ renderItem(ctx, const_cast<SPItem*>(item));
+ }
+ }
+ TRACE(("END mask\n"));
+
+ ctx->popState();
+}
+
+void
+calculatePreserveAspectRatio(unsigned int aspect_align, unsigned int aspect_clip, double vp_width, double vp_height,
+ double *x, double *y, double *width, double *height)
+{
+ if (aspect_align == SP_ASPECT_NONE)
+ return;
+
+ double scalex, scaley, scale;
+ double new_width, new_height;
+ scalex = *width / vp_width;
+ scaley = *height / vp_height;
+ scale = (aspect_clip == SP_ASPECT_MEET) ? MIN(scalex, scaley) : MAX(scalex, scaley);
+ new_width = vp_width * scale;
+ new_height = vp_height * scale;
+ /* Now place viewbox to requested position */
+ switch (aspect_align) {
+ case SP_ASPECT_XMIN_YMIN:
+ break;
+ case SP_ASPECT_XMID_YMIN:
+ *x -= 0.5 * (new_width - *width);
+ break;
+ case SP_ASPECT_XMAX_YMIN:
+ *x -= 1.0 * (new_width - *width);
+ break;
+ case SP_ASPECT_XMIN_YMID:
+ *y -= 0.5 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMID_YMID:
+ *x -= 0.5 * (new_width - *width);
+ *y -= 0.5 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMAX_YMID:
+ *x -= 1.0 * (new_width - *width);
+ *y -= 0.5 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMIN_YMAX:
+ *y -= 1.0 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMID_YMAX:
+ *x -= 0.5 * (new_width - *width);
+ *y -= 1.0 * (new_height - *height);
+ break;
+ case SP_ASPECT_XMAX_YMAX:
+ *x -= 1.0 * (new_width - *width);
+ *y -= 1.0 * (new_height - *height);
+ break;
+ default:
+ break;
+ }
+ *width = new_width;
+ *height = new_height;
+}
+
+#include "clear-n_.h"
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#undef TRACE
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/cairo-renderer.h b/src/extension/internal/cairo-renderer.h
new file mode 100644
index 0000000..934fa45
--- /dev/null
+++ b/src/extension/internal/cairo-renderer.h
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef EXTENSION_INTERNAL_CAIRO_RENDERER_H_SEEN
+#define EXTENSION_INTERNAL_CAIRO_RENDERER_H_SEEN
+
+/** \file
+ * Declaration of CairoRenderer, a class used for rendering via a CairoRenderContext.
+ */
+/*
+ * Authors:
+ * Miklos Erdelyi <erdelyim@gmail.com>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Miklos Erdelyi
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/extension.h"
+#include <set>
+#include <string>
+
+//#include "libnrtype/font-instance.h"
+#include <cairo.h>
+
+class SPItem;
+class SPClipPath;
+class SPMask;
+class SPHatchPath;
+class SPPage;
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class CairoRenderer;
+class CairoRenderContext;
+
+class CairoRenderer {
+public:
+ CairoRenderer();
+ virtual ~CairoRenderer();
+
+ CairoRenderContext *createContext();
+ void destroyContext(CairoRenderContext *ctx);
+
+ void setStateForItem(CairoRenderContext *ctx, SPItem const *item);
+
+ void applyClipPath(CairoRenderContext *ctx, SPClipPath const *cp);
+ void applyMask(CairoRenderContext *ctx, SPMask const *mask);
+
+ /** Initializes the CairoRenderContext according to the specified
+ SPDocument. A set*Target function can only be called on the context
+ before setupDocument. */
+ bool setupDocument(CairoRenderContext *ctx, SPDocument *doc, bool pageBoundingBox, double bleedmargin_px, SPItem *base);
+
+ /** Traverses the object tree and invokes the render methods. */
+ void renderItem(CairoRenderContext *ctx, SPItem *item, SPItem *clone = nullptr, SPPage *page = nullptr);
+ void renderHatchPath(CairoRenderContext *ctx, SPHatchPath const &hatchPath, unsigned key);
+
+private:
+ /** Extract metadata from doc and set it on ctx. */
+ void setMetadata(CairoRenderContext *ctx, SPDocument *doc);
+
+ /** Decide whether the given item should be rendered as a bitmap. */
+ static bool _shouldRasterize(CairoRenderContext *ctx, SPItem const *item);
+
+ /** Render a single item in a fully set up context. */
+ static void _doRender(SPItem *item, CairoRenderContext *ctx, SPItem *origin = nullptr,
+ SPPage *page = nullptr);
+
+};
+
+// FIXME: this should be a static method of CairoRenderer
+void calculatePreserveAspectRatio(unsigned int aspect_align, unsigned int aspect_clip, double vp_width,
+ double vp_height, double *x, double *y, double *width, double *height);
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* !EXTENSION_INTERNAL_CAIRO_RENDERER_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/cdr-input.cpp b/src/extension/internal/cdr-input.cpp
new file mode 100644
index 0000000..521ff0e
--- /dev/null
+++ b/src/extension/internal/cdr-input.cpp
@@ -0,0 +1,382 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This file came from libwpg as a source, their utility wpg2svg
+ * specifically. It has been modified to work as an Inkscape extension.
+ * The Inkscape extension code is covered by this copyright, but the
+ * rest is covered by the one below.
+ *
+ * Authors:
+ * Fridrich Strba (fridrich.strba@bluewin.ch)
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <cstdio>
+
+#include "cdr-input.h"
+
+#ifdef WITH_LIBCDR
+
+#include <string>
+#include <cstring>
+
+#include <libcdr/libcdr.h>
+
+#include <librevenge-stream/librevenge-stream.h>
+
+using librevenge::RVNGString;
+using librevenge::RVNGFileStream;
+using librevenge::RVNGStringVector;
+
+#include <gtkmm/grid.h>
+#include <gtkmm/spinbutton.h>
+
+#include "extension/system.h"
+#include "extension/input.h"
+
+#include "document.h"
+#include "inkscape.h"
+
+#include "ui/dialog-events.h"
+#include <glibmm/i18n.h>
+
+#include "ui/view/svg-view-widget.h"
+
+#include "object/sp-root.h"
+
+#include "util/units.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+
+class CdrImportDialog : public Gtk::Dialog {
+public:
+ CdrImportDialog(const std::vector<RVNGString> &vec);
+ ~CdrImportDialog() override;
+
+ bool showDialog();
+ unsigned getSelectedPage();
+ void getImportSettings(Inkscape::XML::Node *prefs);
+
+private:
+ void _setPreviewPage();
+
+ // Signal handlers
+ void _onPageNumberChanged();
+ void _onSpinButtonPress(GdkEventButton* button_event);
+ void _onSpinButtonRelease(GdkEventButton* button_event);
+
+ class Gtk::Box * vbox1;
+ class Inkscape::UI::View::SVGViewWidget * _previewArea;
+ class Gtk::Button * cancelbutton;
+ class Gtk::Button * okbutton;
+
+ class Gtk::Box * _page_selector_box;
+ class Gtk::Label * _labelSelect;
+ class Gtk::Label * _labelTotalPages;
+ class Gtk::SpinButton * _pageNumberSpin;
+
+ const std::vector<RVNGString> &_vec; // Document to be imported
+ unsigned _current_page; // Current selected page
+ bool _spinning; // whether SpinButton is pressed (i.e. we're "spinning")
+};
+
+CdrImportDialog::CdrImportDialog(const std::vector<RVNGString> &vec)
+ : _previewArea(nullptr)
+ , _vec(vec)
+ , _current_page(1)
+ , _spinning(false)
+{
+ int num_pages = _vec.size();
+ if ( num_pages <= 1 )
+ return;
+
+ // Dialog settings
+ this->set_title(_("Page Selector"));
+ this->set_modal(true);
+ sp_transientize(GTK_WIDGET(this->gobj())); //Make transient
+ this->property_window_position().set_value(Gtk::WIN_POS_NONE);
+ this->set_resizable(true);
+ this->property_destroy_with_parent().set_value(false);
+
+ // Preview area
+ vbox1 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ this->get_content_area()->pack_start(*vbox1);
+
+ // CONTROLS
+ _page_selector_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+
+ // "Select page:" label
+ _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:")));
+ _labelTotalPages = Gtk::manage(new class Gtk::Label());
+ _labelSelect->set_line_wrap(false);
+ _labelSelect->set_use_markup(false);
+ _labelSelect->set_selectable(false);
+ _page_selector_box->pack_start(*_labelSelect, Gtk::PACK_SHRINK);
+
+ // Adjustment + spinner
+ auto pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _vec.size(), 1, 10, 0);
+ _pageNumberSpin = Gtk::manage(new Gtk::SpinButton(pageNumberSpin_adj, 1, 0));
+ _pageNumberSpin->set_can_focus();
+ _pageNumberSpin->set_update_policy(Gtk::UPDATE_ALWAYS);
+ _pageNumberSpin->set_numeric(true);
+ _pageNumberSpin->set_wrap(false);
+ _page_selector_box->pack_start(*_pageNumberSpin, Gtk::PACK_SHRINK);
+
+ _labelTotalPages->set_line_wrap(false);
+ _labelTotalPages->set_use_markup(false);
+ _labelTotalPages->set_selectable(false);
+ gchar *label_text = g_strdup_printf(_("out of %i"), num_pages);
+ _labelTotalPages->set_label(label_text);
+ g_free(label_text);
+ _page_selector_box->pack_start(*_labelTotalPages, Gtk::PACK_SHRINK);
+
+ vbox1->pack_end(*_page_selector_box, Gtk::PACK_SHRINK);
+
+ // Buttons
+ cancelbutton = Gtk::manage(new Gtk::Button(_("_Cancel"), true));
+ okbutton = Gtk::manage(new Gtk::Button(_("_OK"), true));
+ this->add_action_widget(*cancelbutton, Gtk::RESPONSE_CANCEL);
+ this->add_action_widget(*okbutton, Gtk::RESPONSE_OK);
+
+ // Show all widgets in dialog
+ this->show_all();
+
+ // Connect signals
+ _pageNumberSpin->signal_value_changed().connect(sigc::mem_fun(*this, &CdrImportDialog::_onPageNumberChanged));
+ _pageNumberSpin->signal_button_press_event().connect_notify(sigc::mem_fun(*this, &CdrImportDialog::_onSpinButtonPress));
+ _pageNumberSpin->signal_button_release_event().connect_notify(sigc::mem_fun(*this, &CdrImportDialog::_onSpinButtonRelease));
+
+ _setPreviewPage();
+}
+
+CdrImportDialog::~CdrImportDialog() = default;
+
+bool CdrImportDialog::showDialog()
+{
+ show();
+ gint b = run();
+ hide();
+ if (b == Gtk::RESPONSE_OK || b == Gtk::RESPONSE_ACCEPT) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+unsigned CdrImportDialog::getSelectedPage()
+{
+ return _current_page;
+}
+
+void CdrImportDialog::_onPageNumberChanged()
+{
+ unsigned page = static_cast<unsigned>(_pageNumberSpin->get_value_as_int());
+ _current_page = CLAMP(page, 1U, _vec.size());
+ _setPreviewPage();
+}
+
+void CdrImportDialog::_onSpinButtonPress(GdkEventButton* /*button_event*/)
+{
+ _spinning = true;
+}
+
+void CdrImportDialog::_onSpinButtonRelease(GdkEventButton* /*button_event*/)
+{
+ _spinning = false;
+ _setPreviewPage();
+}
+
+/**
+ * \brief Renders the given page's thumbnail
+ */
+void CdrImportDialog::_setPreviewPage()
+{
+ if (_spinning) {
+ return;
+ }
+
+ SPDocument *doc = SPDocument::createNewDocFromMem(_vec[_current_page-1].cstr(), strlen(_vec[_current_page-1].cstr()), false);
+ if(!doc) {
+ g_warning("CDR import: Could not create preview for page %d", _current_page);
+ gchar const *no_preview_template = R"A(
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
+ <path d='M 82,10 18,74 m 0,-64 64,64' style='fill:none;stroke:#ff0000;stroke-width:2px;'/>
+ <rect x='18' y='10' width='64' height='64' style='fill:none;stroke:#000000;stroke-width:1.5px;'/>
+ <text x='50' y='92' style='font-size:10px;text-anchor:middle;font-family:sans-serif;'>%s</text>
+ </svg>
+ )A";
+ gchar * no_preview = g_strdup_printf(no_preview_template, _("No preview"));
+ doc = SPDocument::createNewDocFromMem(no_preview, strlen(no_preview), false);
+ g_free(no_preview);
+ }
+
+ if (!doc) {
+ std::cerr << "CdrImportDialog::_setPreviewPage: No document!" << std::endl;
+ return;
+ }
+
+ if (_previewArea) {
+ _previewArea->setDocument(doc);
+ } else {
+ _previewArea = Gtk::manage(new Inkscape::UI::View::SVGViewWidget(doc));
+ vbox1->pack_start(*_previewArea, Gtk::PACK_EXPAND_WIDGET, 0);
+ }
+
+ _previewArea->setResize(400, 400);
+ _previewArea->show_all();
+}
+
+SPDocument *CdrInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri)
+{
+ #ifdef _WIN32
+ // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows
+ // therefore attempt to convert uri to the system codepage
+ // even if this is not possible the alternate short (8.3) file name will be used if available
+ gchar * converted_uri = g_win32_locale_filename_from_utf8(uri);
+ RVNGFileStream input(converted_uri);
+ g_free(converted_uri);
+ #else
+ RVNGFileStream input(uri);
+ #endif
+
+ if (!libcdr::CDRDocument::isSupported(&input)) {
+ return nullptr;
+ }
+
+ RVNGStringVector output;
+ librevenge::RVNGSVGDrawingGenerator generator(output, "svg");
+
+ if (!libcdr::CDRDocument::parse(&input, &generator)) {
+ return nullptr;
+ }
+
+ if (output.empty()) {
+ return nullptr;
+ }
+
+ std::vector<RVNGString> tmpSVGOutput;
+ for (unsigned i=0; i<output.size(); ++i) {
+ RVNGString tmpString("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
+ tmpString.append(output[i]);
+ tmpSVGOutput.push_back(tmpString);
+ }
+
+ unsigned page_num = 1;
+
+ // If only one page is present, import that one without bothering user
+ if (tmpSVGOutput.size() > 1) {
+ CdrImportDialog *dlg = nullptr;
+ if (INKSCAPE.use_gui()) {
+ dlg = new CdrImportDialog(tmpSVGOutput);
+ if (!dlg->showDialog()) {
+ delete dlg;
+ throw Input::open_cancelled();
+ }
+ }
+
+ // Get needed page
+ if (dlg) {
+ page_num = dlg->getSelectedPage();
+ if (page_num < 1)
+ page_num = 1;
+ if (page_num > tmpSVGOutput.size())
+ page_num = tmpSVGOutput.size();
+ }
+ }
+
+ SPDocument * doc = SPDocument::createNewDocFromMem(tmpSVGOutput[page_num-1].cstr(), strlen(tmpSVGOutput[page_num-1].cstr()), TRUE);
+
+ if (doc && !doc->getRoot()->viewBox_set) {
+ // Scales the document to account for 72dpi scaling in librevenge(<=0.0.4)
+ doc->setWidth(Inkscape::Util::Quantity(doc->getWidth().quantity, "pt"), false);
+ doc->setHeight(Inkscape::Util::Quantity(doc->getHeight().quantity, "pt"), false);
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value("pt"), doc->getHeight().value("pt")));
+ }
+ return doc;
+}
+
+#include "clear-n_.h"
+
+void CdrInput::init()
+{
+ // clang-format off
+ /* 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());
+ // clang-format on
+
+ return;
+
+} // init
+
+} } } /* namespace Inkscape, Extension, Implementation */
+#endif /* WITH_LIBCDR */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/cdr-input.h b/src/extension/internal/cdr-input.h
new file mode 100644
index 0000000..546151f
--- /dev/null
+++ b/src/extension/internal/cdr-input.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This code abstracts the libwpg interfaces into the Inkscape
+ * input extension interface.
+ *
+ * Authors:
+ * Fridrich Strba (fridrich.strba@bluewin.ch)
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __EXTENSION_INTERNAL_CDROUTPUT_H__
+#define __EXTENSION_INTERNAL_CDROUTPUT_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef WITH_LIBCDR
+
+#include <gtkmm/dialog.h>
+
+#include "../implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class CdrInput : public Inkscape::Extension::Implementation::Implementation {
+ CdrInput () = default;;
+public:
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+ static void init( );
+
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* WITH_LIBCDR */
+#endif /* __EXTENSION_INTERNAL_CDROUTPUT_H__ */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/clear-n_.h b/src/extension/internal/clear-n_.h
new file mode 100644
index 0000000..ecd8eaa
--- /dev/null
+++ b/src/extension/internal/clear-n_.h
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ \file clear-n_.h
+
+ A way to clear the N_ macro, which is defined as an inline function.
+ Unfortunately, this makes it so it is hard to use in static strings
+ where you only want to translate a small part. Including this
+ turns it back into a a macro.
+*/
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef N_
+#undef N_
+#endif
+#define N_(x) x
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/emf-inout.cpp b/src/extension/internal/emf-inout.cpp
new file mode 100644
index 0000000..71a5869
--- /dev/null
+++ b/src/extension/internal/emf-inout.cpp
@@ -0,0 +1,3688 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Windows-only Enhanced Metafile input and output.
+ */
+/* Authors:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * David Mathog
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ * References:
+ * - How to Create & Play Enhanced Metafiles in Win32
+ * http://support.microsoft.com/kb/q145999/
+ * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles
+ * http://support.microsoft.com/kb/q66949/
+ * - Metafile Functions
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp
+ * - Metafile Structures
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp
+ */
+
+#include <cstdio>
+#include <cstdlib>
+#include <cstdint>
+#include <3rdparty/libuemf/symbol_convert.h>
+
+#include "emf-inout.h"
+
+#include "clear-n_.h"
+#include "display/drawing-item.h"
+#include "display/drawing.h"
+#include "document.h"
+#include "extension/db.h"
+#include "extension/input.h"
+#include "extension/output.h"
+#include "extension/print.h"
+#include "extension/system.h"
+#include "object/sp-path.h"
+#include "object/sp-root.h"
+#include "path/path-boolop.h"
+#include "print.h"
+#include "svg/css-ostringstream.h"
+#include "svg/svg.h"
+#include "util/units.h"
+
+#include "emf-print.h"
+
+#define PRINT_EMF "org.inkscape.print.emf"
+
+#ifndef U_PS_JOIN_MASK
+#define U_PS_JOIN_MASK (U_PS_JOIN_BEVEL|U_PS_JOIN_MITER|U_PS_JOIN_ROUND)
+#endif
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+static uint32_t ICMmode = 0; // not used yet, but code to read it from EMF implemented
+static uint32_t BLTmode = 0;
+float faraway = 10000000; // used in "exclude" clips, hopefully well outside any real drawing!
+
+Emf::Emf () // The null constructor
+{
+ return;
+}
+
+
+Emf::~Emf () //The destructor
+{
+ return;
+}
+
+
+bool
+Emf::check (Inkscape::Extension::Extension * /*module*/)
+{
+ if (nullptr == Inkscape::Extension::db.get(PRINT_EMF))
+ return FALSE;
+ return TRUE;
+}
+
+
+void
+Emf::print_document_to_file(SPDocument *doc, const gchar *filename)
+{
+ Inkscape::Extension::Print *mod;
+ SPPrintContext context;
+ const gchar *oldconst;
+ gchar *oldoutput;
+ unsigned int ret;
+
+ doc->ensureUpToDate();
+
+ mod = Inkscape::Extension::get_print(PRINT_EMF);
+ oldconst = mod->get_param_string("destination");
+ oldoutput = g_strdup(oldconst);
+ mod->set_param_string("destination", filename);
+
+/* Start */
+ context.module = mod;
+ /* fixme: This has to go into module constructor somehow */
+ /* Create new arena */
+ mod->base = doc->getRoot();
+ Inkscape::Drawing drawing;
+ mod->dkey = SPItem::display_key_new(1);
+ mod->root = mod->base->invoke_show(drawing, mod->dkey, SP_ITEM_SHOW_DISPLAY);
+ drawing.setRoot(mod->root);
+ /* Print document */
+ ret = mod->begin(doc);
+ if (ret) {
+ g_free(oldoutput);
+ throw Inkscape::Extension::Output::save_failed();
+ }
+ mod->base->invoke_print(&context);
+ (void) mod->finish();
+ /* Release arena */
+ mod->base->invoke_hide(mod->dkey);
+ mod->base = nullptr;
+ mod->root = nullptr; // deleted by invoke_hide
+/* end */
+
+ mod->set_param_string("destination", oldoutput);
+ g_free(oldoutput);
+
+ return;
+}
+
+
+void
+Emf::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
+{
+ Inkscape::Extension::Extension * ext;
+
+ ext = Inkscape::Extension::db.get(PRINT_EMF);
+ if (ext == nullptr)
+ return;
+
+ bool new_val = mod->get_param_bool("textToPath");
+ bool new_FixPPTCharPos = mod->get_param_bool("FixPPTCharPos"); // character position bug
+ // reserve FixPPT2 for opacity bug. Currently EMF does not export opacity values
+ bool new_FixPPTDashLine = mod->get_param_bool("FixPPTDashLine"); // dashed line bug
+ bool new_FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys"); // gradient bug
+ bool new_FixPPTLinGrad = mod->get_param_bool("FixPPTLinGrad"); // allow native rectangular linear gradient
+ bool new_FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch"); // force all patterns as standard EMF hatch
+ bool new_FixImageRot = mod->get_param_bool("FixImageRot"); // remove rotations on images
+
+ TableGen( //possibly regenerate the unicode-convert tables
+ mod->get_param_bool("TnrToSymbol"),
+ mod->get_param_bool("TnrToWingdings"),
+ mod->get_param_bool("TnrToZapfDingbats"),
+ mod->get_param_bool("UsePUA")
+ );
+
+ ext->set_param_bool("FixPPTCharPos",new_FixPPTCharPos); // Remember to add any new ones to PrintEmf::init or a mysterious failure will result!
+ ext->set_param_bool("FixPPTDashLine",new_FixPPTDashLine);
+ ext->set_param_bool("FixPPTGrad2Polys",new_FixPPTGrad2Polys);
+ ext->set_param_bool("FixPPTLinGrad",new_FixPPTLinGrad);
+ ext->set_param_bool("FixPPTPatternAsHatch",new_FixPPTPatternAsHatch);
+ ext->set_param_bool("FixImageRot",new_FixImageRot);
+ ext->set_param_bool("textToPath", new_val);
+
+ // ensure usage of dot as decimal separator in scanf/printf functions (independently of current locale)
+ char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr));
+ setlocale(LC_NUMERIC, "C");
+
+ print_document_to_file(doc, filename);
+
+ // restore decimal separator used in scanf/printf functions to initial value
+ setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ return;
+}
+
+
+/* given the transformation matrix from worldTransform return the scale in the matrix part. Assumes that the
+ matrix is not used to skew, invert, or make another distorting transformation. */
+double Emf::current_scale(PEMF_CALLBACK_DATA d){
+ double scale =
+ d->dc[d->level].worldTransform.eM11 * d->dc[d->level].worldTransform.eM22 -
+ d->dc[d->level].worldTransform.eM12 * d->dc[d->level].worldTransform.eM21;
+ if(scale <= 0.0)scale=1.0; /* something is dreadfully wrong with the matrix, but do not crash over it */
+ scale=sqrt(scale);
+ return(scale);
+}
+
+/* given the transformation matrix from worldTransform and the current x,y position in inkscape coordinates,
+ generate an SVG transform that gives the same amount of rotation, no scaling, and maps x,y back onto x,y. This is used for
+ rotating objects when the location of at least one point in that object is known. Returns:
+ "matrix(a,b,c,d,e,f)" (WITH the double quotes)
+*/
+std::string Emf::current_matrix(PEMF_CALLBACK_DATA d, double x, double y, int useoffset){
+ SVGOStringStream cxform;
+ double scale = current_scale(d);
+ cxform << "\"matrix(";
+ cxform << d->dc[d->level].worldTransform.eM11/scale; cxform << ",";
+ cxform << d->dc[d->level].worldTransform.eM12/scale; cxform << ",";
+ cxform << d->dc[d->level].worldTransform.eM21/scale; cxform << ",";
+ cxform << d->dc[d->level].worldTransform.eM22/scale; cxform << ",";
+ if(useoffset){
+ /* for the "new" coordinates drop the worldtransform translations, not used here */
+ double newx = x * d->dc[d->level].worldTransform.eM11/scale + y * d->dc[d->level].worldTransform.eM21/scale;
+ double newy = x * d->dc[d->level].worldTransform.eM12/scale + y * d->dc[d->level].worldTransform.eM22/scale;
+ cxform << x - newx; cxform << ",";
+ cxform << y - newy;
+ }
+ else {
+ cxform << "0,0";
+ }
+ cxform << ")\"";
+ return(cxform.str());
+}
+
+/* given the transformation matrix from worldTransform return the rotation angle in radians.
+ counter clockwise from the x axis. */
+double Emf::current_rotation(PEMF_CALLBACK_DATA d){
+ return -std::atan2(d->dc[d->level].worldTransform.eM12, d->dc[d->level].worldTransform.eM11);
+}
+
+/* Add another 100 blank slots to the hatches array.
+*/
+void Emf::enlarge_hatches(PEMF_CALLBACK_DATA d){
+ d->hatches.size += 100;
+ d->hatches.strings = (char **) realloc(d->hatches.strings,d->hatches.size * sizeof(char *));
+}
+
+/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1)
+*/
+int Emf::in_hatches(PEMF_CALLBACK_DATA d, char *test){
+ int i;
+ for(i=0; i<d->hatches.count; i++){
+ if(strcmp(test,d->hatches.strings[i])==0)return(i+1);
+ }
+ return(0);
+}
+
+/* (Conditionally) add a hatch. If a matching hatch already exists nothing happens. If one
+ does not exist it is added to the hatches list and also entered into <defs>.
+ This is also used to add the path part of the hatches, which they reference with a xlink:href
+*/
+uint32_t Emf::add_hatch(PEMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor){
+ char hatchname[64]; // big enough
+ char hpathname[64]; // big enough
+ char hbkname[64]; // big enough
+ char tmpcolor[8];
+ char bkcolor[8];
+ uint32_t idx;
+
+ switch(hatchType){
+ case U_HS_SOLIDTEXTCLR:
+ case U_HS_DITHEREDTEXTCLR:
+ sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].textColor));
+ break;
+ case U_HS_SOLIDBKCLR:
+ case U_HS_DITHEREDBKCLR:
+ sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor));
+ break;
+ default:
+ sprintf(tmpcolor,"%6.6X",sethexcolor(hatchColor));
+ break;
+ }
+
+ /* For both bkMode types set the PATH + FOREGROUND COLOR for the indicated standard hatch.
+ This will be used late to compose, or recompose the transparent or opaque final hatch.*/
+
+ std::string refpath; // used to reference later the path pieces which are about to be created
+ sprintf(hpathname,"EMFhpath%d_%s",hatchType,tmpcolor);
+ idx = in_hatches(d,hpathname);
+ auto & defs = d->defs;
+ if(!idx){ // add path/color if not already present
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hpathname);
+
+ defs += "\n";
+ switch(hatchType){
+ case U_HS_HORIZONTAL:
+ defs += " <path id=\"";
+ defs += hpathname;
+ defs += "\" d=\"M 0 0 6 0\" style=\"fill:none;stroke:#";
+ defs += tmpcolor;
+ defs += "\" />\n";
+ break;
+ case U_HS_VERTICAL:
+ defs += " <path id=\"";
+ defs += hpathname;
+ defs += "\" d=\"M 0 0 0 6\" style=\"fill:none;stroke:#";
+ defs += tmpcolor;
+ defs += "\" />\n";
+ break;
+ case U_HS_FDIAGONAL:
+ defs += " <line id=\"sub";
+ defs += hpathname;
+ defs += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#";
+ defs += tmpcolor;
+ defs += "\"/>\n";
+ break;
+ case U_HS_BDIAGONAL:
+ defs += " <line id=\"sub";
+ defs += hpathname;
+ defs += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#";
+ defs += tmpcolor;
+ defs += "\"/>\n";
+ break;
+ case U_HS_CROSS:
+ defs += " <path id=\"";
+ defs += hpathname;
+ defs += "\" d=\"M 0 0 6 0 M 0 0 0 6\" style=\"fill:none;stroke:#";
+ defs += tmpcolor;
+ defs += "\" />\n";
+ break;
+ case U_HS_DIAGCROSS:
+ defs += " <line id=\"subfd";
+ defs += hpathname;
+ defs += "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#";
+ defs += tmpcolor;
+ defs += "\"/>\n";
+ defs += " <line id=\"subbd";
+ defs += hpathname;
+ defs += "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#";
+ defs += tmpcolor;
+ defs += "\"/>\n";
+ break;
+ case U_HS_SOLIDCLR:
+ case U_HS_DITHEREDCLR:
+ case U_HS_SOLIDTEXTCLR:
+ case U_HS_DITHEREDTEXTCLR:
+ case U_HS_SOLIDBKCLR:
+ case U_HS_DITHEREDBKCLR:
+ default:
+ defs += " <path id=\"";
+ defs += hpathname;
+ defs += "\" d=\"M 0 0 6 0 6 6 0 6 z\" style=\"fill:#";
+ defs += tmpcolor;
+ defs += ";stroke:none";
+ defs += "\" />\n";
+ break;
+ }
+ }
+
+ // References to paths possibly just created above. These will be used in the actual patterns.
+ switch(hatchType){
+ case U_HS_HORIZONTAL:
+ case U_HS_VERTICAL:
+ case U_HS_CROSS:
+ case U_HS_SOLIDCLR:
+ case U_HS_DITHEREDCLR:
+ case U_HS_SOLIDTEXTCLR:
+ case U_HS_DITHEREDTEXTCLR:
+ case U_HS_SOLIDBKCLR:
+ case U_HS_DITHEREDBKCLR:
+ default:
+ refpath += " <use xlink:href=\"#";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ break;
+ case U_HS_FDIAGONAL:
+ case U_HS_BDIAGONAL:
+ refpath += " <use xlink:href=\"#sub";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ refpath += " <use xlink:href=\"#sub";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(6,0)\" />\n";
+ refpath += " <use xlink:href=\"#sub";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(-6,0)\" />\n";
+ break;
+ case U_HS_DIAGCROSS:
+ refpath += " <use xlink:href=\"#subfd";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ refpath += " <use xlink:href=\"#subfd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(6,0)\"/>\n";
+ refpath += " <use xlink:href=\"#subfd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(-6,0)\"/>\n";
+ refpath += " <use xlink:href=\"#subbd";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ refpath += " <use xlink:href=\"#subbd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(6,0)\"/>\n";
+ refpath += " <use xlink:href=\"#subbd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(-6,0)\"/>\n";
+ break;
+ }
+
+ if(d->dc[d->level].bkMode == U_TRANSPARENT || hatchType >= U_HS_SOLIDCLR){
+ sprintf(hatchname,"EMFhatch%d_%s",hatchType,tmpcolor);
+ sprintf(hpathname,"EMFhpath%d_%s",hatchType,tmpcolor);
+ idx = in_hatches(d,hatchname);
+ if(!idx){ // add it if not already present
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hatchname);
+ defs += "\n";
+ defs += " <pattern id=\"";
+ defs += hatchname;
+ defs += "\" xlink:href=\"#EMFhbasepattern\">\n";
+ defs += refpath;
+ defs += " </pattern>\n";
+ idx = d->hatches.count;
+ }
+ }
+ else { // bkMode==U_OPAQUE
+ /* Set up an object in the defs for this background, if there is not one already there */
+ sprintf(bkcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor));
+ sprintf(hbkname,"EMFhbkclr_%s",bkcolor);
+ idx = in_hatches(d,hbkname);
+ if(!idx){ // add path/color if not already present. Hatchtype is not needed in the name.
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hbkname);
+
+ defs += "\n";
+ defs += " <rect id=\"";
+ defs += hbkname;
+ defs += "\" x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"#";
+ defs += bkcolor;
+ defs += "\" />\n";
+ }
+
+ // this is the pattern, its name will show up in Inkscape's pattern selector
+ sprintf(hatchname,"EMFhatch%d_%s_%s",hatchType,tmpcolor,bkcolor);
+ idx = in_hatches(d,hatchname);
+ if(!idx){ // add it if not already present
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hatchname);
+ defs += "\n";
+ defs += " <pattern id=\"";
+ defs += hatchname;
+ defs += "\" xlink:href=\"#EMFhbasepattern\">\n";
+ defs += " <use xlink:href=\"#";
+ defs += hbkname;
+ defs += "\" />\n";
+ defs += refpath;
+ defs += " </pattern>\n";
+ idx = d->hatches.count;
+ }
+ }
+ return(idx-1);
+}
+
+/* Add another 100 blank slots to the images array.
+*/
+void Emf::enlarge_images(PEMF_CALLBACK_DATA d){
+ d->images.size += 100;
+ d->images.strings = (char **) realloc(d->images.strings,d->images.size * sizeof(char *));
+}
+
+/* See if the image string is already in the list. If it is return its position (1->n, not 1-n-1)
+*/
+int Emf::in_images(PEMF_CALLBACK_DATA d, const char *test){
+ int i;
+ for(i=0; i<d->images.count; i++){
+ if(strcmp(test,d->images.strings[i])==0)return(i+1);
+ }
+ return(0);
+}
+
+/* (Conditionally) add an image. If a matching image already exists nothing happens. If one
+ does not exist it is added to the images list and also entered into <defs>.
+
+ U_EMRCREATEMONOBRUSH records only work when the bitmap is monochrome. If we hit one that isn't
+ set idx to 2^32-1 and let the caller handle it.
+*/
+uint32_t Emf::add_image(PEMF_CALLBACK_DATA d, void *pEmr, uint32_t cbBits, uint32_t cbBmi,
+ uint32_t iUsage, uint32_t offBits, uint32_t offBmi){
+
+ uint32_t idx;
+ char imagename[64]; // big enough
+ char imrotname[64]; // big enough
+ char xywh[64]; // big enough
+ int dibparams = U_BI_UNKNOWN; // type of image not yet determined
+
+ MEMPNG mempng; // PNG in memory comes back in this
+ mempng.buffer = nullptr;
+
+ char *rgba_px = nullptr; // RGBA pixels
+ const char *px = nullptr; // DIB pixels
+ const U_RGBQUAD *ct = nullptr; // DIB color table
+ U_RGBQUAD ct2[2];
+ uint32_t width, height, colortype, numCt, invert; // if needed these values will be set in get_DIB_params
+ if(cbBits && cbBmi && (iUsage == U_DIB_RGB_COLORS)){
+ // next call returns pointers and values, but allocates no memory
+ dibparams = get_DIB_params((const char *)pEmr, offBits, offBmi, &px, (const U_RGBQUAD **) &ct,
+ &numCt, &width, &height, &colortype, &invert);
+ if(dibparams ==U_BI_RGB){
+ // U_EMRCREATEMONOBRUSH uses text/bk colors instead of what is in the color map.
+ if(((PU_EMR)pEmr)->iType == U_EMR_CREATEMONOBRUSH){
+ if(numCt==2){
+ ct2[0] = U_RGB2BGR(d->dc[d->level].textColor);
+ ct2[1] = U_RGB2BGR(d->dc[d->level].bkColor);
+ ct = &ct2[0];
+ }
+ else { // This record is invalid, nothing more to do here, let caller handle it
+ return(U_EMR_INVALID);
+ }
+ }
+
+ if(!DIB_to_RGBA(
+ px, // DIB pixel array
+ ct, // DIB color table
+ numCt, // DIB color table number of entries
+ &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free.
+ width, // Width of pixel array in record
+ height, // Height of pixel array in record
+ colortype, // DIB BitCount Enumeration
+ numCt, // Color table used if not 0
+ invert // If DIB rows are in opposite order from RGBA rows
+ )){
+ toPNG( // Get the image from the RGBA px into mempng
+ &mempng,
+ width, height, // of the SRC bitmap
+ rgba_px
+ );
+ free(rgba_px);
+ }
+ }
+ }
+
+ gchar *base64String=nullptr;
+ if(dibparams == U_BI_JPEG || dibparams==U_BI_PNG){ // image was binary png or jpg in source file
+ base64String = g_base64_encode((guchar*) px, numCt );
+ }
+ else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine
+ base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size );
+ free(mempng.buffer);
+ }
+ else { // unknown or unsupported image type or failed conversion, insert the common bad image picture
+ width = 3;
+ height = 4;
+ base64String = bad_image_png();
+ }
+
+ idx = in_images(d, (char *) base64String);
+ auto & defs = d->defs;
+ if(!idx){ // add it if not already present - we looked at the actual data for comparison
+ if(d->images.count == d->images.size){ enlarge_images(d); }
+ idx = d->images.count;
+ d->images.strings[d->images.count++]=strdup(base64String);
+
+ sprintf(imagename,"EMFimage%d",idx++);
+ sprintf(xywh," x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" ",width,height); // reuse this buffer
+
+ defs += "\n";
+ defs += " <image id=\"";
+ defs += imagename;
+ defs += "\"\n ";
+ defs += xywh;
+ defs += "\n";
+ if(dibparams == U_BI_JPEG){ defs += " xlink:href=\"data:image/jpeg;base64,"; }
+ else { defs += " xlink:href=\"data:image/png;base64,"; }
+ defs += base64String;
+ defs += "\"\n";
+ defs += " preserveAspectRatio=\"none\"\n";
+ defs += " />\n";
+
+
+ defs += "\n";
+ defs += " <pattern id=\"";
+ defs += imagename;
+ defs += "_ref\"\n ";
+ defs += xywh;
+ defs += "\n patternUnits=\"userSpaceOnUse\"";
+ defs += " >\n";
+ defs += " <use id=\"";
+ defs += imagename;
+ defs += "_ign\" ";
+ defs += " xlink:href=\"#";
+ defs += imagename;
+ defs += "\" />\n";
+ defs += " ";
+ defs += " </pattern>\n";
+ }
+ g_free(base64String);//wait until this point to free because it might be a duplicate image
+
+ /* image allows the inner image to be rotated nicely, load this one second only if needed
+ imagename retained from above
+ Here comes a dreadful hack. How do we determine if this rotation of the base image has already
+ been loaded? The image names contain no identifying information, they are just numbered sequentially.
+ So the rotated name is EMFrotimage###_XXXXXX, where ### is the number of the referred to image, and
+ XXXX is the rotation in radians x 1000000 and truncated. That is then stored in BASE64 as the "image".
+ The corresponding SVG generated though is not for an image, but a reference to an image.
+ The name of the pattern MUST still be EMFimage###_ref or output_style() will not be able to use it.
+ */
+ if(current_rotation(d) >= 0.00001 || current_rotation(d) <= -0.00001){ /* some rotation, allow a little rounding error around 0 degrees */
+ int tangle = round(current_rotation(d)*1000000.0);
+ sprintf(imrotname,"EMFrotimage%d_%d",idx-1,tangle);
+ base64String = g_base64_encode((guchar*) imrotname, strlen(imrotname) );
+ idx = in_images(d, (char *) base64String); // scan for this "image"
+ if(!idx){
+ if(d->images.count == d->images.size){ enlarge_images(d); }
+ idx = d->images.count;
+ d->images.strings[d->images.count++]=strdup(base64String);
+ sprintf(imrotname,"EMFimage%d",idx++);
+
+ defs += "\n";
+ defs += " <pattern\n";
+ defs += " id=\"";
+ defs += imrotname;
+ defs += "_ref\"\n";
+ defs += " xlink:href=\"#";
+ defs += imagename;
+ defs += "_ref\"\n";
+ defs += " patternTransform=";
+ defs += current_matrix(d, 0.0, 0.0, 0); //j use offset 0,0
+ defs += " />\n";
+ }
+ g_free(base64String);
+ }
+
+ return(idx-1);
+}
+
+/* Add another 100 blank slots to the gradients array.
+*/
+void Emf::enlarge_gradients(PEMF_CALLBACK_DATA d){
+ d->gradients.size += 100;
+ d->gradients.strings = (char **) realloc(d->gradients.strings,d->gradients.size * sizeof(char *));
+}
+
+/* See if the gradient name is already in the list. If it is return its position (1->n, not 1-n-1)
+*/
+int Emf::in_gradients(PEMF_CALLBACK_DATA d, const char *test){
+ int i;
+ for(i=0; i<d->gradients.count; i++){
+ if(strcmp(test,d->gradients.strings[i])==0)return(i+1);
+ }
+ return(0);
+}
+
+U_COLORREF trivertex_to_colorref(U_TRIVERTEX tv){
+ U_COLORREF uc;
+ uc.Red = tv.Red >> 8;
+ uc.Green = tv.Green >> 8;
+ uc.Blue = tv.Blue >> 8;
+ uc.Reserved = tv.Alpha >> 8; // Not used
+ return(uc);
+}
+
+/* (Conditionally) add a gradient. If a matching gradient already exists nothing happens. If one
+ does not exist it is added to the gradients list and also entered into <defs>.
+ Only call this with H or V gradient, not a triangle.
+*/
+uint32_t Emf::add_gradient(PEMF_CALLBACK_DATA d, uint32_t gradientType, U_TRIVERTEX tv1, U_TRIVERTEX tv2){
+ char hgradname[64]; // big enough
+ char tmpcolor1[8];
+ char tmpcolor2[8];
+ char gradc;
+ uint32_t idx;
+ std::string x2,y2;
+
+ U_COLORREF gradientColor1 = trivertex_to_colorref(tv1);
+ U_COLORREF gradientColor2 = trivertex_to_colorref(tv2);
+
+
+ sprintf(tmpcolor1,"%6.6X",sethexcolor(gradientColor1));
+ sprintf(tmpcolor2,"%6.6X",sethexcolor(gradientColor2));
+ switch(gradientType){
+ case U_GRADIENT_FILL_RECT_H:
+ gradc='H';
+ x2="100";
+ y2="0";
+ break;
+ case U_GRADIENT_FILL_RECT_V:
+ gradc='V';
+ x2="0";
+ y2="100";
+ break;
+ default: // this should never happen, but fill these in to avoid compiler warnings
+ gradc='!';
+ x2="0";
+ y2="0";
+ break;
+ }
+
+ /* Even though the gradient was defined as Horizontal or Vertical if the rectangle is rotated it needs to
+ be at some other alignment, and that needs gradienttransform. Set the name using the same sort of hack
+ as for add_image.
+ */
+ int tangle = round(current_rotation(d)*1000000.0);
+ sprintf(hgradname,"LinGrd%c_%s_%s_%d",gradc,tmpcolor1,tmpcolor2,tangle);
+
+ idx = in_gradients(d,hgradname);
+ if(!idx){ // gradient does not yet exist
+ if(d->gradients.count == d->gradients.size){ enlarge_gradients(d); }
+ d->gradients.strings[d->gradients.count++]=strdup(hgradname);
+ idx = d->gradients.count;
+ SVGOStringStream stmp;
+ stmp << " <linearGradient id=\"";
+ stmp << hgradname;
+ stmp << "\" x1=\"";
+ stmp << pix_to_x_point(d, tv1.x , tv1.y);
+ stmp << "\" y1=\"";
+ stmp << pix_to_y_point(d, tv1.x , tv1.y);
+ stmp << "\" x2=\"";
+ if(gradc=='H'){ // UR corner
+ stmp << pix_to_x_point(d, tv2.x , tv1.y);
+ stmp << "\" y2=\"";
+ stmp << pix_to_y_point(d, tv2.x , tv1.y);
+ }
+ else { // LL corner
+ stmp << pix_to_x_point(d, tv1.x , tv2.y);
+ stmp << "\" y2=\"";
+ stmp << pix_to_y_point(d, tv1.x , tv2.y);
+ }
+ stmp << "\" gradientTransform=\"(1,0,0,1,0,0)\"";
+ stmp << " gradientUnits=\"userSpaceOnUse\"\n";
+ stmp << ">\n";
+ stmp << " <stop offset=\"0\" style=\"stop-color:#";
+ stmp << tmpcolor1;
+ stmp << ";stop-opacity:1\" />\n";
+ stmp << " <stop offset=\"1\" style=\"stop-color:#";
+ stmp << tmpcolor2;
+ stmp << ";stop-opacity:1\" />\n";
+ stmp << " </linearGradient>\n";
+ d->defs += stmp.str().c_str();
+ }
+
+ return(idx-1);
+}
+
+/* Add another 100 blank slots to the clips array.
+*/
+void Emf::enlarge_clips(PEMF_CALLBACK_DATA d){
+ d->clips.size += 100;
+ d->clips.strings = (char **) realloc(d->clips.strings,d->clips.size * sizeof(char *));
+}
+
+/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1)
+*/
+int Emf::in_clips(PEMF_CALLBACK_DATA d, const char *test){
+ int i;
+ for(i=0; i<d->clips.count; i++){
+ if(strcmp(test,d->clips.strings[i])==0)return(i+1);
+ }
+ return(0);
+}
+
+/* (Conditionally) add a clip.
+ If a matching clip already exists nothing happens
+ If one does exist it is added to the clips list, entered into <defs>.
+*/
+void Emf::add_clips(PEMF_CALLBACK_DATA d, const char *clippath, unsigned int logic){
+ int op = combine_ops_to_livarot(logic);
+ Geom::PathVector combined_vect;
+ std::string combined;
+ if (op >= 0 && d->dc[d->level].clip_id) {
+ unsigned int real_idx = d->dc[d->level].clip_id - 1;
+ Geom::PathVector old_vect = sp_svg_read_pathv(d->clips.strings[real_idx]);
+ Geom::PathVector new_vect = sp_svg_read_pathv(clippath);
+ combined_vect = sp_pathvector_boolop(new_vect, old_vect, (bool_op) op , (FillRule) fill_oddEven, (FillRule) fill_oddEven);
+ combined = sp_svg_write_path(combined_vect);
+ }
+ else {
+ combined = clippath; // COPY operation, erases everything and starts a new one
+ }
+
+ uint32_t idx = in_clips(d, combined.c_str());
+ if(!idx){ // add clip if not already present
+ if(d->clips.count == d->clips.size){ enlarge_clips(d); }
+ d->clips.strings[d->clips.count++] = strdup(combined.c_str());
+ d->dc[d->level].clip_id = d->clips.count; // one more than the slot where it is actually stored
+ SVGOStringStream tmp_clippath;
+ tmp_clippath << "\n<clipPath";
+ tmp_clippath << "\n\tclipPathUnits=\"userSpaceOnUse\" ";
+ tmp_clippath << "\n\tid=\"clipEmfPath" << d->dc[d->level].clip_id << "\"";
+ tmp_clippath << " >";
+ tmp_clippath << "\n\t<path d=\"";
+ tmp_clippath << combined;
+ tmp_clippath << "\"";
+ tmp_clippath << "\n\t/>";
+ tmp_clippath << "\n</clipPath>";
+ d->outdef += tmp_clippath.str().c_str();
+ }
+ else {
+ d->dc[d->level].clip_id = idx;
+ }
+}
+
+
+
+void
+Emf::output_style(PEMF_CALLBACK_DATA d, int iType)
+{
+// SVGOStringStream tmp_id;
+ SVGOStringStream tmp_style;
+ char tmp[1024] = {0};
+
+ float fill_rgb[3];
+ d->dc[d->level].style.fill.value.color.get_rgb_floatv(fill_rgb);
+ float stroke_rgb[3];
+ d->dc[d->level].style.stroke.value.color.get_rgb_floatv(stroke_rgb);
+
+ // for U_EMR_BITBLT with no image, try to approximate some of these operations/
+ // Assume src color is "white"
+ if(d->dwRop3){
+ switch(d->dwRop3){
+ case U_PATINVERT: // invert pattern
+ fill_rgb[0] = 1.0 - fill_rgb[0];
+ fill_rgb[1] = 1.0 - fill_rgb[1];
+ fill_rgb[2] = 1.0 - fill_rgb[2];
+ break;
+ case U_SRCINVERT: // treat all of these as black
+ case U_DSTINVERT:
+ case U_BLACKNESS:
+ case U_SRCERASE:
+ case U_NOTSRCCOPY:
+ fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=0.0;
+ break;
+ case U_SRCCOPY: // treat all of these as white
+ case U_NOTSRCERASE:
+ case U_WHITENESS:
+ fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=1.0;
+ break;
+ case U_SRCPAINT: // use the existing color
+ case U_SRCAND:
+ case U_MERGECOPY:
+ case U_MERGEPAINT:
+ case U_PATPAINT:
+ case U_PATCOPY:
+ default:
+ break;
+ }
+ d->dwRop3 = 0; // might as well reset it here, it must be set for each BITBLT
+ }
+
+ // Implement some of these, the ones where the original screen color does not matter.
+ // The options that merge screen and pen colors cannot be done correctly because we
+ // have no way of knowing what color is already on the screen. For those just pass the
+ // pen color through.
+ switch(d->dwRop2){
+ case U_R2_BLACK:
+ fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 0.0;
+ stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 0.0;
+ break;
+ case U_R2_NOTMERGEPEN:
+ case U_R2_MASKNOTPEN:
+ break;
+ case U_R2_NOTCOPYPEN:
+ fill_rgb[0] = 1.0 - fill_rgb[0];
+ fill_rgb[1] = 1.0 - fill_rgb[1];
+ fill_rgb[2] = 1.0 - fill_rgb[2];
+ stroke_rgb[0] = 1.0 - stroke_rgb[0];
+ stroke_rgb[1] = 1.0 - stroke_rgb[1];
+ stroke_rgb[2] = 1.0 - stroke_rgb[2];
+ break;
+ case U_R2_MASKPENNOT:
+ case U_R2_NOT:
+ case U_R2_XORPEN:
+ case U_R2_NOTMASKPEN:
+ case U_R2_NOTXORPEN:
+ case U_R2_NOP:
+ case U_R2_MERGENOTPEN:
+ case U_R2_COPYPEN:
+ case U_R2_MASKPEN:
+ case U_R2_MERGEPENNOT:
+ case U_R2_MERGEPEN:
+ break;
+ case U_R2_WHITE:
+ fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 1.0;
+ stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 1.0;
+ break;
+ default:
+ break;
+ }
+
+
+// tmp_id << "\n\tid=\"" << (d->id++) << "\"";
+// d->outsvg += tmp_id.str().c_str();
+ d->outsvg += "\n\tstyle=\"";
+ if (iType == U_EMR_STROKEPATH || !d->dc[d->level].fill_set) {
+ tmp_style << "fill:none;";
+ } else {
+ switch(d->dc[d->level].fill_mode){
+ // both of these use the url(#) method
+ case DRAW_PATTERN:
+ snprintf(tmp, 1023, "fill:url(#%s); ",d->hatches.strings[d->dc[d->level].fill_idx]);
+ tmp_style << tmp;
+ break;
+ case DRAW_IMAGE:
+ snprintf(tmp, 1023, "fill:url(#EMFimage%d_ref); ",d->dc[d->level].fill_idx);
+ tmp_style << tmp;
+ break;
+ case DRAW_LINEAR_GRADIENT:
+ case DRAW_PAINT:
+ default: // <-- this should never happen, but just in case...
+ snprintf(
+ tmp, 1023,
+ "fill:#%02x%02x%02x;",
+ SP_COLOR_F_TO_U(fill_rgb[0]),
+ SP_COLOR_F_TO_U(fill_rgb[1]),
+ SP_COLOR_F_TO_U(fill_rgb[2])
+ );
+ tmp_style << tmp;
+ break;
+ }
+ snprintf(
+ tmp, 1023,
+ "fill-rule:%s;",
+ (d->dc[d->level].style.fill_rule.value == SP_WIND_RULE_NONZERO ? "evenodd" : "nonzero")
+ );
+ tmp_style << tmp;
+ tmp_style << "fill-opacity:1;";
+
+ // if the stroke is the same as the fill, and the right size not to change the end size of the object, do not do it separately
+ if(
+ (d->dc[d->level].fill_set ) &&
+ (d->dc[d->level].stroke_set ) &&
+ (d->dc[d->level].style.stroke_width.value == 1 ) &&
+ (d->dc[d->level].fill_mode == d->dc[d->level].stroke_mode) &&
+ (
+ (d->dc[d->level].fill_mode != DRAW_PAINT) ||
+ (
+ (fill_rgb[0]==stroke_rgb[0]) &&
+ (fill_rgb[1]==stroke_rgb[1]) &&
+ (fill_rgb[2]==stroke_rgb[2])
+ )
+ )
+ ){
+ d->dc[d->level].stroke_set = false;
+ }
+ }
+
+ if (iType == U_EMR_FILLPATH || !d->dc[d->level].stroke_set) {
+ tmp_style << "stroke:none;";
+ } else {
+ switch(d->dc[d->level].stroke_mode){
+ // both of these use the url(#) method
+ case DRAW_PATTERN:
+ snprintf(tmp, 1023, "stroke:url(#%s); ",d->hatches.strings[d->dc[d->level].stroke_idx]);
+ tmp_style << tmp;
+ break;
+ case DRAW_IMAGE:
+ snprintf(tmp, 1023, "stroke:url(#EMFimage%d_ref); ",d->dc[d->level].stroke_idx);
+ tmp_style << tmp;
+ break;
+ case DRAW_LINEAR_GRADIENT:
+ case DRAW_PAINT:
+ default: // <-- this should never happen, but just in case...
+ snprintf(
+ tmp, 1023,
+ "stroke:#%02x%02x%02x;",
+ SP_COLOR_F_TO_U(stroke_rgb[0]),
+ SP_COLOR_F_TO_U(stroke_rgb[1]),
+ SP_COLOR_F_TO_U(stroke_rgb[2])
+ );
+ tmp_style << tmp;
+ break;
+ }
+ tmp_style << "stroke-width:" <<
+ MAX( 0.001, d->dc[d->level].style.stroke_width.value ) << "px;";
+
+ tmp_style << "stroke-linecap:" <<
+ (
+ d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_BUTT ? "butt" :
+ d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_ROUND ? "round" :
+ d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_SQUARE ? "square" :
+ "unknown"
+ ) << ";";
+
+ tmp_style << "stroke-linejoin:" <<
+ (
+ d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_MITER ? "miter" :
+ d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_ROUND ? "round" :
+ d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_BEVEL ? "bevel" :
+ "unknown"
+ ) << ";";
+
+ // Set miter limit if known, even if it is not needed immediately (not miter)
+ tmp_style << "stroke-miterlimit:" <<
+ MAX( 2.0, d->dc[d->level].style.stroke_miterlimit.value ) << ";";
+
+ if (d->dc[d->level].style.stroke_dasharray.set &&
+ !d->dc[d->level].style.stroke_dasharray.values.empty() )
+ {
+ tmp_style << "stroke-dasharray:";
+ for (unsigned i=0; i<d->dc[d->level].style.stroke_dasharray.values.size(); i++) {
+ if (i)
+ tmp_style << ",";
+ tmp_style << d->dc[d->level].style.stroke_dasharray.values[i].value;
+ }
+ tmp_style << ";";
+ tmp_style << "stroke-dashoffset:0;";
+ } else {
+ tmp_style << "stroke-dasharray:none;";
+ }
+ tmp_style << "stroke-opacity:1;";
+ }
+ tmp_style << "\" ";
+ if (d->dc[d->level].clip_id)
+ tmp_style << "\n\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\" ";
+
+ d->outsvg += tmp_style.str().c_str();
+}
+
+
+double
+Emf::_pix_x_to_point(PEMF_CALLBACK_DATA d, double px)
+{
+ double scale = (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0);
+ double tmp;
+ tmp = ((((double) (px - d->dc[d->level].winorg.x))*scale) + d->dc[d->level].vieworg.x) * d->D2PscaleX;
+ tmp -= d->ulCornerOutX; //The EMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner
+ return(tmp);
+}
+
+double
+Emf::_pix_y_to_point(PEMF_CALLBACK_DATA d, double py)
+{
+ double scale = (d->dc[d->level].ScaleInY ? d->dc[d->level].ScaleInY : 1.0);
+ double tmp;
+ tmp = ((((double) (py - d->dc[d->level].winorg.y))*scale) * d->E2IdirY + d->dc[d->level].vieworg.y) * d->D2PscaleY;
+ tmp -= d->ulCornerOutY; //The EMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner
+ return(tmp);
+}
+
+
+double
+Emf::pix_to_x_point(PEMF_CALLBACK_DATA d, double px, double py)
+{
+ double wpx = px * d->dc[d->level].worldTransform.eM11 + py * d->dc[d->level].worldTransform.eM21 + d->dc[d->level].worldTransform.eDx;
+ double x = _pix_x_to_point(d, wpx);
+
+ return x;
+}
+
+double
+Emf::pix_to_y_point(PEMF_CALLBACK_DATA d, double px, double py)
+{
+
+ double wpy = px * d->dc[d->level].worldTransform.eM12 + py * d->dc[d->level].worldTransform.eM22 + d->dc[d->level].worldTransform.eDy;
+ double y = _pix_y_to_point(d, wpy);
+
+ return y;
+
+}
+
+double
+Emf::pix_to_abs_size(PEMF_CALLBACK_DATA d, double px)
+{
+ double ppx = fabs(px * (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0) * d->D2PscaleX * current_scale(d));
+ return ppx;
+}
+
+/* snaps coordinate pairs made up of values near +/-faraway, +/-faraway to exactly faraway.
+ This eliminates coordinate drift on repeated clipping cycles which use exclude.
+ It should not affect internals of normal drawings because the value of faraway is so large.
+*/
+void
+Emf::snap_to_faraway_pair(double *x, double *y)
+{
+ if((std::abs(std::abs(*x) - faraway)/faraway <= 1e-4) && (std::abs(std::abs(*y) - faraway)/faraway <= 1e-4)){
+ *x = (*x > 0 ? faraway : -faraway);
+ *y = (*y > 0 ? faraway : -faraway);
+ }
+}
+
+/* returns "x,y" (without the quotes) in inkscape coordinates for a pair of EMF x,y coordinates.
+ Since exclude clip can go through here, it calls snap_to_faraway_pair for numerical stability.
+*/
+std::string Emf::pix_to_xy(PEMF_CALLBACK_DATA d, double x, double y){
+ SVGOStringStream cxform;
+ double tx = pix_to_x_point(d,x,y);
+ double ty = pix_to_y_point(d,x,y);
+ snap_to_faraway_pair(&tx,&ty);
+ cxform << tx;
+ cxform << ",";
+ cxform << ty;
+ return(cxform.str());
+}
+
+
+void
+Emf::select_pen(PEMF_CALLBACK_DATA d, int index)
+{
+ PU_EMRCREATEPEN pEmr = nullptr;
+
+ if (index >= 0 && index < d->n_obj){
+ pEmr = (PU_EMRCREATEPEN) d->emf_obj[index].lpEMFR;
+ }
+
+ if (!pEmr){ return; }
+
+ switch (pEmr->lopn.lopnStyle & U_PS_STYLE_MASK) {
+ case U_PS_DASH:
+ case U_PS_DOT:
+ case U_PS_DASHDOT:
+ case U_PS_DASHDOTDOT:
+ {
+ SPILength spilength(1.f);
+ int penstyle = (pEmr->lopn.lopnStyle & U_PS_STYLE_MASK);
+ if (!d->dc[d->level].style.stroke_dasharray.values.empty() &&
+ (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray !=
+ d->dc[d->level - 1].style.stroke_dasharray)))
+ d->dc[d->level].style.stroke_dasharray.values.clear();
+ if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) {
+ spilength.setDouble(3);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ spilength.setDouble(1);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+ if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) {
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+ if (penstyle==U_PS_DASHDOTDOT) {
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+
+ d->dc[d->level].style.stroke_dasharray.set = true;
+ break;
+ }
+
+ case U_PS_SOLID:
+ default:
+ {
+ d->dc[d->level].style.stroke_dasharray.set = false;
+ break;
+ }
+ }
+
+ switch (pEmr->lopn.lopnStyle & U_PS_ENDCAP_MASK) {
+ case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND; break; }
+ case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; break; }
+ case U_PS_ENDCAP_FLAT:
+ default: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT; break; }
+ }
+
+ switch (pEmr->lopn.lopnStyle & U_PS_JOIN_MASK) {
+ case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL; break; }
+ case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; break; }
+ case U_PS_JOIN_ROUND:
+ default: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND; break; }
+ }
+
+ d->dc[d->level].stroke_set = true;
+
+ if (pEmr->lopn.lopnStyle == U_PS_NULL) {
+ d->dc[d->level].style.stroke_width.value = 0;
+ d->dc[d->level].stroke_set = false;
+ } else if (pEmr->lopn.lopnWidth.x) {
+ int cur_level = d->level;
+ d->level = d->emf_obj[index].level;
+ double pen_width = pix_to_abs_size( d, pEmr->lopn.lopnWidth.x );
+ d->level = cur_level;
+ d->dc[d->level].style.stroke_width.value = pen_width;
+ } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?)
+ //d->dc[d->level].style.stroke_width.value = 1.0;
+ int cur_level = d->level;
+ d->level = d->emf_obj[index].level;
+ double pen_width = pix_to_abs_size( d, 1 );
+ d->level = cur_level;
+ d->dc[d->level].style.stroke_width.value = pen_width;
+ }
+
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->lopn.lopnColor) );
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->lopn.lopnColor) );
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->lopn.lopnColor) );
+ d->dc[d->level].style.stroke.value.color.set( r, g, b );
+}
+
+
+void
+Emf::select_extpen(PEMF_CALLBACK_DATA d, int index)
+{
+ PU_EMREXTCREATEPEN pEmr = nullptr;
+
+ if (index >= 0 && index < d->n_obj)
+ pEmr = (PU_EMREXTCREATEPEN) d->emf_obj[index].lpEMFR;
+
+ if (!pEmr)
+ return;
+
+ switch (pEmr->elp.elpPenStyle & U_PS_STYLE_MASK) {
+ case U_PS_USERSTYLE:
+ {
+ if (pEmr->elp.elpNumEntries) {
+ if (!d->dc[d->level].style.stroke_dasharray.values.empty() &&
+ (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray !=
+ d->dc[d->level - 1].style.stroke_dasharray)))
+ d->dc[d->level].style.stroke_dasharray.values.clear();
+ for (unsigned int i=0; i<pEmr->elp.elpNumEntries; i++) {
+ double dash_length = pix_to_abs_size( d, pEmr->elp.elpStyleEntry[i] );
+ d->dc[d->level].style.stroke_dasharray.values.emplace_back(dash_length);
+ }
+ d->dc[d->level].style.stroke_dasharray.set = true;
+ } else {
+ d->dc[d->level].style.stroke_dasharray.set = false;
+ }
+ break;
+ }
+
+ case U_PS_DASH:
+ case U_PS_DOT:
+ case U_PS_DASHDOT:
+ case U_PS_DASHDOTDOT:
+ {
+ int penstyle = (pEmr->elp.elpPenStyle & U_PS_STYLE_MASK);
+ if (!d->dc[d->level].style.stroke_dasharray.values.empty() &&
+ (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray !=
+ d->dc[d->level - 1].style.stroke_dasharray)))
+ d->dc[d->level].style.stroke_dasharray.values.clear();
+ SPILength spilength;
+ if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) {
+ spilength.setDouble(3);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ spilength.setDouble(2);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+ if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) {
+ spilength.setDouble(1);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ spilength.setDouble(2);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+ if (penstyle==U_PS_DASHDOTDOT) {
+ spilength.setDouble(1);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ spilength.setDouble(2);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+
+ d->dc[d->level].style.stroke_dasharray.set = true;
+ break;
+ }
+ case U_PS_SOLID:
+/* includes these for now, some should maybe not be in here
+ case U_PS_NULL:
+ case U_PS_INSIDEFRAME:
+ case U_PS_ALTERNATE:
+ case U_PS_STYLE_MASK:
+*/
+ default:
+ {
+ d->dc[d->level].style.stroke_dasharray.set = false;
+ break;
+ }
+ }
+
+ switch (pEmr->elp.elpPenStyle & U_PS_ENDCAP_MASK) {
+ case U_PS_ENDCAP_ROUND:
+ {
+ d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND;
+ break;
+ }
+ case U_PS_ENDCAP_SQUARE:
+ {
+ d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE;
+ break;
+ }
+ case U_PS_ENDCAP_FLAT:
+ default:
+ {
+ d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT;
+ break;
+ }
+ }
+
+ switch (pEmr->elp.elpPenStyle & U_PS_JOIN_MASK) {
+ case U_PS_JOIN_BEVEL:
+ {
+ d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL;
+ break;
+ }
+ case U_PS_JOIN_MITER:
+ {
+ d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER;
+ break;
+ }
+ case U_PS_JOIN_ROUND:
+ default:
+ {
+ d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND;
+ break;
+ }
+ }
+
+ d->dc[d->level].stroke_set = true;
+
+ if (pEmr->elp.elpPenStyle == U_PS_NULL) { // draw nothing, but fill out all the values with something
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor));
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor));
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor));
+ d->dc[d->level].style.stroke.value.color.set( r, g, b );
+ d->dc[d->level].style.stroke_width.value = 0;
+ d->dc[d->level].stroke_set = false;
+ d->dc[d->level].stroke_mode = DRAW_PAINT;
+ }
+ else {
+ if (pEmr->elp.elpWidth) {
+ int cur_level = d->level;
+ d->level = d->emf_obj[index].level;
+ double pen_width = pix_to_abs_size( d, pEmr->elp.elpWidth );
+ d->level = cur_level;
+ d->dc[d->level].style.stroke_width.value = pen_width;
+ } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?)
+ //d->dc[d->level].style.stroke_width.value = 1.0;
+ int cur_level = d->level;
+ d->level = d->emf_obj[index].level;
+ double pen_width = pix_to_abs_size( d, 1 );
+ d->level = cur_level;
+ d->dc[d->level].style.stroke_width.value = pen_width;
+ }
+
+ if( pEmr->elp.elpBrushStyle == U_BS_SOLID){
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->elp.elpColor) );
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->elp.elpColor) );
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->elp.elpColor) );
+ d->dc[d->level].style.stroke.value.color.set( r, g, b );
+ d->dc[d->level].stroke_mode = DRAW_PAINT;
+ d->dc[d->level].stroke_set = true;
+ }
+ else if(pEmr->elp.elpBrushStyle == U_BS_HATCHED){
+ d->dc[d->level].stroke_idx = add_hatch(d, pEmr->elp.elpHatch, pEmr->elp.elpColor);
+ d->dc[d->level].stroke_recidx = index; // used if the hatch needs to be redone due to bkMode, textmode, etc. changes
+ d->dc[d->level].stroke_mode = DRAW_PATTERN;
+ d->dc[d->level].stroke_set = true;
+ }
+ else if(pEmr->elp.elpBrushStyle == U_BS_DIBPATTERN || pEmr->elp.elpBrushStyle == U_BS_DIBPATTERNPT){
+ d->dc[d->level].stroke_idx = add_image(d, (void *)pEmr, pEmr->cbBits, pEmr->cbBmi, *(uint32_t *) &(pEmr->elp.elpColor), pEmr->offBits, pEmr->offBmi);
+ d->dc[d->level].stroke_mode = DRAW_IMAGE;
+ d->dc[d->level].stroke_set = true;
+ }
+ else { // U_BS_PATTERN and anything strange that falls in, stroke is solid textColor
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor));
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor));
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor));
+ d->dc[d->level].style.stroke.value.color.set( r, g, b );
+ d->dc[d->level].stroke_mode = DRAW_PAINT;
+ d->dc[d->level].stroke_set = true;
+ }
+ }
+}
+
+
+void
+Emf::select_brush(PEMF_CALLBACK_DATA d, int index)
+{
+ uint32_t tidx;
+ uint32_t iType;
+
+ if (index >= 0 && index < d->n_obj){
+ iType = ((PU_EMR) (d->emf_obj[index].lpEMFR))->iType;
+ if(iType == U_EMR_CREATEBRUSHINDIRECT){
+ PU_EMRCREATEBRUSHINDIRECT pEmr = (PU_EMRCREATEBRUSHINDIRECT) d->emf_obj[index].lpEMFR;
+ if( pEmr->lb.lbStyle == U_BS_SOLID){
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(pEmr->lb.lbColor) );
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(pEmr->lb.lbColor) );
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(pEmr->lb.lbColor) );
+ d->dc[d->level].style.fill.value.color.set( r, g, b );
+ d->dc[d->level].fill_mode = DRAW_PAINT;
+ d->dc[d->level].fill_set = true;
+ }
+ else if(pEmr->lb.lbStyle == U_BS_HATCHED){
+ d->dc[d->level].fill_idx = add_hatch(d, pEmr->lb.lbHatch, pEmr->lb.lbColor);
+ d->dc[d->level].fill_recidx = index; // used if the hatch needs to be redone due to bkMode, textmode, etc. changes
+ d->dc[d->level].fill_mode = DRAW_PATTERN;
+ d->dc[d->level].fill_set = true;
+ }
+ }
+ else if(iType == U_EMR_CREATEDIBPATTERNBRUSHPT || iType == U_EMR_CREATEMONOBRUSH){
+ PU_EMRCREATEDIBPATTERNBRUSHPT pEmr = (PU_EMRCREATEDIBPATTERNBRUSHPT) d->emf_obj[index].lpEMFR;
+ tidx = add_image(d, (void *) pEmr, pEmr->cbBits, pEmr->cbBmi, pEmr->iUsage, pEmr->offBits, pEmr->offBmi);
+ if(tidx == U_EMR_INVALID){ // This happens if createmonobrush has a DIB that isn't monochrome
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor));
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor));
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor));
+ d->dc[d->level].style.fill.value.color.set( r, g, b );
+ d->dc[d->level].fill_mode = DRAW_PAINT;
+ }
+ else {
+ d->dc[d->level].fill_idx = tidx;
+ d->dc[d->level].fill_mode = DRAW_IMAGE;
+ }
+ d->dc[d->level].fill_set = true;
+ }
+ }
+}
+
+
+void
+Emf::select_font(PEMF_CALLBACK_DATA d, int index)
+{
+ PU_EMREXTCREATEFONTINDIRECTW pEmr = nullptr;
+
+ if (index >= 0 && index < d->n_obj)
+ pEmr = (PU_EMREXTCREATEFONTINDIRECTW) d->emf_obj[index].lpEMFR;
+
+ if (!pEmr)return;
+
+
+ /* The logfont information always starts with a U_LOGFONT structure but the U_EMREXTCREATEFONTINDIRECTW
+ is defined as U_LOGFONT_PANOSE so it can handle one of those if that is actually present. Currently only logfont
+ is supported, and the remainder, it it really is a U_LOGFONT_PANOSE record, is ignored
+ */
+ int cur_level = d->level;
+ d->level = d->emf_obj[index].level;
+ double font_size = pix_to_abs_size( d, pEmr->elfw.elfLogFont.lfHeight );
+ /* snap the font_size to the nearest 1/32nd of a point.
+ (The size is converted from Pixels to points, snapped, and converted back.)
+ See the notes where d->D2Pscale[XY] are set for the reason why.
+ Typically this will set the font to the desired exact size. If some peculiar size
+ was intended this will, at worst, make it .03125 off, which is unlikely to be a problem. */
+ font_size = round(20.0 * 0.8 * font_size)/(20.0 * 0.8);
+ d->level = cur_level;
+ d->dc[d->level].style.font_size.computed = font_size;
+ d->dc[d->level].style.font_weight.value =
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_THIN ? SP_CSS_FONT_WEIGHT_100 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_200 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_LIGHT ? SP_CSS_FONT_WEIGHT_300 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_400 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_MEDIUM ? SP_CSS_FONT_WEIGHT_500 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_SEMIBOLD ? SP_CSS_FONT_WEIGHT_600 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_700 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_800 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_HEAVY ? SP_CSS_FONT_WEIGHT_900 :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_NORMAL :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_BOLD :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_LIGHTER :
+ pEmr->elfw.elfLogFont.lfWeight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_BOLDER :
+ SP_CSS_FONT_WEIGHT_NORMAL;
+ d->dc[d->level].style.font_style.value = (pEmr->elfw.elfLogFont.lfItalic ? SP_CSS_FONT_STYLE_ITALIC : SP_CSS_FONT_STYLE_NORMAL);
+ d->dc[d->level].style.text_decoration_line.underline = pEmr->elfw.elfLogFont.lfUnderline;
+ d->dc[d->level].style.text_decoration_line.line_through = pEmr->elfw.elfLogFont.lfStrikeOut;
+ d->dc[d->level].style.text_decoration_line.set = true;
+ d->dc[d->level].style.text_decoration_line.inherit = false;
+ // malformed EMF with empty filename may exist, ignore font change if encountered
+ char *ctmp = U_Utf16leToUtf8((uint16_t *) (pEmr->elfw.elfLogFont.lfFaceName), U_LF_FACESIZE, nullptr);
+ if(ctmp){
+ if (d->dc[d->level].font_name){ free(d->dc[d->level].font_name); }
+ if(*ctmp){
+ d->dc[d->level].font_name = ctmp;
+ }
+ else { // Malformed EMF might specify an empty font name
+ free(ctmp);
+ d->dc[d->level].font_name = strdup("Arial"); // Default font, EMF spec says device can pick whatever it wants
+ }
+ }
+ d->dc[d->level].style.baseline_shift.value = round((double)((pEmr->elfw.elfLogFont.lfEscapement + 3600) % 3600)) / 10.0; // use baseline_shift instead of text_transform to avoid overflow
+}
+
+void
+Emf::delete_object(PEMF_CALLBACK_DATA d, int index)
+{
+ if (index >= 0 && index < d->n_obj) {
+ d->emf_obj[index].type = 0;
+// We are keeping a copy of the EMR rather than just a structure. Currently that is not necessary as the entire
+// EMF is read in at once and is stored in a big malloc. However, in past versions it was handled
+// record by record, and we might need to do that again at some point in the future if we start running into EMF
+// files too big to fit into memory.
+ if (d->emf_obj[index].lpEMFR)
+ free(d->emf_obj[index].lpEMFR);
+ d->emf_obj[index].lpEMFR = nullptr;
+ }
+}
+
+
+void
+Emf::insert_object(PEMF_CALLBACK_DATA d, int index, int type, PU_ENHMETARECORD pObj)
+{
+ if (index >= 0 && index < d->n_obj) {
+ delete_object(d, index);
+ d->emf_obj[index].type = type;
+ d->emf_obj[index].level = d->level;
+ d->emf_obj[index].lpEMFR = emr_dup((char *) pObj);
+ }
+}
+
+/* Identify probable Adobe Illustrator produced EMF files, which do strange things with the scaling.
+ The few so far observed all had this format.
+*/
+int Emf::AI_hack(PU_EMRHEADER pEmr){
+ int ret=0;
+ char *ptr;
+ ptr = (char *)pEmr;
+ PU_EMRSETMAPMODE nEmr = (PU_EMRSETMAPMODE) (ptr + pEmr->emr.nSize);
+ char *string = nullptr;
+ if(pEmr->nDescription)string = U_Utf16leToUtf8((uint16_t *)((char *) pEmr + pEmr->offDescription), pEmr->nDescription, nullptr);
+ if(string){
+ if((pEmr->nDescription >= 13) &&
+ (0==strcmp("Adobe Systems",string)) &&
+ (nEmr->emr.iType == U_EMR_SETMAPMODE) &&
+ (nEmr->iMode == U_MM_ANISOTROPIC)){ ret=1; }
+ free(string);
+ }
+ return(ret);
+}
+
+/**
+ \fn create a UTF-32LE buffer and fill it with UNICODE unknown character
+ \param count number of copies of the Unicode unknown character to fill with
+*/
+uint32_t *Emf::unknown_chars(size_t count){
+ uint32_t *res = (uint32_t *) malloc(sizeof(uint32_t) * (count + 1));
+ if(!res)throw "Inkscape fatal memory allocation error - cannot continue";
+ for(uint32_t i=0; i<count; i++){ res[i] = 0xFFFD; }
+ res[count]=0;
+ return res;
+}
+
+/**
+ \fn store SVG for an image given the pixmap and various coordinate information
+ \param d
+ \param pEmr
+ \param dx (double) destination x in inkscape pixels
+ \param dy (double) destination y in inkscape pixels
+ \param dw (double) destination width in inkscape pixels
+ \param dh (double) destination height in inkscape pixels
+ \param sx (int) source x in src image pixels
+ \param sy (int) source y in src image pixels
+ \param iUsage
+ \param offBits
+ \param cbBits
+ \param offBmi
+ \param cbBmi
+*/
+void Emf::common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr,
+ double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh,
+ uint32_t iUsage, uint32_t offBits, uint32_t cbBits, uint32_t offBmi, uint32_t cbBmi){
+
+ SVGOStringStream tmp_image;
+ int dibparams = U_BI_UNKNOWN; // type of image not yet determined
+
+ tmp_image << "\n\t <image\n";
+ if (d->dc[d->level].clip_id){
+ tmp_image << "\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n";
+ }
+ tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n ";
+
+ MEMPNG mempng; // PNG in memory comes back in this
+ mempng.buffer = nullptr;
+
+ char *rgba_px = nullptr; // RGBA pixels
+ char *sub_px = nullptr; // RGBA pixels, subarray
+ const char *px = nullptr; // DIB pixels
+ const U_RGBQUAD *ct = nullptr; // DIB color table
+ uint32_t width, height, colortype, numCt, invert; // if needed these values will be set in get_DIB_params
+ if(cbBits && cbBmi && (iUsage == U_DIB_RGB_COLORS)){
+ // next call returns pointers and values, but allocates no memory
+ dibparams = get_DIB_params((const char *)pEmr, offBits, offBmi, &px, (const U_RGBQUAD **) &ct,
+ &numCt, &width, &height, &colortype, &invert);
+ if(dibparams ==U_BI_RGB){
+ if(sw == 0 || sh == 0){
+ sw = width;
+ sh = height;
+ }
+
+ if(!DIB_to_RGBA(
+ px, // DIB pixel array
+ ct, // DIB color table
+ numCt, // DIB color table number of entries
+ &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free.
+ width, // Width of pixel array
+ height, // Height of pixel array
+ colortype, // DIB BitCount Enumeration
+ numCt, // Color table used if not 0
+ invert // If DIB rows are in opposite order from RGBA rows
+ )){
+ sub_px = RGBA_to_RGBA( // returns either a subset (side effect: frees rgba_px) or NULL (for subset == entire image)
+ rgba_px, // full pixel array from DIB
+ width, // Width of pixel array
+ height, // Height of pixel array
+ sx,sy, // starting point in pixel array
+ &sw,&sh // columns/rows to extract from the pixel array (output array size)
+ );
+
+ if(!sub_px)sub_px=rgba_px;
+ toPNG( // Get the image from the RGBA px into mempng
+ &mempng,
+ sw, sh, // size of the extracted pixel array
+ sub_px
+ );
+ free(sub_px);
+ }
+ }
+ }
+
+ gchar *base64String=nullptr;
+ if(dibparams == U_BI_JPEG){ // image was binary jpg in source file
+ tmp_image << " xlink:href=\"data:image/jpeg;base64,";
+ base64String = g_base64_encode((guchar*) px, numCt );
+ }
+ else if(dibparams==U_BI_PNG){ // image was binary png in source file
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = g_base64_encode((guchar*) px, numCt );
+ }
+ else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size );
+ free(mempng.buffer);
+ }
+ else { // unknown or unsupported image type or failed conversion, insert the common bad image picture
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = bad_image_png();
+ }
+ tmp_image << base64String;
+ g_free(base64String);
+
+ tmp_image << "\"\n height=\"" << dh << "\"\n width=\"" << dw << "\"\n";
+
+ tmp_image << " transform=" << current_matrix(d, dx, dy, 1); // calculate appropriate offset
+ tmp_image << " preserveAspectRatio=\"none\"\n";
+ tmp_image << "/> \n";
+
+ d->outsvg += tmp_image.str().c_str();
+ d->path = "";
+}
+
+/**
+ \fn myEnhMetaFileProc(char *contents, unsigned int length, PEMF_CALLBACK_DATA lpData)
+ \param contents binary contents of an EMF file
+ \param length length in bytes of contents
+ \param d Inkscape data structures returned by this call
+*/
+//THis was a callback, just build it into a normal function
+int Emf::myEnhMetaFileProc(char *contents, unsigned int length, PEMF_CALLBACK_DATA d)
+{
+ uint32_t off=0;
+ uint32_t emr_mask;
+ int OK =1;
+ int file_status=1;
+ uint32_t nSize;
+ uint32_t iType;
+ const char *blimit = contents + length;
+ PU_ENHMETARECORD lpEMFR;
+ TCHUNK_SPECS tsp;
+ uint32_t tbkMode = U_TRANSPARENT; // holds proposed change to bkMode, if text is involved saving these to the DC must wait until the text is written
+ U_COLORREF tbkColor = U_RGB(255, 255, 255); // holds proposed change to bkColor
+
+ // code for end user debugging
+ int eDbgRecord=0;
+ int eDbgComment=0;
+ int eDbgFinal=0;
+ char const* eDbgString = getenv( "INKSCAPE_DBG_EMF" );
+ if ( eDbgString != nullptr ) {
+ if(strstr(eDbgString,"RECORD")){ eDbgRecord = 1; }
+ if(strstr(eDbgString,"COMMENT")){ eDbgComment = 1; }
+ if(strstr(eDbgString,"FINAL")){ eDbgFinal = 1; }
+ }
+
+ /* initialize the tsp for text reassembly */
+ tsp.string = nullptr;
+ tsp.ori = 0.0; /* degrees */
+ tsp.fs = 12.0; /* font size */
+ tsp.x = 0.0;
+ tsp.y = 0.0;
+ tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/
+ tsp.vadvance = 0.0; /* meaningful only when a complex contains two or more lines */
+ tsp.taln = ALILEFT + ALIBASE;
+ tsp.ldir = LDIR_LR;
+ tsp.spaces = 0; // this field is only used for debugging
+ tsp.color.Red = 0; /* RGB Black */
+ tsp.color.Green = 0; /* RGB Black */
+ tsp.color.Blue = 0; /* RGB Black */
+ tsp.color.Reserved = 0; /* not used */
+ tsp.italics = 0;
+ tsp.weight = 80;
+ tsp.decoration = TXTDECOR_NONE;
+ tsp.condensed = 100;
+ tsp.co = 0;
+ tsp.fi_idx = -1; /* set to an invalid */
+
+ while(OK){
+ if(off>=length)return(0); //normally should exit from while after EMREOF sets OK to false.
+
+ // check record sizes and types thoroughly
+ int badrec = 0;
+ if (!U_emf_record_sizeok(contents + off, blimit, &nSize, &iType, 1) ||
+ !U_emf_record_safe(contents + off)){
+ badrec = 1;
+ }
+ else {
+ emr_mask = emr_properties(iType);
+ if (emr_mask == U_EMR_INVALID) { badrec = 1; }
+ }
+ if (badrec) {
+ file_status = 0;
+ break;
+ }
+
+ lpEMFR = (PU_ENHMETARECORD)(contents + off);
+
+// At run time define environment variable INKSCAPE_DBG_EMF to include string RECORD.
+// Users may employ this to track down toxic records
+ if(eDbgRecord){
+ std::cout << "record type: " << iType << " name " << U_emr_names(iType) << " length: " << nSize << " offset: " << off <<std::endl;
+ }
+ off += nSize;
+
+ SVGOStringStream tmp_outsvg;
+ SVGOStringStream tmp_path;
+ SVGOStringStream tmp_str;
+ SVGOStringStream dbg_str;
+
+/* Uncomment the following to track down text problems */
+//std::cout << "tri->dirty:"<< d->tri->dirty << " emr_mask: " << std::hex << emr_mask << std::dec << std::endl;
+
+ // incompatible change to text drawing detected (color or background change) forces out existing text
+ // OR
+ // next record is valid type and forces pending text to be drawn immediately
+ if ((d->dc[d->level].dirty & DIRTY_TEXT) || ((emr_mask != U_EMR_INVALID) && (emr_mask & U_DRAW_TEXT) && d->tri->dirty)){
+ TR_layout_analyze(d->tri);
+ if (d->dc[d->level].clip_id){
+ SVGOStringStream tmp_clip;
+ tmp_clip << "\n<g\n\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n>";
+ d->outsvg += tmp_clip.str().c_str();
+ }
+ TR_layout_2_svg(d->tri);
+ SVGOStringStream ts;
+ ts << d->tri->out;
+ d->outsvg += ts.str().c_str();
+ d->tri = trinfo_clear(d->tri);
+ if (d->dc[d->level].clip_id){
+ d->outsvg += "\n</g>\n";
+ }
+ }
+ if(d->dc[d->level].dirty){ //Apply the delayed background changes, clear the flag
+ d->dc[d->level].bkMode = tbkMode;
+ memcpy(&(d->dc[d->level].bkColor),&tbkColor, sizeof(U_COLORREF));
+
+ if(d->dc[d->level].dirty & DIRTY_TEXT){
+ // U_COLORREF and TRCOLORREF are exactly the same in memory, but the compiler needs some convincing...
+ if(tbkMode == U_TRANSPARENT){ (void) trinfo_load_bk(d->tri, BKCLR_NONE, *(TRCOLORREF *) &tbkColor); }
+ else { (void) trinfo_load_bk(d->tri, BKCLR_LINE, *(TRCOLORREF *) &tbkColor); } // Opaque
+ }
+
+ /* It is possible to have a series of EMF records that would result in
+ the following creating hash patterns which are never used. For instance, if
+ there were a series of records that changed the background color but did nothing
+ else.
+ */
+ if((d->dc[d->level].stroke_mode == DRAW_PATTERN) && (d->dc[d->level].dirty & DIRTY_STROKE)){
+ select_extpen(d, d->dc[d->level].stroke_recidx);
+ }
+
+ if((d->dc[d->level].fill_mode == DRAW_PATTERN) && (d->dc[d->level].dirty & DIRTY_FILL)){
+ select_brush(d, d->dc[d->level].fill_recidx);
+ }
+
+ d->dc[d->level].dirty = 0;
+ }
+
+// std::cout << "BEFORE DRAW logic d->mask: " << std::hex << d->mask << " emr_mask: " << emr_mask << std::dec << std::endl;
+/*
+std::cout << "BEFORE DRAW"
+ << " test0 " << ( d->mask & U_DRAW_VISIBLE)
+ << " test1 " << ( d->mask & U_DRAW_FORCE)
+ << " test2 " << (emr_mask & U_DRAW_ALTERS)
+ << " test3 " << (emr_mask & U_DRAW_VISIBLE)
+ << " test4 " << !(d->mask & U_DRAW_ONLYTO)
+ << " test5 " << ((d->mask & U_DRAW_ONLYTO) && !(emr_mask & U_DRAW_ONLYTO) )
+ << std::endl;
+*/
+
+ if(
+ (emr_mask != U_EMR_INVALID) && // next record is valid type
+ (d->mask & U_DRAW_VISIBLE) && // Current set of objects are drawable
+ (
+ (d->mask & U_DRAW_FORCE) || // This draw is forced by STROKE/FILL/STROKEANDFILL PATH
+ (emr_mask & U_DRAW_ALTERS) || // Next record would alter the drawing environment in some way
+ (
+ (emr_mask & U_DRAW_VISIBLE) && // Next record is visible...
+ (
+ ( !(d->mask & U_DRAW_ONLYTO) ) || // Non *TO records cannot be followed by any Visible
+ ((d->mask & U_DRAW_ONLYTO) && !(emr_mask & U_DRAW_ONLYTO) )// *TO records can only be followed by other *TO records
+ )
+ )
+ )
+ ){
+// std::cout << "PATH DRAW at TOP path" << *(d->path) << std::endl;
+ if(!(d->path.empty())){
+ d->outsvg += " <path "; // this is the ONLY place <path should be used!!! One exception, gradientfill.
+ if(d->drawtype){ // explicit draw type EMR record
+ output_style(d, d->drawtype);
+ }
+ else if(d->mask & U_DRAW_CLOSED){ // implicit draw type
+ output_style(d, U_EMR_STROKEANDFILLPATH);
+ }
+ else {
+ output_style(d, U_EMR_STROKEPATH);
+ }
+ d->outsvg += "\n\t";
+ d->outsvg += "\n\td=\""; // this is the ONLY place d=" should be used!!!! One exception, gradientfill.
+ d->outsvg += d->path;
+ d->outsvg += " \" /> \n";
+ d->path = "";
+ }
+ // reset the flags
+ d->mask = 0;
+ d->drawtype = 0;
+ }
+// std::cout << "AFTER DRAW logic d->mask: " << std::hex << d->mask << " emr_mask: " << emr_mask << std::dec << std::endl;
+
+ switch (iType)
+ {
+ case U_EMR_HEADER:
+ {
+ dbg_str << "<!-- U_EMR_HEADER -->\n";
+
+ d->outdef += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
+
+ if (d->pDesc) {
+ d->outdef += "<!-- ";
+ d->outdef += d->pDesc;
+ d->outdef += " -->\n";
+ }
+
+ PU_EMRHEADER pEmr = (PU_EMRHEADER) lpEMFR;
+ SVGOStringStream tmp_outdef;
+ tmp_outdef << "<svg\n";
+ tmp_outdef << " xmlns:svg=\"http://www.w3.org/2000/svg\"\n";
+ tmp_outdef << " xmlns=\"http://www.w3.org/2000/svg\"\n";
+ tmp_outdef << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n";
+ tmp_outdef << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"; // needed for sodipodi:role
+ tmp_outdef << " version=\"1.0\"\n";
+
+
+ /* inclusive-inclusive, so the size is 1 more than the difference */
+ d->MM100InX = pEmr->rclFrame.right - pEmr->rclFrame.left + 1;
+ d->MM100InY = pEmr->rclFrame.bottom - pEmr->rclFrame.top + 1;
+ d->PixelsInX = pEmr->rclBounds.right - pEmr->rclBounds.left + 1;
+ d->PixelsInY = pEmr->rclBounds.bottom - pEmr->rclBounds.top + 1;
+
+ /*
+ calculate ratio of Inkscape dpi/EMF device dpi
+ This can cause problems later due to accuracy limits in the EMF. A high resolution
+ EMF might have a final D2Pscale[XY] of 0.074998, and adjusting the (integer) device size
+ by 1 will still not get it exactly to 0.075. Later when the font size is calculated it
+ can end up as 29.9992 or 22.4994 instead of the intended 30 or 22.5. This is handled by
+ snapping font sizes to the nearest .01. The best estimate is made by using both values.
+ */
+ if ((pEmr->szlMillimeters.cx + pEmr->szlMillimeters.cy) && ( pEmr->szlDevice.cx + pEmr->szlDevice.cy)){
+ d->E2IdirY = 1.0; // assume MM_TEXT, if not, this will be changed later
+ d->D2PscaleX = d->D2PscaleY = Inkscape::Util::Quantity::convert(1, "mm", "px") *
+ (double)(pEmr->szlMillimeters.cx + pEmr->szlMillimeters.cy)/
+ (double)( pEmr->szlDevice.cx + pEmr->szlDevice.cy);
+ }
+ trinfo_load_qe(d->tri, d->D2PscaleX); /* quantization error that will affect text positions */
+
+ /* Adobe Illustrator files set mapmode to MM_ANISOTROPIC and somehow or other this
+ converts the rclFrame values from MM_HIMETRIC to MM_HIENGLISH, with another factor of 3 thrown
+ in for good measure. Ours not to question why...
+ */
+ if(AI_hack(pEmr)){
+ d->MM100InX *= 25.4/(10.0*3.0);
+ d->MM100InY *= 25.4/(10.0*3.0);
+ d->D2PscaleX *= 25.4/(10.0*3.0);
+ d->D2PscaleY *= 25.4/(10.0*3.0);
+ }
+
+ d->MMX = d->MM100InX / 100.0;
+ d->MMY = d->MM100InY / 100.0;
+
+ d->PixelsOutX = Inkscape::Util::Quantity::convert(d->MMX, "mm", "px");
+ d->PixelsOutY = Inkscape::Util::Quantity::convert(d->MMY, "mm", "px");
+
+ // Upper left corner, from header rclBounds, in device units, usually both 0, but not always
+ d->ulCornerInX = pEmr->rclBounds.left;
+ d->ulCornerInY = pEmr->rclBounds.top;
+ d->ulCornerOutX = d->ulCornerInX * d->D2PscaleX;
+ d->ulCornerOutY = d->ulCornerInY * d->E2IdirY * d->D2PscaleY;
+
+ tmp_outdef <<
+ " width=\"" << d->MMX << "mm\"\n" <<
+ " height=\"" << d->MMY << "mm\">\n";
+ d->outdef += tmp_outdef.str().c_str();
+ d->outdef += "<defs>"; // temporary end of header
+
+ // d->defs holds any defines which are read in.
+
+ tmp_outsvg << "\n</defs>\n\n"; // start of main body
+
+ if (pEmr->nHandles) {
+ d->n_obj = pEmr->nHandles;
+ d->emf_obj = new EMF_OBJECT[d->n_obj];
+
+ // Init the new emf_obj list elements to null, provided the
+ // dynamic allocation succeeded.
+ if ( d->emf_obj != nullptr )
+ {
+ for( int i=0; i < d->n_obj; ++i )
+ d->emf_obj[i].lpEMFR = nullptr;
+ } //if
+
+ } else {
+ d->emf_obj = nullptr;
+ }
+
+ break;
+ }
+ case U_EMR_POLYBEZIER:
+ {
+ dbg_str << "<!-- U_EMR_POLYBEZIER -->\n";
+
+ PU_EMRPOLYBEZIER pEmr = (PU_EMRPOLYBEZIER) lpEMFR;
+ uint32_t i,j;
+
+ if (pEmr->cptl<4)
+ break;
+
+ d->mask |= emr_mask;
+
+ tmp_str <<
+ "\n\tM " <<
+ pix_to_xy( d, pEmr->aptl[0].x, pEmr->aptl[0].y) << " ";
+
+ for (i=1; i<pEmr->cptl; ) {
+ tmp_str << "\n\tC ";
+ for (j=0; j<3 && i<pEmr->cptl; j++,i++) {
+ tmp_str << pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y) << " ";
+ }
+ }
+
+ tmp_path << tmp_str.str().c_str();
+
+ break;
+ }
+ case U_EMR_POLYGON:
+ {
+ dbg_str << "<!-- U_EMR_POLYGON -->\n";
+
+ PU_EMRPOLYGON pEmr = (PU_EMRPOLYGON) lpEMFR;
+ uint32_t i;
+
+ if (pEmr->cptl < 2)
+ break;
+
+ d->mask |= emr_mask;
+
+ tmp_str <<
+ "\n\tM " <<
+ pix_to_xy( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " ";
+
+ for (i=1; i<pEmr->cptl; i++) {
+ tmp_str <<
+ "\n\tL " <<
+ pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " ";
+ }
+
+ tmp_path << tmp_str.str().c_str();
+ tmp_path << " z";
+
+ break;
+ }
+ case U_EMR_POLYLINE:
+ {
+ dbg_str << "<!-- U_EMR_POLYLINE -->\n";
+
+ PU_EMRPOLYLINE pEmr = (PU_EMRPOLYLINE) lpEMFR;
+ uint32_t i;
+
+ if (pEmr->cptl<2)
+ break;
+
+ d->mask |= emr_mask;
+
+ tmp_str <<
+ "\n\tM " <<
+ pix_to_xy( d, pEmr->aptl[0].x, pEmr->aptl[0].y ) << " ";
+
+ for (i=1; i<pEmr->cptl; i++) {
+ tmp_str <<
+ "\n\tL " <<
+ pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " ";
+ }
+
+ tmp_path << tmp_str.str().c_str();
+
+ break;
+ }
+ case U_EMR_POLYBEZIERTO:
+ {
+ dbg_str << "<!-- U_EMR_POLYBEZIERTO -->\n";
+
+ PU_EMRPOLYBEZIERTO pEmr = (PU_EMRPOLYBEZIERTO) lpEMFR;
+ uint32_t i,j;
+
+ d->mask |= emr_mask;
+
+ for (i=0; i<pEmr->cptl;) {
+ tmp_path << "\n\tC ";
+ for (j=0; j<3 && i<pEmr->cptl; j++,i++) {
+ tmp_path <<
+ pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " ";
+ }
+ }
+
+ break;
+ }
+ case U_EMR_POLYLINETO:
+ {
+ dbg_str << "<!-- U_EMR_POLYLINETO -->\n";
+
+ PU_EMRPOLYLINETO pEmr = (PU_EMRPOLYLINETO) lpEMFR;
+ uint32_t i;
+
+ d->mask |= emr_mask;
+
+ for (i=0; i<pEmr->cptl;i++) {
+ tmp_path <<
+ "\n\tL " <<
+ pix_to_xy( d, pEmr->aptl[i].x, pEmr->aptl[i].y ) << " ";
+ }
+
+ break;
+ }
+ case U_EMR_POLYPOLYLINE:
+ case U_EMR_POLYPOLYGON:
+ {
+ if (lpEMFR->iType == U_EMR_POLYPOLYLINE)
+ dbg_str << "<!-- U_EMR_POLYPOLYLINE -->\n";
+ if (lpEMFR->iType == U_EMR_POLYPOLYGON)
+ dbg_str << "<!-- U_EMR_POLYPOLYGON -->\n";
+
+ PU_EMRPOLYPOLYGON pEmr = (PU_EMRPOLYPOLYGON) lpEMFR;
+ unsigned int n, i, j;
+
+ d->mask |= emr_mask;
+
+ U_POINTL *aptl = (PU_POINTL) &pEmr->aPolyCounts[pEmr->nPolys];
+
+ i = 0;
+ for (n=0; n<pEmr->nPolys && i<pEmr->cptl; n++) {
+ SVGOStringStream poly_path;
+
+ poly_path << "\n\tM " << pix_to_xy( d, aptl[i].x, aptl[i].y) << " ";
+ i++;
+
+ for (j=1; j<pEmr->aPolyCounts[n] && i<pEmr->cptl; j++) {
+ poly_path << "\n\tL " << pix_to_xy( d, aptl[i].x, aptl[i].y) << " ";
+ i++;
+ }
+
+ tmp_str << poly_path.str().c_str();
+ if (lpEMFR->iType == U_EMR_POLYPOLYGON)
+ tmp_str << " z";
+ tmp_str << " \n";
+ }
+
+ tmp_path << tmp_str.str().c_str();
+
+ break;
+ }
+ case U_EMR_SETWINDOWEXTEX:
+ {
+ dbg_str << "<!-- U_EMR_SETWINDOWEXTEX -->\n";
+
+ PU_EMRSETWINDOWEXTEX pEmr = (PU_EMRSETWINDOWEXTEX) lpEMFR;
+
+ d->dc[d->level].sizeWnd = pEmr->szlExtent;
+
+ if (!d->dc[d->level].sizeWnd.cx || !d->dc[d->level].sizeWnd.cy) {
+ d->dc[d->level].sizeWnd = d->dc[d->level].sizeView;
+ if (!d->dc[d->level].sizeWnd.cx || !d->dc[d->level].sizeWnd.cy) {
+ d->dc[d->level].sizeWnd.cx = d->PixelsOutX;
+ d->dc[d->level].sizeWnd.cy = d->PixelsOutY;
+ }
+ }
+
+ if (!d->dc[d->level].sizeView.cx || !d->dc[d->level].sizeView.cy) {
+ d->dc[d->level].sizeView = d->dc[d->level].sizeWnd;
+ }
+
+ /* scales logical to EMF pixels, transfer a negative sign on Y, if any */
+ if (d->dc[d->level].sizeWnd.cx && d->dc[d->level].sizeWnd.cy) {
+ d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].sizeWnd.cx;
+ d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].sizeWnd.cy;
+ if(d->dc[d->level].ScaleInY < 0){
+ d->dc[d->level].ScaleInY *= -1.0;
+ d->E2IdirY = -1.0;
+ }
+ }
+ else {
+ d->dc[d->level].ScaleInX = 1;
+ d->dc[d->level].ScaleInY = 1;
+ }
+ break;
+ }
+ case U_EMR_SETWINDOWORGEX:
+ {
+ dbg_str << "<!-- U_EMR_SETWINDOWORGEX -->\n";
+
+ PU_EMRSETWINDOWORGEX pEmr = (PU_EMRSETWINDOWORGEX) lpEMFR;
+ d->dc[d->level].winorg = pEmr->ptlOrigin;
+ break;
+ }
+ case U_EMR_SETVIEWPORTEXTEX:
+ {
+ dbg_str << "<!-- U_EMR_SETVIEWPORTEXTEX -->\n";
+
+ PU_EMRSETVIEWPORTEXTEX pEmr = (PU_EMRSETVIEWPORTEXTEX) lpEMFR;
+
+ d->dc[d->level].sizeView = pEmr->szlExtent;
+
+ if (!d->dc[d->level].sizeView.cx || !d->dc[d->level].sizeView.cy) {
+ d->dc[d->level].sizeView = d->dc[d->level].sizeWnd;
+ if (!d->dc[d->level].sizeView.cx || !d->dc[d->level].sizeView.cy) {
+ d->dc[d->level].sizeView.cx = d->PixelsOutX;
+ d->dc[d->level].sizeView.cy = d->PixelsOutY;
+ }
+ }
+
+ if (!d->dc[d->level].sizeWnd.cx || !d->dc[d->level].sizeWnd.cy) {
+ d->dc[d->level].sizeWnd = d->dc[d->level].sizeView;
+ }
+
+ /* scales logical to EMF pixels, transfer a negative sign on Y, if any */
+ if (d->dc[d->level].sizeWnd.cx && d->dc[d->level].sizeWnd.cy) {
+ d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.cx / (double) d->dc[d->level].sizeWnd.cx;
+ d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.cy / (double) d->dc[d->level].sizeWnd.cy;
+ if( d->dc[d->level].ScaleInY < 0){
+ d->dc[d->level].ScaleInY *= -1.0;
+ d->E2IdirY = -1.0;
+ }
+ }
+ else {
+ d->dc[d->level].ScaleInX = 1;
+ d->dc[d->level].ScaleInY = 1;
+ }
+ break;
+ }
+ case U_EMR_SETVIEWPORTORGEX:
+ {
+ dbg_str << "<!-- U_EMR_SETVIEWPORTORGEX -->\n";
+
+ PU_EMRSETVIEWPORTORGEX pEmr = (PU_EMRSETVIEWPORTORGEX) lpEMFR;
+ d->dc[d->level].vieworg = pEmr->ptlOrigin;
+ break;
+ }
+ case U_EMR_SETBRUSHORGEX: dbg_str << "<!-- U_EMR_SETBRUSHORGEX -->\n"; break;
+ case U_EMR_EOF:
+ {
+ dbg_str << "<!-- U_EMR_EOF -->\n";
+
+ tmp_outsvg << "</svg>\n";
+ d->outsvg = d->outdef + d->defs + d->outsvg;
+ OK=0;
+ break;
+ }
+ case U_EMR_SETPIXELV: dbg_str << "<!-- U_EMR_SETPIXELV -->\n"; break;
+ case U_EMR_SETMAPPERFLAGS: dbg_str << "<!-- U_EMR_SETMAPPERFLAGS -->\n"; break;
+ case U_EMR_SETMAPMODE:
+ {
+ dbg_str << "<!-- U_EMR_SETMAPMODE -->\n";
+ PU_EMRSETMAPMODE pEmr = (PU_EMRSETMAPMODE) lpEMFR;
+ switch (pEmr->iMode){
+ case U_MM_TEXT:
+ default:
+ // Use all values from the header.
+ break;
+ /* For all of the following the indicated scale this will be encoded in WindowExtEx/ViewportExtex
+ and show up in ScaleIn[XY]
+ */
+ case U_MM_LOMETRIC: // 1 LU = 0.1 mm,
+ case U_MM_HIMETRIC: // 1 LU = 0.01 mm
+ case U_MM_LOENGLISH: // 1 LU = 0.1 in
+ case U_MM_HIENGLISH: // 1 LU = 0.01 in
+ case U_MM_TWIPS: // 1 LU = 1/1440 in
+ d->E2IdirY = -1.0;
+ // Use d->D2Pscale[XY] values from the header.
+ break;
+ case U_MM_ISOTROPIC: // ScaleIn[XY] should be set elsewhere by SETVIEWPORTEXTEX and SETWINDOWEXTEX
+ case U_MM_ANISOTROPIC:
+ break;
+ }
+ break;
+ }
+ case U_EMR_SETBKMODE:
+ {
+ dbg_str << "<!-- U_EMR_SETBKMODE -->\n";
+ PU_EMRSETBKMODE pEmr = (PU_EMRSETBKMODE) lpEMFR;
+ tbkMode = pEmr->iMode;
+ if(tbkMode != d->dc[d->level].bkMode){
+ d->dc[d->level].dirty |= DIRTY_TEXT;
+ if(tbkMode != d->dc[d->level].bkMode){
+ if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; }
+ if(d->dc[d->level].stroke_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_STROKE; }
+ }
+ memcpy(&tbkColor,&(d->dc[d->level].bkColor),sizeof(U_COLORREF));
+ }
+ break;
+ }
+ case U_EMR_SETPOLYFILLMODE:
+ {
+ dbg_str << "<!-- U_EMR_SETPOLYFILLMODE -->\n";
+
+ PU_EMRSETPOLYFILLMODE pEmr = (PU_EMRSETPOLYFILLMODE) lpEMFR;
+ d->dc[d->level].style.fill_rule.value =
+ (pEmr->iMode == U_ALTERNATE
+ ? SP_WIND_RULE_NONZERO
+ : (pEmr->iMode == U_WINDING ? SP_WIND_RULE_INTERSECT : SP_WIND_RULE_NONZERO));
+ break;
+ }
+ case U_EMR_SETROP2:
+ {
+ dbg_str << "<!-- U_EMR_SETROP2 -->\n";
+ PU_EMRSETROP2 pEmr = (PU_EMRSETROP2) lpEMFR;
+ d->dwRop2 = pEmr->iMode;
+ break;
+ }
+ case U_EMR_SETSTRETCHBLTMODE:
+ {
+ PU_EMRSETSTRETCHBLTMODE pEmr = (PU_EMRSETSTRETCHBLTMODE) lpEMFR; // from wingdi.h
+ BLTmode = pEmr->iMode;
+ dbg_str << "<!-- U_EMR_SETSTRETCHBLTMODE -->\n";
+ break;
+ }
+ case U_EMR_SETTEXTALIGN:
+ {
+ dbg_str << "<!-- U_EMR_SETTEXTALIGN -->\n";
+
+ PU_EMRSETTEXTALIGN pEmr = (PU_EMRSETTEXTALIGN) lpEMFR;
+ d->dc[d->level].textAlign = pEmr->iMode;
+ break;
+ }
+ case U_EMR_SETCOLORADJUSTMENT:
+ dbg_str << "<!-- U_EMR_SETCOLORADJUSTMENT -->\n";
+ break;
+ case U_EMR_SETTEXTCOLOR:
+ {
+ dbg_str << "<!-- U_EMR_SETTEXTCOLOR -->\n";
+
+ PU_EMRSETTEXTCOLOR pEmr = (PU_EMRSETTEXTCOLOR) lpEMFR;
+ d->dc[d->level].textColor = pEmr->crColor;
+ if(tbkMode != d->dc[d->level].bkMode){
+ if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; }
+ if(d->dc[d->level].stroke_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_STROKE; }
+ }
+ // not text_dirty, because multicolored complex text is supported in libTERE
+ break;
+ }
+ case U_EMR_SETBKCOLOR:
+ {
+ dbg_str << "<!-- U_EMR_SETBKCOLOR -->\n";
+
+ PU_EMRSETBKCOLOR pEmr = (PU_EMRSETBKCOLOR) lpEMFR;
+ tbkColor = pEmr->crColor;
+ if(memcmp(&tbkColor, &(d->dc[d->level].bkColor), sizeof(U_COLORREF))){
+ d->dc[d->level].dirty |= DIRTY_TEXT;
+ if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; }
+ if(d->dc[d->level].stroke_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_STROKE; }
+ tbkMode = d->dc[d->level].bkMode;
+ }
+ break;
+ }
+ case U_EMR_OFFSETCLIPRGN:
+ {
+ dbg_str << "<!-- U_EMR_OFFSETCLIPRGN -->\n";
+ if (d->dc[d->level].clip_id) { // can only offsetan existing clipping path
+ PU_EMROFFSETCLIPRGN pEmr = (PU_EMROFFSETCLIPRGN) lpEMFR;
+ U_POINTL off = pEmr->ptlOffset;
+ unsigned int real_idx = d->dc[d->level].clip_id - 1;
+ Geom::PathVector tmp_vect = sp_svg_read_pathv(d->clips.strings[real_idx]);
+ double ox = pix_to_x_point(d, off.x, off.y) - pix_to_x_point(d, 0, 0); // take into account all active transforms
+ double oy = pix_to_y_point(d, off.x, off.y) - pix_to_y_point(d, 0, 0);
+ Geom::Affine tf = Geom::Translate(ox,oy);
+ tmp_vect *= tf;
+ add_clips(d, sp_svg_write_path(tmp_vect).c_str(), U_RGN_COPY);
+ }
+ break;
+ }
+ case U_EMR_MOVETOEX:
+ {
+ dbg_str << "<!-- U_EMR_MOVETOEX -->\n";
+
+ PU_EMRMOVETOEX pEmr = (PU_EMRMOVETOEX) lpEMFR;
+
+ d->mask |= emr_mask;
+
+ d->dc[d->level].cur = pEmr->ptl;
+
+ tmp_path <<
+ "\n\tM " << pix_to_xy( d, pEmr->ptl.x, pEmr->ptl.y ) << " ";
+ break;
+ }
+ case U_EMR_SETMETARGN: dbg_str << "<!-- U_EMR_SETMETARGN -->\n"; break;
+ case U_EMR_EXCLUDECLIPRECT:
+ {
+ dbg_str << "<!-- U_EMR_EXCLUDECLIPRECT -->\n";
+
+ PU_EMREXCLUDECLIPRECT pEmr = (PU_EMREXCLUDECLIPRECT) lpEMFR;
+ U_RECTL rc = pEmr->rclClip;
+
+ SVGOStringStream tmp_path;
+ //outer rect, clockwise
+ tmp_path << "M " << faraway << "," << faraway << " ";
+ tmp_path << "L " << faraway << "," << -faraway << " ";
+ tmp_path << "L " << -faraway << "," << -faraway << " ";
+ tmp_path << "L " << -faraway << "," << faraway << " ";
+ tmp_path << "z ";
+ //inner rect, counterclockwise (sign of Y is reversed)
+ tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " ";
+ tmp_path << "z";
+
+ add_clips(d, tmp_path.str().c_str(), U_RGN_AND);
+
+ d->path = "";
+ d->drawtype = 0;
+ break;
+ }
+ case U_EMR_INTERSECTCLIPRECT:
+ {
+ dbg_str << "<!-- U_EMR_INTERSECTCLIPRECT -->\n";
+
+ PU_EMRINTERSECTCLIPRECT pEmr = (PU_EMRINTERSECTCLIPRECT) lpEMFR;
+ U_RECTL rc = pEmr->rclClip;
+
+ SVGOStringStream tmp_path;
+ tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " ";
+ tmp_path << "z";
+
+ add_clips(d, tmp_path.str().c_str(), U_RGN_AND);
+
+ d->path = "";
+ d->drawtype = 0;
+ break;
+ }
+ case U_EMR_SCALEVIEWPORTEXTEX: dbg_str << "<!-- U_EMR_SCALEVIEWPORTEXTEX -->\n"; break;
+ case U_EMR_SCALEWINDOWEXTEX: dbg_str << "<!-- U_EMR_SCALEWINDOWEXTEX -->\n"; break;
+ case U_EMR_SAVEDC:
+ dbg_str << "<!-- U_EMR_SAVEDC -->\n";
+
+ if (d->level < EMF_MAX_DC) {
+ d->dc[d->level + 1] = d->dc[d->level];
+ if(d->dc[d->level].font_name){
+ d->dc[d->level + 1].font_name = strdup(d->dc[d->level].font_name); // or memory access problems because font name pointer duplicated
+ }
+ d->level = d->level + 1;
+ }
+ break;
+ case U_EMR_RESTOREDC:
+ {
+ dbg_str << "<!-- U_EMR_RESTOREDC -->\n";
+
+ PU_EMRRESTOREDC pEmr = (PU_EMRRESTOREDC) lpEMFR;
+ int old_level = d->level;
+ if (pEmr->iRelative >= 0) {
+ if (pEmr->iRelative < d->level)
+ d->level = pEmr->iRelative;
+ }
+ else {
+ if (d->level + pEmr->iRelative >= 0)
+ d->level = d->level + pEmr->iRelative;
+ }
+ while (old_level > d->level) {
+ if (!d->dc[old_level].style.stroke_dasharray.values.empty() &&
+ (old_level == 0 || (old_level > 0 && d->dc[old_level].style.stroke_dasharray !=
+ d->dc[old_level - 1].style.stroke_dasharray))) {
+ d->dc[old_level].style.stroke_dasharray.values.clear();
+ }
+ if(d->dc[old_level].font_name){
+ free(d->dc[old_level].font_name); // else memory leak
+ d->dc[old_level].font_name = nullptr;
+ }
+ old_level--;
+ }
+ break;
+ }
+ case U_EMR_SETWORLDTRANSFORM:
+ {
+ dbg_str << "<!-- U_EMR_SETWORLDTRANSFORM -->\n";
+
+ PU_EMRSETWORLDTRANSFORM pEmr = (PU_EMRSETWORLDTRANSFORM) lpEMFR;
+ d->dc[d->level].worldTransform = pEmr->xform;
+ break;
+ }
+ case U_EMR_MODIFYWORLDTRANSFORM:
+ {
+ dbg_str << "<!-- U_EMR_MODIFYWORLDTRANSFORM -->\n";
+
+ PU_EMRMODIFYWORLDTRANSFORM pEmr = (PU_EMRMODIFYWORLDTRANSFORM) lpEMFR;
+ switch (pEmr->iMode)
+ {
+ case U_MWT_IDENTITY:
+ d->dc[d->level].worldTransform.eM11 = 1.0;
+ d->dc[d->level].worldTransform.eM12 = 0.0;
+ d->dc[d->level].worldTransform.eM21 = 0.0;
+ d->dc[d->level].worldTransform.eM22 = 1.0;
+ d->dc[d->level].worldTransform.eDx = 0.0;
+ d->dc[d->level].worldTransform.eDy = 0.0;
+ break;
+ case U_MWT_LEFTMULTIPLY:
+ {
+// d->dc[d->level].worldTransform = pEmr->xform * worldTransform;
+
+ float a11 = pEmr->xform.eM11;
+ float a12 = pEmr->xform.eM12;
+ float a13 = 0.0;
+ float a21 = pEmr->xform.eM21;
+ float a22 = pEmr->xform.eM22;
+ float a23 = 0.0;
+ float a31 = pEmr->xform.eDx;
+ float a32 = pEmr->xform.eDy;
+ float a33 = 1.0;
+
+ float b11 = d->dc[d->level].worldTransform.eM11;
+ float b12 = d->dc[d->level].worldTransform.eM12;
+ //float b13 = 0.0;
+ float b21 = d->dc[d->level].worldTransform.eM21;
+ float b22 = d->dc[d->level].worldTransform.eM22;
+ //float b23 = 0.0;
+ float b31 = d->dc[d->level].worldTransform.eDx;
+ float b32 = d->dc[d->level].worldTransform.eDy;
+ //float b33 = 1.0;
+
+ float c11 = a11*b11 + a12*b21 + a13*b31;;
+ float c12 = a11*b12 + a12*b22 + a13*b32;;
+ //float c13 = a11*b13 + a12*b23 + a13*b33;;
+ float c21 = a21*b11 + a22*b21 + a23*b31;;
+ float c22 = a21*b12 + a22*b22 + a23*b32;;
+ //float c23 = a21*b13 + a22*b23 + a23*b33;;
+ float c31 = a31*b11 + a32*b21 + a33*b31;;
+ float c32 = a31*b12 + a32*b22 + a33*b32;;
+ //float c33 = a31*b13 + a32*b23 + a33*b33;;
+
+ d->dc[d->level].worldTransform.eM11 = c11;;
+ d->dc[d->level].worldTransform.eM12 = c12;;
+ d->dc[d->level].worldTransform.eM21 = c21;;
+ d->dc[d->level].worldTransform.eM22 = c22;;
+ d->dc[d->level].worldTransform.eDx = c31;
+ d->dc[d->level].worldTransform.eDy = c32;
+
+ break;
+ }
+ case U_MWT_RIGHTMULTIPLY:
+ {
+// d->dc[d->level].worldTransform = worldTransform * pEmr->xform;
+
+ float a11 = d->dc[d->level].worldTransform.eM11;
+ float a12 = d->dc[d->level].worldTransform.eM12;
+ float a13 = 0.0;
+ float a21 = d->dc[d->level].worldTransform.eM21;
+ float a22 = d->dc[d->level].worldTransform.eM22;
+ float a23 = 0.0;
+ float a31 = d->dc[d->level].worldTransform.eDx;
+ float a32 = d->dc[d->level].worldTransform.eDy;
+ float a33 = 1.0;
+
+ float b11 = pEmr->xform.eM11;
+ float b12 = pEmr->xform.eM12;
+ //float b13 = 0.0;
+ float b21 = pEmr->xform.eM21;
+ float b22 = pEmr->xform.eM22;
+ //float b23 = 0.0;
+ float b31 = pEmr->xform.eDx;
+ float b32 = pEmr->xform.eDy;
+ //float b33 = 1.0;
+
+ float c11 = a11*b11 + a12*b21 + a13*b31;;
+ float c12 = a11*b12 + a12*b22 + a13*b32;;
+ //float c13 = a11*b13 + a12*b23 + a13*b33;;
+ float c21 = a21*b11 + a22*b21 + a23*b31;;
+ float c22 = a21*b12 + a22*b22 + a23*b32;;
+ //float c23 = a21*b13 + a22*b23 + a23*b33;;
+ float c31 = a31*b11 + a32*b21 + a33*b31;;
+ float c32 = a31*b12 + a32*b22 + a33*b32;;
+ //float c33 = a31*b13 + a32*b23 + a33*b33;;
+
+ d->dc[d->level].worldTransform.eM11 = c11;;
+ d->dc[d->level].worldTransform.eM12 = c12;;
+ d->dc[d->level].worldTransform.eM21 = c21;;
+ d->dc[d->level].worldTransform.eM22 = c22;;
+ d->dc[d->level].worldTransform.eDx = c31;
+ d->dc[d->level].worldTransform.eDy = c32;
+
+ break;
+ }
+// case MWT_SET:
+ default:
+ d->dc[d->level].worldTransform = pEmr->xform;
+ break;
+ }
+ break;
+ }
+ case U_EMR_SELECTOBJECT:
+ {
+ dbg_str << "<!-- U_EMR_SELECTOBJECT -->\n";
+
+ PU_EMRSELECTOBJECT pEmr = (PU_EMRSELECTOBJECT) lpEMFR;
+ unsigned int index = pEmr->ihObject;
+
+ if (index & U_STOCK_OBJECT) {
+ switch (index) {
+ case U_NULL_BRUSH:
+ d->dc[d->level].fill_mode = DRAW_PAINT;
+ d->dc[d->level].fill_set = false;
+ break;
+ case U_BLACK_BRUSH:
+ case U_DKGRAY_BRUSH:
+ case U_GRAY_BRUSH:
+ case U_LTGRAY_BRUSH:
+ case U_WHITE_BRUSH:
+ {
+ float val = 0;
+ switch (index) {
+ case U_BLACK_BRUSH:
+ val = 0.0 / 255.0;
+ break;
+ case U_DKGRAY_BRUSH:
+ val = 64.0 / 255.0;
+ break;
+ case U_GRAY_BRUSH:
+ val = 128.0 / 255.0;
+ break;
+ case U_LTGRAY_BRUSH:
+ val = 192.0 / 255.0;
+ break;
+ case U_WHITE_BRUSH:
+ val = 255.0 / 255.0;
+ break;
+ }
+ d->dc[d->level].style.fill.value.color.set( val, val, val );
+
+ d->dc[d->level].fill_mode = DRAW_PAINT;
+ d->dc[d->level].fill_set = true;
+ break;
+ }
+ case U_NULL_PEN:
+ d->dc[d->level].stroke_mode = DRAW_PAINT;
+ d->dc[d->level].stroke_set = false;
+ break;
+ case U_BLACK_PEN:
+ case U_WHITE_PEN:
+ {
+ float val = index == U_BLACK_PEN ? 0 : 1;
+ d->dc[d->level].style.stroke_dasharray.set = false;
+ d->dc[d->level].style.stroke_width.value = 1.0;
+ d->dc[d->level].style.stroke.value.color.set( val, val, val );
+
+ d->dc[d->level].stroke_mode = DRAW_PAINT;
+ d->dc[d->level].stroke_set = true;
+
+ break;
+ }
+ }
+ } else {
+ if ( /*index >= 0 &&*/ index < (unsigned int) d->n_obj) {
+ switch (d->emf_obj[index].type)
+ {
+ case U_EMR_CREATEPEN:
+ select_pen(d, index);
+ break;
+ case U_EMR_CREATEBRUSHINDIRECT:
+ case U_EMR_CREATEDIBPATTERNBRUSHPT:
+ case U_EMR_CREATEMONOBRUSH:
+ select_brush(d, index);
+ break;
+ case U_EMR_EXTCREATEPEN:
+ select_extpen(d, index);
+ break;
+ case U_EMR_EXTCREATEFONTINDIRECTW:
+ select_font(d, index);
+ break;
+ }
+ }
+ }
+ break;
+ }
+ case U_EMR_CREATEPEN:
+ {
+ dbg_str << "<!-- U_EMR_CREATEPEN -->\n";
+
+ PU_EMRCREATEPEN pEmr = (PU_EMRCREATEPEN) lpEMFR;
+ insert_object(d, pEmr->ihPen, U_EMR_CREATEPEN, lpEMFR);
+ break;
+ }
+ case U_EMR_CREATEBRUSHINDIRECT:
+ {
+ dbg_str << "<!-- U_EMR_CREATEBRUSHINDIRECT -->\n";
+
+ PU_EMRCREATEBRUSHINDIRECT pEmr = (PU_EMRCREATEBRUSHINDIRECT) lpEMFR;
+ insert_object(d, pEmr->ihBrush, U_EMR_CREATEBRUSHINDIRECT, lpEMFR);
+ break;
+ }
+ case U_EMR_DELETEOBJECT:
+ dbg_str << "<!-- U_EMR_DELETEOBJECT -->\n";
+ // Objects here are not deleted until the draw completes, new ones may write over an existing one.
+ break;
+ case U_EMR_ANGLEARC:
+ dbg_str << "<!-- U_EMR_ANGLEARC -->\n";
+ break;
+ case U_EMR_ELLIPSE:
+ {
+ dbg_str << "<!-- U_EMR_ELLIPSE -->\n";
+
+ PU_EMRELLIPSE pEmr = (PU_EMRELLIPSE) lpEMFR;
+ U_RECTL rclBox = pEmr->rclBox;
+
+ double cx = pix_to_x_point( d, (rclBox.left + rclBox.right)/2.0, (rclBox.bottom + rclBox.top)/2.0 );
+ double cy = pix_to_y_point( d, (rclBox.left + rclBox.right)/2.0, (rclBox.bottom + rclBox.top)/2.0 );
+ double rx = pix_to_abs_size( d, std::abs(rclBox.right - rclBox.left )/2.0 );
+ double ry = pix_to_abs_size( d, std::abs(rclBox.top - rclBox.bottom)/2.0 );
+
+ SVGOStringStream tmp_ellipse;
+ tmp_ellipse << "cx=\"" << cx << "\" ";
+ tmp_ellipse << "cy=\"" << cy << "\" ";
+ tmp_ellipse << "rx=\"" << rx << "\" ";
+ tmp_ellipse << "ry=\"" << ry << "\" ";
+
+ d->mask |= emr_mask;
+
+ d->outsvg += " <ellipse ";
+ output_style(d, lpEMFR->iType); //
+ d->outsvg += "\n\t";
+ d->outsvg += tmp_ellipse.str().c_str();
+ d->outsvg += "/> \n";
+ d->path = "";
+ break;
+ }
+ case U_EMR_RECTANGLE:
+ {
+ dbg_str << "<!-- U_EMR_RECTANGLE -->\n";
+
+ PU_EMRRECTANGLE pEmr = (PU_EMRRECTANGLE) lpEMFR;
+ U_RECTL rc = pEmr->rclBox;
+
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, rc.left , rc.top ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, rc.right, rc.top ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, rc.right, rc.bottom ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, rc.left, rc.bottom ) << " ";
+ tmp_rectangle << "\n\tz";
+
+ d->mask |= emr_mask;
+
+ tmp_path << tmp_rectangle.str().c_str();
+ break;
+ }
+ case U_EMR_ROUNDRECT:
+ {
+ dbg_str << "<!-- U_EMR_ROUNDRECT -->\n";
+
+ PU_EMRROUNDRECT pEmr = (PU_EMRROUNDRECT) lpEMFR;
+ U_RECTL rc = pEmr->rclBox;
+ U_SIZEL corner = pEmr->szlCorner;
+ double f = 4.*(sqrt(2) - 1)/3;
+ double f1 = 1.0 - f;
+ double cnx = corner.cx/2;
+ double cny = corner.cy/2;
+
+ // clang-format off
+ 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";
+ // clang-format on
+
+ 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, &center, &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, &center, &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, &center, &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, &center, &start, &end, &size)){
+ // draw a line from current position to start, arc from there
+ tmp_path << "\n\tL " << pix_to_xy(d, start.x, start.y);
+ tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0;
+ tmp_path << " ";
+ tmp_path << 180.0 * current_rotation(d)/M_PI;
+ tmp_path << " ";
+ tmp_path << " " << f1 << "," << f2 << " ";
+ tmp_path << pix_to_xy(d, end.x, end.y)<< " ";
+
+ d->mask |= emr_mask;
+ }
+ else {
+ dbg_str << "<!-- ARCTO record is invalid -->\n";
+ }
+ break;
+ }
+ case U_EMR_POLYDRAW: dbg_str << "<!-- U_EMR_POLYDRAW -->\n"; break;
+ case U_EMR_SETARCDIRECTION:
+ {
+ dbg_str << "<!-- U_EMR_SETARCDIRECTION -->\n";
+ PU_EMRSETARCDIRECTION pEmr = (PU_EMRSETARCDIRECTION) lpEMFR;
+ if(d->arcdir == U_AD_CLOCKWISE || d->arcdir == U_AD_COUNTERCLOCKWISE){ // EMF file could be corrupt
+ d->arcdir = pEmr->iArcDirection;
+ }
+ break;
+ }
+ case U_EMR_SETMITERLIMIT:
+ {
+ dbg_str << "<!-- U_EMR_SETMITERLIMIT -->\n";
+
+ PU_EMRSETMITERLIMIT pEmr = (PU_EMRSETMITERLIMIT) lpEMFR;
+
+ //The function takes a float but saves a 32 bit int in the U_EMR_SETMITERLIMIT record.
+ float miterlimit = *((int32_t *) &(pEmr->eMiterLimit));
+ d->dc[d->level].style.stroke_miterlimit.value = miterlimit; //ratio, not a pt size
+ if (d->dc[d->level].style.stroke_miterlimit.value < 2)
+ d->dc[d->level].style.stroke_miterlimit.value = 2.0;
+ break;
+ }
+ case U_EMR_BEGINPATH:
+ {
+ dbg_str << "<!-- U_EMR_BEGINPATH -->\n";
+ // The next line should never be needed, should have been handled before main switch
+ // qualifier added because EMF's encountered where moveto preceded beginpath followed by lineto
+ if(d->mask & U_DRAW_VISIBLE){
+ d->path = "";
+ }
+ d->mask |= emr_mask;
+ break;
+ }
+ case U_EMR_ENDPATH:
+ {
+ dbg_str << "<!-- U_EMR_ENDPATH -->\n";
+ d->mask &= (0xFFFFFFFF - U_DRAW_ONLYTO); // clear the OnlyTo bit (it might not have been set), prevents any further path extension
+ break;
+ }
+ case U_EMR_CLOSEFIGURE:
+ {
+ dbg_str << "<!-- U_EMR_CLOSEFIGURE -->\n";
+ // EMF may contain multiple closefigures on one path
+ tmp_path << "\n\tz";
+ d->mask |= U_DRAW_CLOSED;
+ break;
+ }
+ case U_EMR_FILLPATH:
+ {
+ dbg_str << "<!-- U_EMR_FILLPATH -->\n";
+ if(d->mask & U_DRAW_PATH){ // Operation only effects declared paths
+ if(!(d->mask & U_DRAW_CLOSED)){ // Close a path not explicitly closed by an EMRCLOSEFIGURE, otherwise fill makes no sense
+ tmp_path << "\n\tz";
+ d->mask |= U_DRAW_CLOSED;
+ }
+ d->mask |= emr_mask;
+ d->drawtype = U_EMR_FILLPATH;
+ }
+ break;
+ }
+ case U_EMR_STROKEANDFILLPATH:
+ {
+ dbg_str << "<!-- U_EMR_STROKEANDFILLPATH -->\n";
+ if(d->mask & U_DRAW_PATH){ // Operation only effects declared paths
+ if(!(d->mask & U_DRAW_CLOSED)){ // Close a path not explicitly closed by an EMRCLOSEFIGURE, otherwise fill makes no sense
+ tmp_path << "\n\tz";
+ d->mask |= U_DRAW_CLOSED;
+ }
+ d->mask |= emr_mask;
+ d->drawtype = U_EMR_STROKEANDFILLPATH;
+ }
+ break;
+ }
+ case U_EMR_STROKEPATH:
+ {
+ dbg_str << "<!-- U_EMR_STROKEPATH -->\n";
+ if(d->mask & U_DRAW_PATH){ // Operation only effects declared paths
+ d->mask |= emr_mask;
+ d->drawtype = U_EMR_STROKEPATH;
+ }
+ break;
+ }
+ case U_EMR_FLATTENPATH: dbg_str << "<!-- U_EMR_FLATTENPATH -->\n"; break;
+ case U_EMR_WIDENPATH: dbg_str << "<!-- U_EMR_WIDENPATH -->\n"; break;
+ case U_EMR_SELECTCLIPPATH:
+ {
+ dbg_str << "<!-- U_EMR_SELECTCLIPPATH -->\n";
+ PU_EMRSELECTCLIPPATH pEmr = (PU_EMRSELECTCLIPPATH) lpEMFR;
+ int logic = pEmr->iMode;
+
+ if ((logic < U_RGN_MIN) || (logic > U_RGN_MAX)){ break; }
+ add_clips(d, d->path.c_str(), logic); // finds an existing one or stores this, sets clip_id
+ d->path = "";
+ d->drawtype = 0;
+ break;
+ }
+ case U_EMR_ABORTPATH:
+ {
+ dbg_str << "<!-- U_EMR_ABORTPATH -->\n";
+ d->path = "";
+ d->drawtype = 0;
+ break;
+ }
+ case U_EMR_UNDEF69: dbg_str << "<!-- U_EMR_UNDEF69 -->\n"; break;
+ case U_EMR_COMMENT:
+ {
+ dbg_str << "<!-- U_EMR_COMMENT -->\n";
+
+ PU_EMRCOMMENT pEmr = (PU_EMRCOMMENT) lpEMFR;
+
+ char *szTxt = (char *) pEmr->Data;
+
+ for (uint32_t i = 0; i < pEmr->cbData; i++) {
+ if ( *szTxt) {
+ if ( *szTxt >= ' ' && *szTxt < 'z' && *szTxt != '<' && *szTxt != '>' ) {
+ tmp_str << *szTxt;
+ }
+ szTxt++;
+ }
+ }
+
+ if (false && strlen(tmp_str.str().c_str())) {
+ tmp_outsvg << " <!-- \"";
+ tmp_outsvg << tmp_str.str().c_str();
+ tmp_outsvg << "\" -->\n";
+ }
+
+ break;
+ }
+ case U_EMR_FILLRGN: dbg_str << "<!-- U_EMR_FILLRGN -->\n"; break;
+ case U_EMR_FRAMERGN: dbg_str << "<!-- U_EMR_FRAMERGN -->\n"; break;
+ case U_EMR_INVERTRGN: dbg_str << "<!-- U_EMR_INVERTRGN -->\n"; break;
+ case U_EMR_PAINTRGN: dbg_str << "<!-- U_EMR_PAINTRGN -->\n"; break;
+ case U_EMR_EXTSELECTCLIPRGN:
+ {
+ dbg_str << "<!-- U_EMR_EXTSELECTCLIPRGN -->\n";
+
+ PU_EMREXTSELECTCLIPRGN pEmr = (PU_EMREXTSELECTCLIPRGN) lpEMFR;
+ // the only mode we implement - this clears the clipping region
+ if (pEmr->iMode == U_RGN_COPY) {
+ d->dc[d->level].clip_id = 0;
+ }
+ break;
+ }
+ case U_EMR_BITBLT:
+ {
+ dbg_str << "<!-- U_EMR_BITBLT -->\n";
+
+ PU_EMRBITBLT pEmr = (PU_EMRBITBLT) lpEMFR;
+ // Treat all nonImage bitblts as a rectangular write. Definitely not correct, but at
+ // least it leaves objects where the operations should have been.
+ if (!pEmr->cbBmiSrc) {
+ // should be an application of a DIBPATTERNBRUSHPT, use a solid color instead
+
+ if(pEmr->dwRop == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */
+ int32_t dx = pEmr->Dest.x;
+ int32_t dy = pEmr->Dest.y;
+ int32_t dw = pEmr->cDest.x;
+ int32_t dh = pEmr->cDest.y;
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " ";
+ tmp_rectangle << "\n\tz";
+
+ d->mask |= emr_mask;
+ d->dwRop3 = pEmr->dwRop; // we will try to approximate SOME of these
+ d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that
+
+ tmp_path << tmp_rectangle.str().c_str();
+ }
+ else {
+ double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y);
+ double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y);
+ double dw = pix_to_abs_size( d, pEmr->cDest.x);
+ double dh = pix_to_abs_size( d, pEmr->cDest.y);
+ //source position within the bitmap, in pixels
+ int sx = pEmr->Src.x + pEmr->xformSrc.eDx;
+ int sy = pEmr->Src.y + pEmr->xformSrc.eDy;
+ int sw = 0; // extract all of the image
+ int sh = 0;
+ if(sx<0)sx=0;
+ if(sy<0)sy=0;
+ common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh,
+ pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc);
+ }
+ break;
+ }
+ case U_EMR_STRETCHBLT:
+ {
+ dbg_str << "<!-- U_EMR_STRETCHBLT -->\n";
+ PU_EMRSTRETCHBLT pEmr = (PU_EMRSTRETCHBLT) lpEMFR;
+ // Always grab image, ignore modes.
+ if (pEmr->cbBmiSrc) {
+ double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y);
+ double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y);
+ double dw = pix_to_abs_size( d, pEmr->cDest.x);
+ double dh = pix_to_abs_size( d, pEmr->cDest.y);
+ //source position within the bitmap, in pixels
+ int sx = pEmr->Src.x + pEmr->xformSrc.eDx;
+ int sy = pEmr->Src.y + pEmr->xformSrc.eDy;
+ int sw = pEmr->cSrc.x; // extract the specified amount of the image
+ int sh = pEmr->cSrc.y;
+ common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh,
+ pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc);
+ }
+ break;
+ }
+ case U_EMR_MASKBLT:
+ {
+ dbg_str << "<!-- U_EMR_MASKBLT -->\n";
+ PU_EMRMASKBLT pEmr = (PU_EMRMASKBLT) lpEMFR;
+ // Always grab image, ignore masks and modes.
+ if (pEmr->cbBmiSrc) {
+ double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y);
+ double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y);
+ double dw = pix_to_abs_size( d, pEmr->cDest.x);
+ double dh = pix_to_abs_size( d, pEmr->cDest.y);
+ int sx = pEmr->Src.x + pEmr->xformSrc.eDx; //source position within the bitmap, in pixels
+ int sy = pEmr->Src.y + pEmr->xformSrc.eDy;
+ int sw = 0; // extract all of the image
+ int sh = 0;
+ common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh,
+ pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc);
+ }
+ break;
+ }
+ case U_EMR_PLGBLT: dbg_str << "<!-- U_EMR_PLGBLT -->\n"; break;
+ case U_EMR_SETDIBITSTODEVICE: dbg_str << "<!-- U_EMR_SETDIBITSTODEVICE -->\n"; break;
+ case U_EMR_STRETCHDIBITS:
+ {
+ // Some applications use multiple EMF operations, including multiple STRETCHDIBITS to create
+ // images with transparent regions. PowerPoint does this with rotated images, for instance.
+ // Parsing all of that to derive a single resultant image object is left for a later version
+ // of this code. In the meantime, every STRETCHDIBITS goes directly to an image. The Inkscape
+ // user can sort out transparency later using Gimp, if need be.
+
+ PU_EMRSTRETCHDIBITS pEmr = (PU_EMRSTRETCHDIBITS) lpEMFR;
+ double dx = pix_to_x_point( d, pEmr->Dest.x, pEmr->Dest.y );
+ double dy = pix_to_y_point( d, pEmr->Dest.x, pEmr->Dest.y );
+ double dw = pix_to_abs_size( d, pEmr->cDest.x);
+ double dh = pix_to_abs_size( d, pEmr->cDest.y);
+ int sx = pEmr->Src.x; //source position within the bitmap, in pixels
+ int sy = pEmr->Src.y;
+ int sw = pEmr->cSrc.x; // extract the specified amount of the image
+ int sh = pEmr->cSrc.y;
+ common_image_extraction(d,pEmr,dx,dy,dw,dh,sx,sy,sw,sh,
+ pEmr->iUsageSrc, pEmr->offBitsSrc, pEmr->cbBitsSrc, pEmr->offBmiSrc, pEmr->cbBmiSrc);
+
+ dbg_str << "<!-- U_EMR_STRETCHDIBITS -->\n";
+ break;
+ }
+ case U_EMR_EXTCREATEFONTINDIRECTW:
+ {
+ dbg_str << "<!-- U_EMR_EXTCREATEFONTINDIRECTW -->\n";
+
+ PU_EMREXTCREATEFONTINDIRECTW pEmr = (PU_EMREXTCREATEFONTINDIRECTW) lpEMFR;
+ insert_object(d, pEmr->ihFont, U_EMR_EXTCREATEFONTINDIRECTW, lpEMFR);
+ break;
+ }
+ case U_EMR_EXTTEXTOUTA:
+ case U_EMR_EXTTEXTOUTW:
+ case U_EMR_SMALLTEXTOUT:
+ {
+ dbg_str << "<!-- U_EMR_EXTTEXTOUTA/W -->\n";
+
+ PU_EMREXTTEXTOUTW pEmr = (PU_EMREXTTEXTOUTW) lpEMFR;
+ PU_EMRSMALLTEXTOUT pEmrS = (PU_EMRSMALLTEXTOUT) lpEMFR;
+
+ double x1,y1;
+ int roff = sizeof(U_EMRSMALLTEXTOUT); //offset to the start of the variable fields, only used with U_EMR_SMALLTEXTOUT
+ int cChars;
+ if(lpEMFR->iType==U_EMR_SMALLTEXTOUT){
+ x1 = pEmrS->Dest.x;
+ y1 = pEmrS->Dest.y;
+ cChars = pEmrS->cChars;
+ if(!(pEmrS->fuOptions & U_ETO_NO_RECT)){ roff += sizeof(U_RECTL); }
+ }
+ else {
+ x1 = pEmr->emrtext.ptlReference.x;
+ y1 = pEmr->emrtext.ptlReference.y;
+ cChars = 0;
+ }
+ uint32_t fOptions = pEmr->emrtext.fOptions;
+
+ if (d->dc[d->level].textAlign & U_TA_UPDATECP) {
+ x1 = d->dc[d->level].cur.x;
+ y1 = d->dc[d->level].cur.y;
+ }
+
+ double x = pix_to_x_point(d, x1, y1);
+ double y = pix_to_y_point(d, x1, y1);
+
+ /* Rotation issues are handled entirely in libTERE now */
+
+ uint32_t *dup_wt = nullptr;
+
+ if( lpEMFR->iType==U_EMR_EXTTEXTOUTA){
+ /* These should be JUST ASCII, but they might not be...
+ If it holds Utf-8 or plain ASCII the first call will succeed.
+ If not, assume that it holds Latin1.
+ If that fails then something is really screwed up!
+ */
+ dup_wt = U_Utf8ToUtf32le((char *) pEmr + pEmr->emrtext.offString, pEmr->emrtext.nChars, nullptr);
+ if(!dup_wt)dup_wt = U_Latin1ToUtf32le((char *) pEmr + pEmr->emrtext.offString, pEmr->emrtext.nChars, nullptr);
+ if(!dup_wt)dup_wt = unknown_chars(pEmr->emrtext.nChars);
+ }
+ else if( lpEMFR->iType==U_EMR_EXTTEXTOUTW){
+ dup_wt = U_Utf16leToUtf32le((uint16_t *)((char *) pEmr + pEmr->emrtext.offString), pEmr->emrtext.nChars, nullptr);
+ if(!dup_wt)dup_wt = unknown_chars(pEmr->emrtext.nChars);
+ }
+ else { // U_EMR_SMALLTEXTOUT
+ if(pEmrS->fuOptions & U_ETO_SMALL_CHARS){
+ dup_wt = U_Utf8ToUtf32le((char *) pEmrS + roff, cChars, nullptr);
+ }
+ else {
+ dup_wt = U_Utf16leToUtf32le((uint16_t *)((char *) pEmrS + roff), cChars, nullptr);
+ }
+ if(!dup_wt)dup_wt = unknown_chars(cChars);
+ }
+
+ msdepua(dup_wt); //convert everything in Microsoft's private use area. For Symbol, Wingdings, Dingbats
+
+ if(NonToUnicode(dup_wt, d->dc[d->level].font_name)){
+ free(d->dc[d->level].font_name);
+ d->dc[d->level].font_name = strdup("Times New Roman");
+ }
+
+ char *ansi_text;
+ ansi_text = (char *) U_Utf32leToUtf8((uint32_t *)dup_wt, 0, nullptr);
+ free(dup_wt);
+ // Empty string or starts with an invalid escape/control sequence, which is bogus text. Throw it out before g_markup_escape_text can make things worse
+ if(*((uint8_t *)ansi_text) <= 0x1F){
+ free(ansi_text);
+ ansi_text=nullptr;
+ }
+
+ if (ansi_text) {
+
+ SVGOStringStream ts;
+
+ gchar *escaped_text = g_markup_escape_text(ansi_text, -1);
+
+ tsp.x = x*0.8; // TERE expects sizes in points.
+ tsp.y = y*0.8;
+ tsp.color.Red = d->dc[d->level].textColor.Red;
+ tsp.color.Green = d->dc[d->level].textColor.Green;
+ tsp.color.Blue = d->dc[d->level].textColor.Blue;
+ tsp.color.Reserved = 0;
+ switch(d->dc[d->level].style.font_style.value){
+ case SP_CSS_FONT_STYLE_OBLIQUE:
+ tsp.italics = FC_SLANT_OBLIQUE; break;
+ case SP_CSS_FONT_STYLE_ITALIC:
+ tsp.italics = FC_SLANT_ITALIC; break;
+ default:
+ case SP_CSS_FONT_STYLE_NORMAL:
+ tsp.italics = FC_SLANT_ROMAN; break;
+ }
+ switch(d->dc[d->level].style.font_weight.value){
+ case SP_CSS_FONT_WEIGHT_100: tsp.weight = FC_WEIGHT_THIN ; break;
+ case SP_CSS_FONT_WEIGHT_200: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break;
+ case SP_CSS_FONT_WEIGHT_300: tsp.weight = FC_WEIGHT_LIGHT ; break;
+ case SP_CSS_FONT_WEIGHT_400: tsp.weight = FC_WEIGHT_NORMAL ; break;
+ case SP_CSS_FONT_WEIGHT_500: tsp.weight = FC_WEIGHT_MEDIUM ; break;
+ case SP_CSS_FONT_WEIGHT_600: tsp.weight = FC_WEIGHT_SEMIBOLD ; break;
+ case SP_CSS_FONT_WEIGHT_700: tsp.weight = FC_WEIGHT_BOLD ; break;
+ case SP_CSS_FONT_WEIGHT_800: tsp.weight = FC_WEIGHT_EXTRABOLD ; break;
+ case SP_CSS_FONT_WEIGHT_900: tsp.weight = FC_WEIGHT_HEAVY ; break;
+ case SP_CSS_FONT_WEIGHT_NORMAL: tsp.weight = FC_WEIGHT_NORMAL ; break;
+ case SP_CSS_FONT_WEIGHT_BOLD: tsp.weight = FC_WEIGHT_BOLD ; break;
+ case SP_CSS_FONT_WEIGHT_LIGHTER: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break;
+ case SP_CSS_FONT_WEIGHT_BOLDER: tsp.weight = FC_WEIGHT_EXTRABOLD ; break;
+ default: tsp.weight = FC_WEIGHT_NORMAL ; break;
+ }
+ // EMF only supports two types of text decoration
+ tsp.decoration = TXTDECOR_NONE;
+ if(d->dc[d->level].style.text_decoration_line.underline){ tsp.decoration |= TXTDECOR_UNDER; }
+ if(d->dc[d->level].style.text_decoration_line.line_through){ tsp.decoration |= TXTDECOR_STRIKE;}
+
+ // EMF textalignment is a bit strange: 0x6 is center, 0x2 is right, 0x0 is left, the value 0x4 is also drawn left
+ tsp.taln = ((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_CENTER) ? ALICENTER :
+ (((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_LEFT) ? ALILEFT :
+ ALIRIGHT);
+ tsp.taln |= ((d->dc[d->level].textAlign & U_TA_BASEBIT) ? ALIBASE :
+ ((d->dc[d->level].textAlign & U_TA_BOTTOM) ? ALIBOT :
+ ALITOP));
+
+ // language direction can be encoded two ways, U_TA_RTLREADING is preferred
+ if( (fOptions & U_ETO_RTLREADING) || (d->dc[d->level].textAlign & U_TA_RTLREADING) ){ tsp.ldir = LDIR_RL; }
+ else{ tsp.ldir = LDIR_LR; }
+
+ tsp.condensed = FC_WIDTH_NORMAL; // Not implemented well in libTERE (yet)
+ tsp.ori = d->dc[d->level].style.baseline_shift.value; // For now orientation is always the same as escapement
+ tsp.ori += 180.0 * current_rotation(d)/ M_PI; // radians to degrees
+ tsp.string = (uint8_t *) U_strdup(escaped_text); // this will be free'd much later at a trinfo_clear().
+ tsp.fs = d->dc[d->level].style.font_size.computed * 0.8; // Font size in points
+ char *fontspec = TR_construct_fontspec(&tsp, d->dc[d->level].font_name);
+ tsp.fi_idx = ftinfo_load_fontname(d->tri->fti,fontspec);
+ free(fontspec);
+ // when font name includes narrow it may not be set to "condensed". Narrow fonts do not work well anyway though
+ // as the metrics from fontconfig may not match, or the font may not be present.
+ if(0<= TR_findcasesub(d->dc[d->level].font_name, (char *) "Narrow")){ tsp.co=1; }
+ else { tsp.co=0; }
+
+ int status = trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ori is actually escapement
+ if(status==-1){ // change of escapement, emit what we have and reset
+ TR_layout_analyze(d->tri);
+ if (d->dc[d->level].clip_id){
+ SVGOStringStream tmp_clip;
+ tmp_clip << "\n<g\n\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n>";
+ d->outsvg += tmp_clip.str().c_str();
+ }
+ TR_layout_2_svg(d->tri);
+ ts << d->tri->out;
+ d->outsvg += ts.str().c_str();
+ d->tri = trinfo_clear(d->tri);
+ (void) trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ignore return status, it must work
+ if (d->dc[d->level].clip_id){
+ d->outsvg += "\n</g>\n";
+ }
+ }
+
+ g_free(escaped_text);
+ free(ansi_text);
+ }
+
+ break;
+ }
+ case U_EMR_POLYBEZIER16:
+ {
+ dbg_str << "<!-- U_EMR_POLYBEZIER16 -->\n";
+
+ PU_EMRPOLYBEZIER16 pEmr = (PU_EMRPOLYBEZIER16) lpEMFR;
+ PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ?
+ uint32_t i,j;
+
+ if (pEmr->cpts<4)
+ break;
+
+ d->mask |= emr_mask;
+
+ tmp_str << "\n\tM " << pix_to_xy( d, apts[0].x, apts[0].y ) << " ";
+
+ for (i=1; i<pEmr->cpts; ) {
+ tmp_str << "\n\tC ";
+ for (j=0; j<3 && i<pEmr->cpts; j++,i++) {
+ tmp_str << pix_to_xy( d, apts[i].x, apts[i].y ) << " ";
+ }
+ }
+
+ tmp_path << tmp_str.str().c_str();
+
+ break;
+ }
+ case U_EMR_POLYGON16:
+ {
+ dbg_str << "<!-- U_EMR_POLYGON16 -->\n";
+
+ PU_EMRPOLYGON16 pEmr = (PU_EMRPOLYGON16) lpEMFR;
+ PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ?
+ SVGOStringStream tmp_poly;
+ unsigned int i;
+ unsigned int first = 0;
+
+ d->mask |= emr_mask;
+
+ // skip the first point?
+ tmp_poly << "\n\tM " << pix_to_xy( d, apts[first].x, apts[first].y ) << " ";
+
+ for (i=first+1; i<pEmr->cpts; i++) {
+ tmp_poly << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y ) << " ";
+ }
+
+ tmp_path << tmp_poly.str().c_str();
+ tmp_path << "\n\tz";
+ d->mask |= U_DRAW_CLOSED;
+
+ break;
+ }
+ case U_EMR_POLYLINE16:
+ {
+ dbg_str << "<!-- U_EMR_POLYLINE16 -->\n";
+
+ PU_EMRPOLYLINE16 pEmr = (PU_EMRPOLYLINE16) lpEMFR;
+ PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ?
+ uint32_t i;
+
+ if (pEmr->cpts<2)
+ break;
+
+ d->mask |= emr_mask;
+
+ tmp_str << "\n\tM " << pix_to_xy( d, apts[0].x, apts[0].y ) << " ";
+
+ for (i=1; i<pEmr->cpts; i++) {
+ tmp_str << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y ) << " ";
+ }
+
+ tmp_path << tmp_str.str().c_str();
+
+ break;
+ }
+ case U_EMR_POLYBEZIERTO16:
+ {
+ dbg_str << "<!-- U_EMR_POLYBEZIERTO16 -->\n";
+
+ PU_EMRPOLYBEZIERTO16 pEmr = (PU_EMRPOLYBEZIERTO16) lpEMFR;
+ PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ?
+ uint32_t i,j;
+
+ d->mask |= emr_mask;
+
+ for (i=0; i<pEmr->cpts;) {
+ tmp_path << "\n\tC ";
+ for (j=0; j<3 && i<pEmr->cpts; j++,i++) {
+ tmp_path << pix_to_xy( d, apts[i].x, apts[i].y) << " ";
+ }
+ }
+
+ break;
+ }
+ case U_EMR_POLYLINETO16:
+ {
+ dbg_str << "<!-- U_EMR_POLYLINETO16 -->\n";
+
+ PU_EMRPOLYLINETO16 pEmr = (PU_EMRPOLYLINETO16) lpEMFR;
+ PU_POINT16 apts = (PU_POINT16) pEmr->apts; // Bug in MinGW wingdi.h ?
+ uint32_t i;
+
+ d->mask |= emr_mask;
+
+ for (i=0; i<pEmr->cpts;i++) {
+ tmp_path << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y) << " ";
+ }
+
+ break;
+ }
+ case U_EMR_POLYPOLYLINE16:
+ case U_EMR_POLYPOLYGON16:
+ {
+ if (lpEMFR->iType == U_EMR_POLYPOLYLINE16)
+ dbg_str << "<!-- U_EMR_POLYPOLYLINE16 -->\n";
+ if (lpEMFR->iType == U_EMR_POLYPOLYGON16)
+ dbg_str << "<!-- U_EMR_POLYPOLYGON16 -->\n";
+
+ PU_EMRPOLYPOLYGON16 pEmr = (PU_EMRPOLYPOLYGON16) lpEMFR;
+ unsigned int n, i, j;
+
+ d->mask |= emr_mask;
+
+ PU_POINT16 apts = (PU_POINT16) &pEmr->aPolyCounts[pEmr->nPolys];
+
+ i = 0;
+ for (n=0; n<pEmr->nPolys && i<pEmr->cpts; n++) {
+ SVGOStringStream poly_path;
+
+ poly_path << "\n\tM " << pix_to_xy( d, apts[i].x, apts[i].y) << " ";
+ i++;
+
+ for (j=1; j<pEmr->aPolyCounts[n] && i<pEmr->cpts; j++) {
+ poly_path << "\n\tL " << pix_to_xy( d, apts[i].x, apts[i].y) << " ";
+ i++;
+ }
+
+ tmp_str << poly_path.str().c_str();
+ if (lpEMFR->iType == U_EMR_POLYPOLYGON16)
+ tmp_str << " z";
+ tmp_str << " \n";
+ }
+
+ tmp_path << tmp_str.str().c_str();
+
+ break;
+ }
+ case U_EMR_POLYDRAW16: dbg_str << "<!-- U_EMR_POLYDRAW16 -->\n"; break;
+ case U_EMR_CREATEMONOBRUSH:
+ {
+ dbg_str << "<!-- U_EMR_CREATEDIBPATTERNBRUSHPT -->\n";
+
+ PU_EMRCREATEMONOBRUSH pEmr = (PU_EMRCREATEMONOBRUSH) lpEMFR;
+ insert_object(d, pEmr->ihBrush, U_EMR_CREATEMONOBRUSH, lpEMFR);
+ break;
+ }
+ case U_EMR_CREATEDIBPATTERNBRUSHPT:
+ {
+ dbg_str << "<!-- U_EMR_CREATEDIBPATTERNBRUSHPT -->\n";
+
+ PU_EMRCREATEDIBPATTERNBRUSHPT pEmr = (PU_EMRCREATEDIBPATTERNBRUSHPT) lpEMFR;
+ insert_object(d, pEmr->ihBrush, U_EMR_CREATEDIBPATTERNBRUSHPT, lpEMFR);
+ break;
+ }
+ case U_EMR_EXTCREATEPEN:
+ {
+ dbg_str << "<!-- U_EMR_EXTCREATEPEN -->\n";
+
+ PU_EMREXTCREATEPEN pEmr = (PU_EMREXTCREATEPEN) lpEMFR;
+ insert_object(d, pEmr->ihPen, U_EMR_EXTCREATEPEN, lpEMFR);
+ break;
+ }
+ case U_EMR_POLYTEXTOUTA: dbg_str << "<!-- U_EMR_POLYTEXTOUTA -->\n"; break;
+ case U_EMR_POLYTEXTOUTW: dbg_str << "<!-- U_EMR_POLYTEXTOUTW -->\n"; break;
+ case U_EMR_SETICMMODE:
+ {
+ dbg_str << "<!-- U_EMR_SETICMMODE -->\n";
+ PU_EMRSETICMMODE pEmr = (PU_EMRSETICMMODE) lpEMFR;
+ ICMmode= pEmr->iMode;
+ break;
+ }
+ case U_EMR_CREATECOLORSPACE: dbg_str << "<!-- U_EMR_CREATECOLORSPACE -->\n"; break;
+ case U_EMR_SETCOLORSPACE: dbg_str << "<!-- U_EMR_SETCOLORSPACE -->\n"; break;
+ case U_EMR_DELETECOLORSPACE: dbg_str << "<!-- U_EMR_DELETECOLORSPACE -->\n"; break;
+ case U_EMR_GLSRECORD: dbg_str << "<!-- U_EMR_GLSRECORD -->\n"; break;
+ case U_EMR_GLSBOUNDEDRECORD: dbg_str << "<!-- U_EMR_GLSBOUNDEDRECORD -->\n"; break;
+ case U_EMR_PIXELFORMAT: dbg_str << "<!-- U_EMR_PIXELFORMAT -->\n"; break;
+ case U_EMR_DRAWESCAPE: dbg_str << "<!-- U_EMR_DRAWESCAPE -->\n"; break;
+ case U_EMR_EXTESCAPE: dbg_str << "<!-- U_EMR_EXTESCAPE -->\n"; break;
+ case U_EMR_UNDEF107: dbg_str << "<!-- U_EMR_UNDEF107 -->\n"; break;
+ // U_EMR_SMALLTEXTOUT is handled with U_EMR_EXTTEXTOUTA/W above
+ case U_EMR_FORCEUFIMAPPING: dbg_str << "<!-- U_EMR_FORCEUFIMAPPING -->\n"; break;
+ case U_EMR_NAMEDESCAPE: dbg_str << "<!-- U_EMR_NAMEDESCAPE -->\n"; break;
+ case U_EMR_COLORCORRECTPALETTE: dbg_str << "<!-- U_EMR_COLORCORRECTPALETTE -->\n"; break;
+ case U_EMR_SETICMPROFILEA: dbg_str << "<!-- U_EMR_SETICMPROFILEA -->\n"; break;
+ case U_EMR_SETICMPROFILEW: dbg_str << "<!-- U_EMR_SETICMPROFILEW -->\n"; break;
+ case U_EMR_ALPHABLEND: dbg_str << "<!-- U_EMR_ALPHABLEND -->\n"; break;
+ case U_EMR_SETLAYOUT: dbg_str << "<!-- U_EMR_SETLAYOUT -->\n"; break;
+ case U_EMR_TRANSPARENTBLT: dbg_str << "<!-- U_EMR_TRANSPARENTBLT -->\n"; break;
+ case U_EMR_UNDEF117: dbg_str << "<!-- U_EMR_UNDEF117 -->\n"; break;
+ case U_EMR_GRADIENTFILL:
+ {
+ /* Gradient fill is doable for rectangles because those correspond to linear gradients. However,
+ the general case for the triangle fill, with a different color in each corner of the triangle,
+ has no SVG equivalent and cannot be easily emulated with SVG gradients. So the linear gradient
+ is implemented, and the triangle fill just paints with the color of the first corner.
+
+ This record can hold a series of gradients so we are forced to add path elements directly here,
+ it cannot wait for the top of the main loop. Any existing path is erased.
+
+ */
+ dbg_str << "<!-- U_EMR_GRADIENTFILL -->\n";
+ PU_EMRGRADIENTFILL pEmr = (PU_EMRGRADIENTFILL) lpEMFR;
+ int nV = pEmr->nTriVert; // Number of TriVertex objects
+ int nG = pEmr->nGradObj; // Number of gradient triangle/rectangle objects
+ U_TRIVERTEX *tv = (U_TRIVERTEX *)(((char *)lpEMFR) + sizeof(U_EMRGRADIENTFILL));
+ if( pEmr->ulMode == U_GRADIENT_FILL_RECT_H ||
+ pEmr->ulMode == U_GRADIENT_FILL_RECT_V
+ ){
+ SVGOStringStream tmp_rectangle;
+ int i,fill_idx;
+ U_GRADIENT4 *rcs = (U_GRADIENT4 *)(((char *)lpEMFR) + sizeof(U_EMRGRADIENTFILL) + sizeof(U_TRIVERTEX)*nV);
+ for(i=0;i<nG;i++){
+ tmp_rectangle << "\n<path d=\"";
+ fill_idx = add_gradient(d, pEmr->ulMode, tv[rcs[i].UpperLeft], tv[rcs[i].LowerRight]);
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, tv[rcs[i].UpperLeft ].x , tv[rcs[i].UpperLeft ].y ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, tv[rcs[i].LowerRight].x , tv[rcs[i].UpperLeft ].y ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, tv[rcs[i].LowerRight].x , tv[rcs[i].LowerRight].y ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, tv[rcs[i].UpperLeft ].x , tv[rcs[i].LowerRight].y ) << " ";
+ tmp_rectangle << "\n\tz\"";
+ tmp_rectangle << "\n\tstyle=\"stroke:none;fill:url(#";
+ tmp_rectangle << d->gradients.strings[fill_idx];
+ tmp_rectangle << ");\"\n";
+ if (d->dc[d->level].clip_id){
+ tmp_rectangle << "\tclip-path=\"url(#clipEmfPath" << d->dc[d->level].clip_id << ")\"\n";
+ }
+ tmp_rectangle << "/>\n";
+ }
+ d->outsvg += tmp_rectangle.str().c_str();
+ }
+ else if(pEmr->ulMode == U_GRADIENT_FILL_TRIANGLE){
+ SVGOStringStream tmp_triangle;
+ char tmpcolor[8];
+ int i;
+ U_GRADIENT3 *tris = (U_GRADIENT3 *)(((char *)lpEMFR) + sizeof(U_EMRGRADIENTFILL) + sizeof(U_TRIVERTEX)*nV);
+ for(i=0;i<nG;i++){
+ tmp_triangle << "\n<path d=\"";
+ sprintf(tmpcolor,"%6.6X",sethexcolor(trivertex_to_colorref(tv[tris[i].Vertex1])));
+ tmp_triangle << "\n\tM " << pix_to_xy( d, tv[tris[i].Vertex1].x , tv[tris[i].Vertex1].y ) << " ";
+ tmp_triangle << "\n\tL " << pix_to_xy( d, tv[tris[i].Vertex2].x , tv[tris[i].Vertex2].y ) << " ";
+ tmp_triangle << "\n\tL " << pix_to_xy( d, tv[tris[i].Vertex3].x , tv[tris[i].Vertex3].y ) << " ";
+ tmp_triangle << "\n\tz\"";
+ tmp_triangle << "\n\tstyle=\"stroke:none;fill:#";
+ tmp_triangle << tmpcolor;
+ tmp_triangle << ";\"\n/>\n";
+ }
+ d->outsvg += tmp_triangle.str().c_str();
+ }
+ d->path = "";
+ // if it is anything else the record is bogus, so ignore it
+ break;
+ }
+ case U_EMR_SETLINKEDUFIS: dbg_str << "<!-- U_EMR_SETLINKEDUFIS -->\n"; break;
+ case U_EMR_SETTEXTJUSTIFICATION: dbg_str << "<!-- U_EMR_SETTEXTJUSTIFICATION -->\n"; break;
+ case U_EMR_COLORMATCHTOTARGETW: dbg_str << "<!-- U_EMR_COLORMATCHTOTARGETW -->\n"; break;
+ case U_EMR_CREATECOLORSPACEW: dbg_str << "<!-- U_EMR_CREATECOLORSPACEW -->\n"; break;
+ default:
+ dbg_str << "<!-- U_EMR_??? -->\n";
+ break;
+ } //end of switch
+// At run time define environment variable INKSCAPE_DBG_EMF to include string COMMENT.
+// Users may employ this to to place a comment for each processed EMR record in the SVG
+ if(eDbgComment){
+ d->outsvg += dbg_str.str().c_str();
+ }
+ d->outsvg += tmp_outsvg.str().c_str();
+ d->path += tmp_path.str().c_str();
+
+ } //end of while
+// At run time define environment variable INKSCAPE_DBG_EMF to include string FINAL
+// Users may employ this to to show the final SVG derived from the EMF
+ if(eDbgFinal){
+ std::cout << d->outsvg << std::endl;
+ }
+ (void) emr_properties(U_EMR_INVALID); // force the release of the lookup table memory, returned value is irrelevant
+
+ return(file_status);
+}
+
+void Emf::free_emf_strings(EMF_STRINGS name){
+ if(name.count){
+ for(int i=0; i< name.count; i++){ free(name.strings[i]); }
+ free(name.strings);
+ }
+ name.count = 0;
+ name.size = 0;
+}
+
+SPDocument *
+Emf::open( Inkscape::Extension::Input * /*mod*/, const gchar *uri )
+{
+ if (uri == nullptr) {
+ return nullptr;
+ }
+
+ // ensure usage of dot as decimal separator in scanf/printf functions (indepentendly of current locale)
+ char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr));
+ setlocale(LC_NUMERIC, "C");
+
+ EMF_CALLBACK_DATA d;
+
+ d.n_obj = 0; //these might not be set otherwise if the input file is corrupt
+ d.emf_obj = nullptr;
+ d.dc[0].font_name = strdup("Arial"); // Default font, set only on lowest level, it copies up from there EMF spec says device can pick whatever it wants
+
+ // set up the size default for patterns in defs. This might not be referenced if there are no patterns defined in the drawing.
+
+ d.defs += "\n";
+ d.defs += " <pattern id=\"EMFhbasepattern\" \n";
+ d.defs += " patternUnits=\"userSpaceOnUse\"\n";
+ d.defs += " width=\"6\" \n";
+ d.defs += " height=\"6\" \n";
+ d.defs += " x=\"0\" \n";
+ d.defs += " y=\"0\"> \n";
+ d.defs += " </pattern> \n";
+
+
+ size_t length;
+ char *contents;
+ if(emf_readdata(uri, &contents, &length))return(nullptr);
+
+ d.pDesc = nullptr;
+
+ // set up the text reassembly system
+ if(!(d.tri = trinfo_init(nullptr)))return(nullptr);
+ (void) trinfo_load_ft_opts(d.tri, 1,
+ FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP,
+ FT_KERNING_UNSCALED);
+
+ int good = myEnhMetaFileProc(contents,length, &d);
+ free(contents);
+
+ if (d.pDesc){ free( d.pDesc ); }
+
+// std::cout << "SVG Output: " << std::endl << d.outsvg << std::endl;
+
+ SPDocument *doc = nullptr;
+ if (good) {
+ doc = SPDocument::createNewDocFromMem(d.outsvg.c_str(), strlen(d.outsvg.c_str()), TRUE);
+ }
+
+ free_emf_strings(d.hatches);
+ free_emf_strings(d.images);
+ free_emf_strings(d.gradients);
+ free_emf_strings(d.clips);
+
+ if (d.emf_obj) {
+ int i;
+ for (i=0; i<d.n_obj; i++)
+ delete_object(&d, i);
+ delete[] d.emf_obj;
+ }
+
+ d.dc[0].style.stroke_dasharray.values.clear();
+
+ for(int i=0; i<=EMF_MAX_DC; i++){
+ if(d.dc[i].font_name)free(d.dc[i].font_name);
+ }
+
+ d.tri = trinfo_release_except_FC(d.tri);
+
+ // in earlier versions no viewbox was generated and a call to setViewBoxIfMissing() was needed here.
+
+ // restore decimal separator used in scanf/printf functions to initial value
+ setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ return doc;
+}
+
+
+void
+Emf::init ()
+{
+ /* EMF in */
+ // clang-format off
+ 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"
+ "</input>\n"
+ "</inkscape-extension>", new Emf());
+ // clang-format on
+
+ /* EMF out */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("EMF Output") "</name>\n"
+ "<id>org.inkscape.output.emf</id>\n"
+ "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"bool\">true</param>\n"
+ "<param name=\"TnrToSymbol\" gui-text=\"" N_("Map Unicode to Symbol font") "\" type=\"bool\">true</param>\n"
+ "<param name=\"TnrToWingdings\" gui-text=\"" N_("Map Unicode to Wingdings") "\" type=\"bool\">true</param>\n"
+ "<param name=\"TnrToZapfDingbats\" gui-text=\"" N_("Map Unicode to Zapf Dingbats") "\" type=\"bool\">true</param>\n"
+ "<param name=\"UsePUA\" gui-text=\"" N_("Use MS Unicode PUA (0xF020-0xF0FF) for converted characters") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTCharPos\" gui-text=\"" N_("Compensate for PPT font bug") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTDashLine\" gui-text=\"" N_("Convert dashed/dotted lines to single lines") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTGrad2Polys\" gui-text=\"" N_("Convert gradients to colored polygon series") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTLinGrad\" gui-text=\"" N_("Use native rectangular linear gradients") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTPatternAsHatch\" gui-text=\"" N_("Map all fill patterns to standard EMF hatches") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixImageRot\" gui-text=\"" N_("Ignore image rotations") "\" type=\"bool\">false</param>\n"
+ "<output>\n"
+ "<extension>.emf</extension>\n"
+ "<mimetype>image/x-emf</mimetype>\n"
+ "<filetypename>" N_("Enhanced Metafile (*.emf)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Enhanced Metafile") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>", new Emf());
+ // clang-format on
+
+ return;
+}
+
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/emf-inout.h b/src/extension/internal/emf-inout.h
new file mode 100644
index 0000000..74c8053
--- /dev/null
+++ b/src/extension/internal/emf-inout.h
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Enhanced Metafile Input/Output
+ */
+/* Authors:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * David Mathog
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_EXTENSION_INTERNAL_EMF_H
+#define SEEN_EXTENSION_INTERNAL_EMF_H
+
+#include <3rdparty/libuemf/uemf.h>
+#include <3rdparty/libuemf/uemf_safe.h>
+#include <3rdparty/libuemf/uemf_endian.h> // for U_emf_record_sizeok()
+#include "extension/internal/metafile-inout.h" // picks up PNG
+#include "extension/implementation/implementation.h"
+#include "style.h"
+#include "text_reassemble.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+#define DIRTY_NONE 0x00
+#define DIRTY_TEXT 0x01
+#define DIRTY_FILL 0x02
+#define DIRTY_STROKE 0x04
+
+struct EMF_OBJECT {
+ int type = 0;
+ int level = 0;
+ char *lpEMFR = nullptr;
+};
+using PEMF_OBJECT = EMF_OBJECT *;
+
+struct EMF_STRINGS {
+ int size = 0; // number of slots allocated in strings
+ int count = 0; // number of slots used in strings
+ char **strings = nullptr; // place to store strings
+};
+using PEMF_STRINGS = EMF_STRINGS *;
+
+struct EMF_DEVICE_CONTEXT {
+ EMF_DEVICE_CONTEXT() :
+ // SPStyle: class with constructor
+ font_name(nullptr),
+ clip_id(0),
+ stroke_set(false), stroke_mode(0), stroke_idx(0), stroke_recidx(0),
+ fill_set(false), fill_mode(0), fill_idx(0), fill_recidx(0),
+ dirty(0),
+ // sizeWnd, sizeView, winorg, vieworg,
+ ScaleInX(0), ScaleInY(0),
+ ScaleOutX(0), ScaleOutY(0),
+ bkMode(U_TRANSPARENT),
+ // bkColor, textColor
+ textAlign(0)
+ // worldTransform, cur
+ {
+ sizeWnd = sizel_set( 0.0, 0.0 );
+ sizeView = sizel_set( 0.0, 0.0 );
+ winorg = point32_set( 0.0, 0.0 );
+ vieworg = point32_set( 0.0, 0.0 );
+ bkColor = U_RGB(255, 255, 255); // default foreground color (white)
+ textColor = U_RGB(0, 0, 0); // default foreground color (black)
+ worldTransform.eM11 = 1.0;
+ worldTransform.eM12 = 0.0;
+ worldTransform.eM21 = 0.0;
+ worldTransform.eM22 = 1.0;
+ worldTransform.eDx = 0.0;
+ worldTransform.eDy = 0.0;
+ cur = point32_set( 0, 0 );
+ };
+ SPStyle style;
+ char *font_name;
+ int clip_id; // 0 if none, else 1 + index into clips
+ bool stroke_set;
+ int stroke_mode; // enumeration from drawmode, not used if fill_set is not True
+ int stroke_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill
+ int stroke_recidx;// record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change
+ bool fill_set;
+ int fill_mode; // enumeration from drawmode, not used if fill_set is not True
+ int fill_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill
+ int fill_recidx; // record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change
+ int dirty; // holds the dirty bits for text, stroke, fill
+ U_SIZEL sizeWnd;
+ U_SIZEL sizeView;
+ U_POINTL winorg;
+ U_POINTL vieworg;
+ double ScaleInX, ScaleInY;
+ double ScaleOutX, ScaleOutY;
+ uint16_t bkMode;
+ U_COLORREF bkColor;
+ U_COLORREF textColor;
+ uint32_t textAlign;
+ U_XFORM worldTransform;
+ U_POINTL cur;
+};
+using PEMF_DEVICE_CONTEXT = EMF_DEVICE_CONTEXT *;
+
+#define EMF_MAX_DC 128
+
+struct EMF_CALLBACK_DATA {
+
+ EMF_CALLBACK_DATA() :
+ // dc: array, structure w/ constructor
+ level(0),
+ E2IdirY(1.0),
+ D2PscaleX(1.0), D2PscaleY(1.0),
+ MM100InX(0), MM100InY(0),
+ PixelsInX(0), PixelsInY(0),
+ PixelsOutX(0), PixelsOutY(0),
+ ulCornerInX(0), ulCornerInY(0),
+ ulCornerOutX(0), ulCornerOutY(0),
+ mask(0),
+ arcdir(U_AD_COUNTERCLOCKWISE),
+ dwRop2(U_R2_COPYPEN), dwRop3(0),
+ MMX(0),MMY(0),
+ drawtype(0),
+ pDesc(nullptr),
+ // hatches, images, gradients, struct w/ constructor
+ tri(nullptr),
+ n_obj(0)
+ // emf_obj;
+ {};
+
+ Glib::ustring outsvg;
+ Glib::ustring path;
+ Glib::ustring outdef;
+ Glib::ustring defs;
+
+ EMF_DEVICE_CONTEXT dc[EMF_MAX_DC+1]; // FIXME: This should be dynamic..
+ int level;
+
+ double E2IdirY; // EMF Y direction relative to Inkscape Y direction. Will be negative for MM_LOMETRIC etc.
+ double D2PscaleX,D2PscaleY; // EMF device to Inkscape Page scale.
+ float MM100InX, MM100InY; // size of the drawing in hundredths of a millimeter
+ float PixelsInX, PixelsInY; // size of the drawing, in EMF device pixels
+ float PixelsOutX, PixelsOutY; // size of the drawing, in Inkscape pixels
+ double ulCornerInX,ulCornerInY; // Upper left corner, from header rclBounds, in logical units
+ double ulCornerOutX,ulCornerOutY; // Upper left corner, in Inkscape pixels
+ uint32_t mask; // Draw properties
+ int arcdir; //U_AD_COUNTERCLOCKWISE 1 or U_AD_CLOCKWISE 2
+
+ uint32_t dwRop2; // Binary raster operation, 0 if none (use brush/pen unmolested)
+ uint32_t dwRop3; // Ternary raster operation, 0 if none (use brush/pen unmolested)
+
+ float MMX;
+ float MMY;
+
+ unsigned int drawtype; // one of 0 or U_EMR_FILLPATH, U_EMR_STROKEPATH, U_EMR_STROKEANDFILLPATH
+ char *pDesc;
+ // both of these end up in <defs> under the names shown here. These structures allow duplicates to be avoided.
+ EMF_STRINGS hatches; // hold pattern names, all like EMFhatch#_$$$$$$ where # is the EMF hatch code and $$$$$$ is the color
+ EMF_STRINGS images; // hold images, all like Image#, where # is the slot the image lives.
+ EMF_STRINGS gradients; // hold gradient names, all like EMF[HV]_$$$$$$_$$$$$$ where $$$$$$ are the colors
+ EMF_STRINGS clips; // hold clipping paths, referred to be the slot where the clipping path lives
+ TR_INFO *tri; // Text Reassembly data structure
+
+
+ int n_obj;
+ PEMF_OBJECT emf_obj;
+};
+using PEMF_CALLBACK_DATA = EMF_CALLBACK_DATA *;
+
+class Emf : public Metafile
+{
+
+public:
+
+ Emf(); // Empty constructor
+
+ ~Emf() override;//Destructor
+
+ bool check(Inkscape::Extension::Extension *module) override; //Can this module load (always yes for now)
+
+ void save(Inkscape::Extension::Output *mod, // Save the given document to the given filename
+ SPDocument *doc,
+ gchar const *filename) override;
+
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+
+ static void init();//Initialize the class
+
+private:
+
+protected:
+ static void print_document_to_file(SPDocument *doc, const gchar *filename);
+ static double current_scale(PEMF_CALLBACK_DATA d);
+ static std::string current_matrix(PEMF_CALLBACK_DATA d, double x, double y, int useoffset);
+ static double current_rotation(PEMF_CALLBACK_DATA d);
+ static void enlarge_hatches(PEMF_CALLBACK_DATA d);
+ static int in_hatches(PEMF_CALLBACK_DATA d, char *test);
+ static uint32_t add_hatch(PEMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor);
+ static void enlarge_images(PEMF_CALLBACK_DATA d);
+ static int in_images(PEMF_CALLBACK_DATA d, const char *test);
+ static uint32_t add_image(PEMF_CALLBACK_DATA d, void *pEmr, uint32_t cbBits, uint32_t cbBmi,
+ uint32_t iUsage, uint32_t offBits, uint32_t offBmi);
+ static void enlarge_gradients(PEMF_CALLBACK_DATA d);
+ static int in_gradients(PEMF_CALLBACK_DATA d, const char *test);
+ static uint32_t add_gradient(PEMF_CALLBACK_DATA d, uint32_t gradientType, U_TRIVERTEX tv1, U_TRIVERTEX tv2);
+
+ static void enlarge_clips(PEMF_CALLBACK_DATA d);
+ static int in_clips(PEMF_CALLBACK_DATA d, const char *test);
+ static void add_clips(PEMF_CALLBACK_DATA d, const char *clippath, unsigned int logic);
+
+ static void output_style(PEMF_CALLBACK_DATA d, int iType);
+ static double _pix_x_to_point(PEMF_CALLBACK_DATA d, double px);
+ static double _pix_y_to_point(PEMF_CALLBACK_DATA d, double py);
+ static double pix_to_x_point(PEMF_CALLBACK_DATA d, double px, double py);
+ static double pix_to_y_point(PEMF_CALLBACK_DATA d, double px, double py);
+ static double pix_to_abs_size(PEMF_CALLBACK_DATA d, double px);
+ static void snap_to_faraway_pair(double *x, double *y);
+ static std::string pix_to_xy(PEMF_CALLBACK_DATA d, double x, double y);
+ static void select_pen(PEMF_CALLBACK_DATA d, int index);
+ static void select_extpen(PEMF_CALLBACK_DATA d, int index);
+ static void select_brush(PEMF_CALLBACK_DATA d, int index);
+ static void select_font(PEMF_CALLBACK_DATA d, int index);
+ static void delete_object(PEMF_CALLBACK_DATA d, int index);
+ static void insert_object(PEMF_CALLBACK_DATA d, int index, int type, PU_ENHMETARECORD pObj);
+ static int AI_hack(PU_EMRHEADER pEmr);
+ static uint32_t *unknown_chars(size_t count);
+ static void common_image_extraction(PEMF_CALLBACK_DATA d, void *pEmr,
+ double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh,
+ uint32_t iUsage, uint32_t offBits, uint32_t cbBits, uint32_t offBmi, uint32_t cbBmi);
+ static int myEnhMetaFileProc(char *contents, unsigned int length, PEMF_CALLBACK_DATA d);
+ static void free_emf_strings(EMF_STRINGS name);
+
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+
+#endif /* EXTENSION_INTERNAL_EMF_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/emf-print.cpp b/src/extension/internal/emf-print.cpp
new file mode 100644
index 0000000..6d1a3ef
--- /dev/null
+++ b/src/extension/internal/emf-print.cpp
@@ -0,0 +1,2210 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Enhanced Metafile printing
+ *//*
+ * Authors:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ * David Mathog
+ *
+ * Copyright (C) 2006-2009 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ * References:
+ * - How to Create & Play Enhanced Metafiles in Win32
+ * http://support.microsoft.com/kb/q145999/
+ * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles
+ * http://support.microsoft.com/kb/q66949/
+ * - Metafile Functions
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp
+ * - Metafile Structures
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp
+ */
+
+#include "emf-print.h"
+
+#include <cstring>
+#include <glibmm/miscutils.h>
+#include <3rdparty/libuemf/symbol_convert.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/rect.h>
+#include <2geom/curves.h>
+#include <2geom/svg-path-parser.h> // to get from SVG text to Geom::Path
+
+#include "inkscape-version.h"
+
+#include "document.h"
+#include "path-prefix.h"
+#include "style.h"
+#include "style-enums.h" // Fill rules
+
+#include "display/cairo-utils.h" // for Inkscape::Pixbuf::PF_CAIRO
+#include "display/curve.h"
+
+#include "extension/system.h"
+#include "extension/print.h"
+
+#include "helper/geom.h"
+#include "helper/geom-curves.h"
+
+#include "object/sp-pattern.h"
+#include "object/sp-image.h"
+#include "object/sp-gradient.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-item.h"
+#include "object/sp-root.h"
+#include "object/sp-shape.h"
+#include "object/sp-clippath.h"
+
+#include "path/path-boolop.h"
+
+#include "util/units.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+#define PXPERMETER 2835
+
+/* globals */
+static double PX2WORLD;
+static bool FixPPTCharPos, FixPPTDashLine, FixPPTGrad2Polys, FixPPTLinGrad, FixPPTPatternAsHatch, FixImageRot;
+static EMFTRACK *et = nullptr;
+static EMFHANDLES *eht = nullptr;
+
+void PrintEmf::smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale)
+{
+ float fdx;
+ int i;
+ uint32_t *ladx;
+ const char *cptr = &string[strlen(string) + 1]; // this works because of the first fake terminator
+
+ *adx = nullptr;
+ *ky = 0.0; // set a default value
+ sscanf(cptr, "%7d", ndx);
+ if (!*ndx) {
+ return; // this could happen with an empty string
+ }
+ cptr += 7;
+ ladx = (uint32_t *) malloc(*ndx * sizeof(uint32_t));
+ if (!ladx) {
+ g_message("Out of memory");
+ }
+ *adx = ladx;
+ for (i = 0; i < *ndx; i++, cptr += 7, ladx++) {
+ sscanf(cptr, "%7f", &fdx);
+ *ladx = (uint32_t) round(fdx * scale);
+ }
+ cptr++; // skip 2nd fake terminator
+ sscanf(cptr, "%7f", &fdx);
+ *ky = fdx;
+ cptr += 7; // advance over ky and its space
+ sscanf(cptr, "%07d", rtl);
+}
+
+PrintEmf::PrintEmf()
+{
+ // all of the class variables are initialized elsewhere, many in PrintEmf::Begin,
+}
+
+
+unsigned int PrintEmf::setup(Inkscape::Extension::Print * /*mod*/)
+{
+ return TRUE;
+}
+
+
+unsigned int PrintEmf::begin(Inkscape::Extension::Print *mod, SPDocument *doc)
+{
+ U_SIZEL szlDev, szlMm;
+ U_RECTL rclBounds, rclFrame;
+ char *rec;
+ gchar const *utf8_fn = mod->get_param_string("destination");
+
+ // Typically PX2WORLD is 1200/90, using inkscape's default dpi
+ PX2WORLD = 1200.0 / Inkscape::Util::Quantity::convert(1.0, "in", "px");
+ FixPPTCharPos = mod->get_param_bool("FixPPTCharPos");
+ FixPPTDashLine = mod->get_param_bool("FixPPTDashLine");
+ FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys");
+ FixPPTLinGrad = mod->get_param_bool("FixPPTLinGrad");
+ FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch");
+ FixImageRot = mod->get_param_bool("FixImageRot");
+
+ (void) emf_start(utf8_fn, 1000000, 250000, &et); // Initialize the et structure
+ (void) htable_create(128, 128, &eht); // Initialize the eht structure
+
+ char *ansi_uri = (char *) utf8_fn;
+
+
+ // width and height in px
+ _width = doc->getWidth().value("px");
+ _height = doc->getHeight().value("px");
+ _doc_unit_scale = doc->getDocumentScale()[Geom::X];
+
+ // initialize a few global variables
+ hbrush = hbrushOld = hpen = 0;
+ htextalignment = U_TA_BASELINE | U_TA_LEFT;
+ use_stroke = use_fill = simple_shape = usebk = false;
+
+ Inkscape::XML::Node *nv = doc->getReprNamedView();
+ if (nv) {
+ const char *p1 = nv->attribute("pagecolor");
+ char *p2;
+ uint32_t lc = strtoul(&p1[1], &p2, 16); // it looks like "#ABC123"
+ if (*p2) {
+ lc = 0;
+ }
+ gv.bgc = _gethexcolor(lc);
+ gv.rgb[0] = (float) U_RGBAGetR(gv.bgc) / 255.0;
+ gv.rgb[1] = (float) U_RGBAGetG(gv.bgc) / 255.0;
+ gv.rgb[2] = (float) U_RGBAGetB(gv.bgc) / 255.0;
+ }
+
+ bool pageBoundingBox;
+ pageBoundingBox = mod->get_param_bool("pageBoundingBox");
+
+ Geom::Rect d;
+ if (pageBoundingBox) {
+ d = Geom::Rect::from_xywh(0, 0, _width, _height);
+ } else {
+ SPItem *doc_item = doc->getRoot();
+ Geom::OptRect bbox = doc_item->desktopVisualBounds();
+ if (bbox) {
+ d = *bbox;
+ }
+ }
+
+ d *= Geom::Scale(Inkscape::Util::Quantity::convert(1, "px", "in"));
+
+ float dwInchesX = d.width();
+ float dwInchesY = d.height();
+
+ // dwInchesX x dwInchesY in micrometer units, 1200 dpi/25.4 -> dpmm
+ (void) drawing_size((int) ceil(dwInchesX * 25.4), (int) ceil(dwInchesY * 25.4),1200.0/25.4, &rclBounds, &rclFrame);
+
+ // set up the reference device as 100 X A4 horizontal, (1200 dpi/25.4 -> dpmm). Extra digits maintain dpi better in EMF
+ int MMX = 216;
+ int MMY = 279;
+ (void) device_size(MMX, MMY, 1200.0 / 25.4, &szlDev, &szlMm);
+ int PixelsX = szlDev.cx;
+ int PixelsY = szlDev.cy;
+
+ // set up the description: (version string)0(file)00
+ char buff[1024];
+ memset(buff, 0, sizeof(buff));
+ char *p1 = strrchr(ansi_uri, '\\');
+ char *p2 = strrchr(ansi_uri, '/');
+ char *p = MAX(p1, p2);
+ if (p) {
+ p++;
+ } else {
+ p = ansi_uri;
+ }
+ snprintf(buff, sizeof(buff) - 1, "Inkscape %s \1%s\1", Inkscape::version_string, p);
+ uint16_t *Description = U_Utf8ToUtf16le(buff, 0, nullptr);
+ int cbDesc = 2 + wchar16len(Description); // also count the final terminator
+ (void) U_Utf16leEdit(Description, '\1', '\0'); // swap the temporary \1 characters for nulls
+
+ // construct the EMRHEADER record and append it to the EMF in memory
+ rec = U_EMRHEADER_set(rclBounds, rclFrame, nullptr, cbDesc, Description, szlDev, szlMm, 0);
+ free(Description);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at EMRHEADER");
+ }
+
+
+ // Simplest mapping mode, supply all coordinates in pixels
+ rec = U_EMRSETMAPMODE_set(U_MM_TEXT);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at EMRSETMAPMODE");
+ }
+
+
+ // In earlier versions this was used to scale from inkscape's dpi of 90 to
+ // the files 1200 dpi, taking into account PX2WORLD which was 20. Now PX2WORLD
+ // is set so that this matrix is unitary. The usual value of PX2WORLD is 1200/90,
+ // but might be different if the internal dpi is changed.
+
+ U_XFORM worldTransform;
+ worldTransform.eM11 = 1.0;
+ worldTransform.eM12 = 0.0;
+ worldTransform.eM21 = 0.0;
+ worldTransform.eM22 = 1.0;
+ worldTransform.eDx = 0;
+ worldTransform.eDy = 0;
+
+ rec = U_EMRMODIFYWORLDTRANSFORM_set(worldTransform, U_MWT_LEFTMULTIPLY);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at EMRMODIFYWORLDTRANSFORM");
+ }
+
+
+ if (true) {
+ snprintf(buff, sizeof(buff) - 1, "Screen=%dx%dpx, %dx%dmm", PixelsX, PixelsY, MMX, MMY);
+ rec = textcomment_set(buff);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at textcomment_set 1");
+ }
+
+ snprintf(buff, sizeof(buff) - 1, "Drawing=%.1fx%.1fpx, %.1fx%.1fmm", _width, _height, Inkscape::Util::Quantity::convert(dwInchesX, "in", "mm"), Inkscape::Util::Quantity::convert(dwInchesY, "in", "mm"));
+ rec = textcomment_set(buff);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at textcomment_set 1");
+ }
+ }
+
+ /* set some parameters, else the program that reads the EMF may default to other values */
+
+ rec = U_EMRSETBKMODE_set(U_TRANSPARENT);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at U_EMRSETBKMODE_set");
+ }
+
+ hpolyfillmode = U_WINDING;
+ rec = U_EMRSETPOLYFILLMODE_set(U_WINDING);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at U_EMRSETPOLYFILLMODE_set");
+ }
+
+ // Text alignment: (only changed if RTL text is encountered )
+ // - (x,y) coordinates received by this filter are those of the point where the text
+ // actually starts, and already takes into account the text object's alignment;
+ // - for this reason, the EMF text alignment must always be TA_BASELINE|TA_LEFT.
+ htextalignment = U_TA_BASELINE | U_TA_LEFT;
+ rec = U_EMRSETTEXTALIGN_set(U_TA_BASELINE | U_TA_LEFT);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTALIGN_set");
+ }
+
+ htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0;
+ rec = U_EMRSETTEXTCOLOR_set(U_RGB(0, 0, 0));
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at U_EMRSETTEXTCOLOR_set");
+ }
+
+ rec = U_EMRSETROP2_set(U_R2_COPYPEN);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::begin at U_EMRSETROP2_set");
+ }
+
+ /* miterlimit is set with eah pen, so no need to check for it changes as in WMF */
+
+ return 0;
+}
+
+
+unsigned int PrintEmf::finish(Inkscape::Extension::Print * /*mod*/)
+{
+ do_clip_if_present(nullptr); // Terminate any open clip.
+ char *rec;
+ if (!et) {
+ return 0;
+ }
+
+
+ // earlier versions had flush of fill here, but it never executed and was removed
+
+ rec = U_EMREOF_set(0, nullptr, et); // generate the EOF record
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::finish");
+ }
+ (void) emf_finish(et, eht); // Finalize and write out the EMF
+ emf_free(&et); // clean up
+ htable_free(&eht); // clean up
+
+ return 0;
+}
+
+// fcolor is defined when gradients are being expanded, it is the color of one stripe or ring.
+int PrintEmf::create_brush(SPStyle const *style, PU_COLORREF fcolor)
+{
+ float rgb[3];
+ char *rec;
+ U_LOGBRUSH lb;
+ uint32_t brush, fmode;
+ MFDrawMode fill_mode;
+ Inkscape::Pixbuf *pixbuf;
+ uint32_t brushStyle;
+ int hatchType;
+ U_COLORREF hatchColor;
+ U_COLORREF bkColor;
+ uint32_t width = 0; // quiets a harmless compiler warning, initialization not otherwise required.
+ uint32_t height = 0;
+
+ if (!et) {
+ return 0;
+ }
+
+ // set a default fill in case we can't figure out a better way to do it
+ fmode = U_ALTERNATE;
+ fill_mode = DRAW_PAINT;
+ brushStyle = U_BS_SOLID;
+ hatchType = U_HS_SOLIDCLR;
+ bkColor = U_RGB(0, 0, 0);
+ if (fcolor) {
+ hatchColor = *fcolor;
+ } else {
+ hatchColor = U_RGB(0, 0, 0);
+ }
+
+ if (!fcolor && style) {
+ if (style->fill.isColor()) {
+ fill_mode = DRAW_PAINT;
+#if 0
+// opacity not supported by EMF
+ float opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+ if (opacity <= 0.0) {
+ opacity = 0.0; // basically the same as no fill
+ }
+#endif
+ style->fill.value.color.get_rgb_floatv(rgb);
+ hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]);
+
+ fmode = style->fill_rule.computed == 0 ? U_WINDING : (style->fill_rule.computed == 2 ? U_ALTERNATE : U_ALTERNATE);
+ } else if (SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style))) { // must be paint-server
+ SPPaintServer *paintserver = style->fill.value.href->getObject();
+ SPPattern *pat = SP_PATTERN(paintserver);
+ double dwidth = pat->width();
+ double dheight = pat->height();
+ width = dwidth;
+ height = dheight;
+ brush_classify(pat, 0, &pixbuf, &hatchType, &hatchColor, &bkColor);
+ if (pixbuf) {
+ fill_mode = DRAW_IMAGE;
+ } else { // pattern
+ fill_mode = DRAW_PATTERN;
+ if (hatchType == -1) { // Not a standard hatch, so force it to something
+ hatchType = U_HS_CROSS;
+ hatchColor = U_RGB(0xFF, 0xC3, 0xC3);
+ }
+ }
+ if (FixPPTPatternAsHatch) {
+ if (hatchType == -1) { // image or unclassified
+ fill_mode = DRAW_PATTERN;
+ hatchType = U_HS_DIAGCROSS;
+ hatchColor = U_RGB(0xFF, 0xC3, 0xC3);
+ }
+ }
+ brushStyle = U_BS_HATCHED;
+ } else if (SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))) { // must be a gradient
+ // currently we do not do anything with gradients, the code below just sets the color to the average of the stops
+ SPPaintServer *paintserver = style->fill.value.href->getObject();
+ SPLinearGradient *lg = nullptr;
+ SPRadialGradient *rg = nullptr;
+
+ if (SP_IS_LINEARGRADIENT(paintserver)) {
+ lg = SP_LINEARGRADIENT(paintserver);
+ 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);
+ rg->ensureVector(); // when exporting from commandline, vector is not built
+ fill_mode = DRAW_RADIAL_GRADIENT;
+ } else {
+ // default fill
+ }
+
+ if (rg) {
+ if (FixPPTGrad2Polys) {
+ return hold_gradient(rg, fill_mode);
+ } else {
+ hatchColor = avg_stop_color(rg);
+ }
+ } else if (lg) {
+ if (FixPPTGrad2Polys || FixPPTLinGrad) {
+ return hold_gradient(lg, fill_mode);
+ } else {
+ hatchColor = avg_stop_color(lg);
+ }
+ }
+ }
+ } else { // if (!style)
+ // default fill
+ }
+
+ lb = logbrush_set(brushStyle, hatchColor, hatchType);
+
+ switch (fill_mode) {
+ case DRAW_LINEAR_GRADIENT: // fill with average color unless gradients are converted to slices
+ case DRAW_RADIAL_GRADIENT: // ditto
+ case DRAW_PAINT:
+ case DRAW_PATTERN:
+ // SVG text has no background attribute, so OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output.
+ if (usebk) {
+ rec = U_EMRSETBKCOLOR_set(bkColor);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_brush at U_EMRSETBKCOLOR_set");
+ }
+ rec = U_EMRSETBKMODE_set(U_OPAQUE);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_brush at U_EMRSETBKMODE_set");
+ }
+ }
+ rec = createbrushindirect_set(&brush, eht, lb);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_brush at createbrushindirect_set");
+ }
+ break;
+ case DRAW_IMAGE:
+ char *px;
+ char *rgba_px;
+ uint32_t cbPx;
+ uint32_t colortype;
+ PU_RGBQUAD ct;
+ int numCt;
+ U_BITMAPINFOHEADER Bmih;
+ PU_BITMAPINFO Bmi;
+ rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!!
+ colortype = U_BCBM_COLOR32;
+ (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1);
+ // pixbuf can be either PF_CAIRO or PF_GDK, and these have R and B bytes swapped
+ if (pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { swapRBinRGBA(px, width * height); }
+ Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0);
+ Bmi = bitmapinfo_set(Bmih, ct);
+ rec = createdibpatternbrushpt_set(&brush, eht, U_DIB_RGB_COLORS, Bmi, cbPx, px);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_brush at createdibpatternbrushpt_set");
+ }
+ free(px);
+ free(Bmi); // ct will be NULL because of colortype
+ break;
+ }
+
+ hbrush = brush; // need this later for destroy_brush
+ rec = selectobject_set(brush, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_brush at selectobject_set");
+ }
+
+ if (fmode != hpolyfillmode) {
+ hpolyfillmode = fmode;
+ rec = U_EMRSETPOLYFILLMODE_set(fmode);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_brush at U_EMRSETPOLYdrawmode_set");
+ }
+ }
+
+ return 0;
+}
+
+
+void PrintEmf::destroy_brush()
+{
+ char *rec;
+ // before an object may be safely deleted it must no longer be selected
+ // select in a stock object to deselect this one, the stock object should
+ // never be used because we always select in a new one before drawing anythingrestore previous brush, necessary??? Would using a default stock object not work?
+ rec = selectobject_set(U_NULL_BRUSH, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::destroy_brush at selectobject_set");
+ }
+ if (hbrush) {
+ rec = deleteobject_set(&hbrush, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::destroy_brush");
+ }
+ hbrush = 0;
+ }
+}
+
+
+int PrintEmf::create_pen(SPStyle const *style, const Geom::Affine &transform)
+{
+ U_EXTLOGPEN *elp;
+ U_NUM_STYLEENTRY n_dash = 0;
+ U_STYLEENTRY *dash = nullptr;
+ char *rec = nullptr;
+ int linestyle = U_PS_SOLID;
+ int linecap = 0;
+ int linejoin = 0;
+ uint32_t pen;
+ uint32_t brushStyle;
+ Inkscape::Pixbuf *pixbuf;
+ int hatchType;
+ U_COLORREF hatchColor;
+ U_COLORREF bkColor;
+ uint32_t width, height;
+ char *px = nullptr;
+ char *rgba_px;
+ uint32_t cbPx = 0;
+ uint32_t colortype;
+ PU_RGBQUAD ct = nullptr;
+ int numCt = 0;
+ U_BITMAPINFOHEADER Bmih;
+ PU_BITMAPINFO Bmi = nullptr;
+
+ if (!et) {
+ return 0;
+ }
+
+ // set a default stroke in case we can't figure out a better way to do it
+ brushStyle = U_BS_SOLID;
+ hatchColor = U_RGB(0, 0, 0);
+ hatchType = U_HS_HORIZONTAL;
+ bkColor = U_RGB(0, 0, 0);
+
+ if (style) {
+ float rgb[3];
+
+ if (SP_IS_PATTERN(SP_STYLE_STROKE_SERVER(style))) { // must be paint-server
+ SPPaintServer *paintserver = style->stroke.value.href->getObject();
+ SPPattern *pat = SP_PATTERN(paintserver);
+ double dwidth = pat->width();
+ double dheight = pat->height();
+ width = dwidth;
+ height = dheight;
+ brush_classify(pat, 0, &pixbuf, &hatchType, &hatchColor, &bkColor);
+ if (pixbuf) {
+ brushStyle = U_BS_DIBPATTERN;
+ rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!!
+ colortype = U_BCBM_COLOR32;
+ (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1);
+ // pixbuf can be either PF_CAIRO or PF_GDK, and these have R and B bytes swapped
+ if (pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { swapRBinRGBA(px, width * height); }
+ Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0);
+ Bmi = bitmapinfo_set(Bmih, ct);
+ } else { // pattern
+ brushStyle = U_BS_HATCHED;
+ if (usebk) { // OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output.
+ rec = U_EMRSETBKCOLOR_set(bkColor);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_pen at U_EMRSETBKCOLOR_set");
+ }
+ rec = U_EMRSETBKMODE_set(U_OPAQUE);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_pen at U_EMRSETBKMODE_set");
+ }
+ }
+ if (hatchType == -1) { // Not a standard hatch, so force it to something
+ hatchType = U_HS_CROSS;
+ hatchColor = U_RGB(0xFF, 0xC3, 0xC3);
+ }
+ }
+ if (FixPPTPatternAsHatch) {
+ if (hatchType == -1) { // image or unclassified
+ brushStyle = U_BS_HATCHED;
+ hatchType = U_HS_DIAGCROSS;
+ hatchColor = U_RGB(0xFF, 0xC3, 0xC3);
+ }
+ }
+ } else if (SP_IS_GRADIENT(SP_STYLE_STROKE_SERVER(style))) { // must be a gradient
+ // currently we do not do anything with gradients, the code below has no net effect.
+
+ SPPaintServer *paintserver = style->stroke.value.href->getObject();
+ if (SP_IS_LINEARGRADIENT(paintserver)) {
+ SPLinearGradient *lg = SP_LINEARGRADIENT(paintserver);
+
+ 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);
+
+ rg->ensureVector(); // when exporting from commandline, vector is not built
+ double r = rg->r.computed;
+
+ Geom::Point c(rg->cx.computed, rg->cy.computed);
+ Geom::Point xhandle_point(r, 0);
+ Geom::Point yhandle_point(0, -r);
+ yhandle_point += c;
+ xhandle_point += c;
+ if (rg->gradientTransform_set) {
+ c = c * rg->gradientTransform;
+ yhandle_point = yhandle_point * rg->gradientTransform;
+ xhandle_point = xhandle_point * rg->gradientTransform;
+ }
+ hatchColor = avg_stop_color(rg);
+ } else {
+ // default fill
+ }
+ } else if (style->stroke.isColor()) { // test last, always seems to be set, even for other types above
+ style->stroke.value.color.get_rgb_floatv(rgb);
+ brushStyle = U_BS_SOLID;
+ hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]);
+ hatchType = U_HS_SOLIDCLR;
+ } else {
+ // default fill
+ }
+
+
+
+ using Geom::X;
+ using Geom::Y;
+
+ Geom::Point zero(0, 0);
+ Geom::Point one(1, 1);
+ Geom::Point p0(zero * transform);
+ Geom::Point p1(one * transform);
+ Geom::Point p(p1 - p0);
+
+ double scale = sqrt((p[X] * p[X]) + (p[Y] * p[Y])) / sqrt(2);
+
+ if (!style->stroke_width.computed) {
+ return 0; //if width is 0 do not (reset) the pen, it should already be NULL_PEN
+ }
+ uint32_t linewidth = MAX(1, (uint32_t) round(scale * style->stroke_width.computed * PX2WORLD));
+
+ if (style->stroke_linecap.computed == 0) {
+ linecap = U_PS_ENDCAP_FLAT;
+ } else if (style->stroke_linecap.computed == 1) {
+ linecap = U_PS_ENDCAP_ROUND;
+ } else if (style->stroke_linecap.computed == 2) {
+ linecap = U_PS_ENDCAP_SQUARE;
+ }
+
+ if (style->stroke_linejoin.computed == 0) {
+ linejoin = U_PS_JOIN_MITER;
+ } else if (style->stroke_linejoin.computed == 1) {
+ linejoin = U_PS_JOIN_ROUND;
+ } else if (style->stroke_linejoin.computed == 2) {
+ linejoin = U_PS_JOIN_BEVEL;
+ }
+
+ if (!style->stroke_dasharray.values.empty()) {
+ if (FixPPTDashLine) { // will break up line into many smaller lines. Override gradient if that was set, cannot do both.
+ brushStyle = U_BS_SOLID;
+ hatchType = U_HS_HORIZONTAL;
+ } else {
+ unsigned i = 0;
+ while ((linestyle != U_PS_USERSTYLE) && (i < style->stroke_dasharray.values.size())) {
+ if (style->stroke_dasharray.values[i].value > 0.00000001) {
+ linestyle = U_PS_USERSTYLE;
+ }
+ i++;
+ }
+
+ if (linestyle == U_PS_USERSTYLE) {
+ n_dash = style->stroke_dasharray.values.size();
+ dash = new uint32_t[n_dash];
+ for (i = 0; i < n_dash; i++) {
+ dash[i] = MAX(1, (uint32_t)round(scale * style->stroke_dasharray.values[i].value * PX2WORLD));
+ }
+ }
+ }
+ }
+
+ elp = extlogpen_set(
+ U_PS_GEOMETRIC | linestyle | linecap | linejoin,
+ linewidth,
+ brushStyle,
+ hatchColor,
+ hatchType,
+ n_dash,
+ dash);
+
+ } else { // if (!style)
+ linejoin = 0;
+ elp = extlogpen_set(
+ linestyle,
+ 1,
+ U_BS_SOLID,
+ U_RGB(0, 0, 0),
+ U_HS_HORIZONTAL,
+ 0,
+ nullptr);
+ }
+
+ rec = extcreatepen_set(&pen, eht, Bmi, cbPx, px, elp);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_pen at extcreatepen_set");
+ }
+ free(elp);
+ if (Bmi) {
+ free(Bmi);
+ }
+ if (px) {
+ free(px); // ct will always be NULL
+ }
+
+ rec = selectobject_set(pen, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_pen at selectobject_set");
+ }
+ hpen = pen; // need this later for destroy_pen
+
+ if (linejoin == U_PS_JOIN_MITER) {
+ float miterlimit = style->stroke_miterlimit.value; // This is a ratio.
+
+ if (miterlimit < 1) {
+ miterlimit = 1;
+ }
+
+ rec = U_EMRSETMITERLIMIT_set((uint32_t) miterlimit);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::create_pen at U_EMRSETMITERLIMIT_set");
+ }
+ }
+
+ if (n_dash) {
+ delete[] dash;
+ }
+ return 0;
+}
+
+// set the current pen to the stock object NULL_PEN and then delete the defined pen object, if there is one.
+void PrintEmf::destroy_pen()
+{
+ char *rec = nullptr;
+ // before an object may be safely deleted it must no longer be selected
+ // select in a stock object to deselect this one, the stock object should
+ // never be used because we always select in a new one before drawing anythingrestore previous brush, necessary??? Would using a default stock object not work?
+ rec = selectobject_set(U_NULL_PEN, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::destroy_pen at selectobject_set");
+ }
+ if (hpen) {
+ rec = deleteobject_set(&hpen, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::destroy_pen");
+ }
+ hpen = 0;
+ }
+}
+
+/* Return a Path consisting of just the corner points of the single path in a PathVector. If the
+PathVector has more than one path, or that one path is open, or any of its segments are curved, then the
+returned PathVector is an empty path. If the input path is already just straight lines and vertices the output will be the
+same as the sole path in the input. */
+
+Geom::Path PrintEmf::pathv_to_simple_polygon(Geom::PathVector const &pathv, int *vertices)
+{
+ Geom::Point P1_trail;
+ Geom::Point P1;
+ Geom::Point P1_lead;
+ Geom::Point v1,v2;
+ Geom::Path output;
+ Geom::Path bad;
+ Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv);
+ Geom::PathVector::const_iterator pit = pv.begin();
+ Geom::PathVector::const_iterator pit2 = pv.begin();
+ int first_seg=1;
+ ++pit2;
+ *vertices = 0;
+ if(pit->end_closed() != pit->end_default())return(bad); // path must be closed
+ if(pit2 != pv.end())return(bad); // there may only be one path
+ P1_trail = pit->finalPoint();
+ Geom::Path::const_iterator cit = pit->begin();
+ P1 = cit->initialPoint();
+ for(;cit != pit->end_closed();++cit) {
+ if (!is_straight_curve(*cit)) {
+ *vertices = 0;
+ return(bad);
+ }
+ P1_lead = cit->finalPoint();
+ if(Geom::are_near(P1_lead, P1, 1e-5))continue; // duplicate points at the same coordinate
+ v1 = unit_vector(P1 - P1_trail);
+ v2 = unit_vector(P1_lead - P1 );
+ if(Geom::are_near(dot(v1,v2), 1.0, 1e-5)){ // P1 is within a straight line
+ P1 = P1_lead;
+ continue;
+ }
+ // P1 is the center point of a turn of some angle
+ if(!*vertices){
+ output.start( P1 );
+ output.close( pit->closed() );
+ }
+ if(!Geom::are_near(P1, P1_trail, 1e-5)){ // possible for P1 to start on the end point
+ Geom::LineSegment ls(P1_trail, P1);
+ output.append(ls);
+ if(first_seg){
+ *vertices += 2;
+ first_seg=0;
+ }
+ else {
+ *vertices += 1;
+ }
+ }
+ P1_trail = P1;
+ P1 = P1_lead;
+ }
+ return(output);
+}
+
+/* Returns the simplified PathVector (no matter what).
+ Sets is_rect if it is a rectangle.
+ Sets angle that will rotate side closest to horizontal onto horizontal.
+*/
+Geom::Path PrintEmf::pathv_to_rect(Geom::PathVector const &pathv, bool *is_rect, double *angle)
+{
+ Geom::Point P1_trail;
+ Geom::Point P1;
+ Geom::Point v1,v2;
+ int vertices;
+ Geom::Path pR = pathv_to_simple_polygon(pathv, &vertices);
+ *is_rect = false;
+ if(vertices==4){ // or else it cannot be a rectangle
+ int vertex_count=0;
+ /* Get the ends of the LAST line segment.
+ Find minimum rotation to align rectangle with X,Y axes. (Very degenerate if it is rotated 45 degrees.) */
+ *angle = 10.0; /* must be > than the actual angle in radians. */
+ for(Geom::Path::iterator cit = pR.begin();; ++cit){
+ P1_trail = cit->initialPoint();
+ P1 = cit->finalPoint();
+ v1 = unit_vector(P1 - P1_trail);
+ if(v1[Geom::X] > 0){ // only check the 1 or 2 points on vectors aimed the same direction as unit X
+ double ang = asin(v1[Geom::Y]); // because component is rotation by ang of {1,0| vector
+ if(fabs(ang) < fabs(*angle))*angle = -ang; // y increases down, flips sign on angle
+ }
+ if(cit == pR.end_open())break;
+ }
+
+ /* For increased numerical stability, snap the angle to the nearest 1/100th of a degree. */
+ double convert = 36000.0/ (2.0 * M_PI);
+ *angle = round(*angle * convert)/convert;
+
+ // at this stage v1 holds the last vector in the path, whichever direction it points.
+ for(Geom::Path::iterator cit = pR.begin(); ;++cit) {
+ v2 = v1;
+ P1_trail = cit->initialPoint();
+ P1 = cit->finalPoint();
+ v1 = unit_vector(P1 - P1_trail);
+ // P1 is center of a turn that is not 90 degrees. Limit comes from cos(89.9) = .001745
+ if(!Geom::are_near(dot(v1,v2), 0.0, 2e-3))break;
+ vertex_count++;
+ if(cit == pR.end_open())break;
+ }
+ if(vertex_count == 4){
+ *is_rect=true;
+ }
+ }
+ return(pR);
+}
+
+/* Compare a vector with a rectangle's orientation (angle needed to rotate side(s)
+ closest to horizontal to exactly horizontal) and return:
+ 0 none of the following
+ 1 parallel to horizontal
+ 2 parallel to vertical
+ 3 antiparallel to horizontal
+ 4 antiparallel to vertical
+*/
+int PrintEmf::vector_rect_alignment(double angle, Geom::Point vtest){
+ int stat = 0;
+ Geom::Point v1 = Geom::unit_vector(vtest); // unit vector to test alignment
+ Geom::Point v2 = Geom::Point(1,0) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN)
+ Geom::Point v3 = Geom::Point(0,1) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN)
+ if( Geom::are_near(dot(v1,v2), 1.0, 1e-5)){ stat = 1; }
+ else if(Geom::are_near(dot(v1,v2),-1.0, 1e-5)){ stat = 2; }
+ else if(Geom::are_near(dot(v1,v3), 1.0, 1e-5)){ stat = 3; }
+ else if(Geom::are_near(dot(v1,v3),-1.0, 1e-5)){ stat = 4; }
+ return(stat);
+}
+
+/* retrieve the point at the indicated corner:
+ 0 UL (and default)
+ 1 UR
+ 2 LR
+ 3 LL
+ Needed because the start can be any point, and the direction could run either
+ clockwise or counterclockwise. This should work even if the corners of the rectangle
+ are slightly displaced.
+*/
+Geom::Point PrintEmf::get_pathrect_corner(Geom::Path pathRect, double angle, int corner){
+ Geom::Point center(0,0);
+ for(Geom::Path::iterator cit = pathRect.begin(); ; ++cit) {
+ center += cit->initialPoint()/4.0;
+ if(cit == pathRect.end_open())break;
+ }
+
+ int LR; // 1 if Left, 0 if Right
+ int UL; // 1 if Lower, 0 if Upper (as viewed on screen, y coordinates increase downwards)
+ switch(corner){
+ case 1: //UR
+ LR = 0;
+ UL = 0;
+ break;
+ case 2: //LR
+ LR = 0;
+ UL = 1;
+ break;
+ case 3: //LL
+ LR = 1;
+ UL = 1;
+ break;
+ default: //UL
+ LR = 1;
+ UL = 0;
+ break;
+ }
+
+ Geom::Point v1 = Geom::Point(1,0) * Geom::Rotate(-angle); // unit horizontal side (sign change because Y increases DOWN)
+ Geom::Point v2 = Geom::Point(0,1) * Geom::Rotate(-angle); // unit vertical side (sign change because Y increases DOWN)
+ Geom::Point P1;
+ for(Geom::Path::iterator cit = pathRect.begin(); ; ++cit) {
+ P1 = cit->initialPoint();
+
+ if ( ( LR == (dot(P1 - center,v1) > 0 ? 0 : 1) )
+ && ( UL == (dot(P1 - center,v2) > 0 ? 1 : 0) ) ) break;
+ if(cit == pathRect.end_open())break;
+ }
+ return(P1);
+}
+
+U_TRIVERTEX PrintEmf::make_trivertex(Geom::Point Pt, U_COLORREF uc){
+ U_TRIVERTEX tv;
+ using Geom::X;
+ using Geom::Y;
+ tv.x = (int32_t) round(Pt[X]);
+ tv.y = (int32_t) round(Pt[Y]);
+ tv.Red = uc.Red << 8;
+ tv.Green = uc.Green << 8;
+ tv.Blue = uc.Blue << 8;
+ tv.Alpha = uc.Reserved << 8; // EMF will ignore this
+ return(tv);
+}
+
+/* Examine clip. If there is a (new) one then apply it. If there is one and it is the
+ same as the preceding one, leave the preceding one active. If style is NULL
+ terminate the current clip, if any, and return.
+*/
+void PrintEmf::do_clip_if_present(SPStyle const *style){
+ char *rec;
+ static SPClipPath *scpActive = nullptr;
+ if(!style){
+ if(scpActive){ // clear the existing clip
+ rec = U_EMRRESTOREDC_set(-1);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::fill at U_EMRRESTOREDC_set");
+ }
+ scpActive=nullptr;
+ }
+ } else {
+ /* The current implementation converts only one level of clipping. If there were more
+ clips further up the stack they should be combined with the pathvector using "and". Since this
+ comes up rarely, and would involve a lot of searching (all the way up the stack for every
+ draw operation), it has not yet been implemented.
+
+ Note, to debug this section of code use print statements on sp_svg_write_path(combined_pathvector).
+ */
+ /* find the first clip_ref at object or up the stack. There may not be one. */
+ SPClipPath *scp = nullptr;
+ SPItem *item = SP_ITEM(style->object);
+ while(true) {
+ scp = item->getClipObject();
+ if(scp)break;
+ item = SP_ITEM(item->parent);
+ if(!item || SP_IS_ROOT(item))break; // this will never be a clipping path
+ }
+
+ if(scp != scpActive){ // change or remove the clipping
+ if(scpActive){ // clear the existing clip
+ rec = U_EMRRESTOREDC_set(-1);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::fill at U_EMRRESTOREDC_set");
+ }
+ scpActive = nullptr;
+ }
+
+ if (scp) { // set the new clip
+ /* because of units and who knows what other transforms that might be applied above we
+ need the full transform all the way to the root.
+ */
+ Geom::Affine tf = item->transform;
+ SPItem *scan_item = item;
+ while(true) {
+ scan_item = SP_ITEM(scan_item->parent);
+ if(!scan_item)break;
+ tf *= scan_item->transform;
+ }
+ tf *= Geom::Scale(_doc_unit_scale);; // Transform must be in PIXELS, no matter what the document unit is.
+
+ /* find the clipping path */
+ Geom::PathVector combined_pathvector;
+ Geom::Affine tfc; // clipping transform, generally not the same as item transform
+ for (auto& child: scp->children) {
+ item = SP_ITEM(&child);
+ if (!item) {
+ break;
+ }
+ if (SP_IS_GROUP(item)) { // not implemented
+ // return sp_group_render(item);
+ combined_pathvector = merge_PathVector_with_group(combined_pathvector, item, tfc);
+ } else if (SP_IS_SHAPE(item)) {
+ combined_pathvector = merge_PathVector_with_shape(combined_pathvector, item, tfc);
+ } else { // not implemented
+ }
+ }
+
+ if (!combined_pathvector.empty()) { // if clipping path isn't empty, define EMF clipping record
+ scpActive = scp; // remember for next time
+ // the sole purpose of this SAVEDC is to let us clear the clipping region later.
+ rec = U_EMRSAVEDC_set();
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at U_EMRSAVEDC_set");
+ }
+ (void) draw_pathv_to_EMF(combined_pathvector, tf);
+ rec = U_EMRSELECTCLIPPATH_set(U_RGN_COPY);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::do_clip_if_present at U_EMRSELECTCLIPPATH_set");
+ }
+ }
+ else {
+ scpActive = nullptr; // no valid path available to draw, so no DC was saved, so no signal to restore
+ }
+ } // change or remove clipping
+ } // scp exists
+ } // style exists
+}
+
+Geom::PathVector PrintEmf::merge_PathVector_with_group(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform)
+{
+ // sanity test, only a group should be passed in, return empty if something else happens
+ auto group = dynamic_cast<SPGroup const *>(item);
+ if (!group)
+ return {};
+
+ Geom::PathVector new_combined_pathvector = combined_pathvector;
+ Geom::Affine tfc = item->transform * transform;
+ for (auto& child: group->children) {
+ item = SP_ITEM(&child);
+ if (!item) {
+ break;
+ }
+ if (SP_IS_GROUP(item)) {
+ new_combined_pathvector = merge_PathVector_with_group(new_combined_pathvector, item, tfc); // could be endlessly recursive on a badly formed SVG
+ } else if (SP_IS_SHAPE(item)) {
+ new_combined_pathvector = merge_PathVector_with_shape(new_combined_pathvector, item, tfc);
+ } else { // not implemented
+ }
+ }
+ return new_combined_pathvector;
+}
+
+Geom::PathVector PrintEmf::merge_PathVector_with_shape(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform)
+{
+ Geom::PathVector new_combined_pathvector;
+ auto shape = dynamic_cast<SPShape const *>(item);
+
+ // sanity test, only a shape should be passed in, return empty if something else happens
+ if (!shape)
+ return new_combined_pathvector;
+
+ Geom::Affine tfc = item->transform * transform;
+ if (shape->curve()) {
+ Geom::PathVector const &new_vect = shape->curve()->get_pathvector();
+ if(combined_pathvector.empty()){
+ new_combined_pathvector = new_vect * tfc;
+ }
+ else {
+ new_combined_pathvector = sp_pathvector_boolop(new_vect * tfc, combined_pathvector, bool_op_union , (FillRule) fill_oddEven, (FillRule) fill_oddEven);
+ }
+ }
+ return new_combined_pathvector;
+}
+
+unsigned int PrintEmf::fill(
+ Inkscape::Extension::Print * /*mod*/,
+ Geom::PathVector const &pathv, Geom::Affine const & /*transform*/, SPStyle const *style,
+ Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/)
+{
+ char *rec;
+ using Geom::X;
+ using Geom::Y;
+ Geom::Affine tf = m_tr_stack.top();
+
+ do_clip_if_present(style); // If clipping is needed set it up
+
+ use_fill = true;
+ use_stroke = false;
+
+ fill_transform = tf;
+
+ int brush_stat = create_brush(style, nullptr);
+
+ /* native linear gradients are only used if the object is a rectangle AND the gradient is parallel to the sides of the object */
+ bool is_Rect = false;
+ double angle;
+ int rectDir=0;
+ Geom::Path pathRect;
+ if(FixPPTLinGrad && brush_stat && gv.mode == DRAW_LINEAR_GRADIENT){
+ Geom::PathVector pvr = pathv * fill_transform;
+ pathRect = pathv_to_rect(pvr, &is_Rect, &angle);
+ if(is_Rect){
+ /* Gradientfill records can only be used if the gradient is parallel to the sides of the rectangle.
+ That must be checked here so that we can fall back to another form of gradient fill if it is not
+ the case. */
+ rectDir = vector_rect_alignment(angle, (gv.p2 - gv.p1) * fill_transform);
+ if(!rectDir)is_Rect = false;
+ }
+ if(!is_Rect && !FixPPTGrad2Polys)brush_stat=0; // fall all the way back to a solid fill
+ }
+
+ if (brush_stat) { // only happens if the style is a gradient
+ /*
+ Handle gradients. Uses modified livarot as 2geom boolops is currently broken.
+ Can handle gradients with multiple stops.
+
+ The overlap is needed to avoid antialiasing artifacts when edges are not strictly aligned on pixel boundaries.
+ There is an inevitable loss of accuracy saving through an EMF file because of the integer coordinate system.
+ Keep the overlap quite large so that loss of accuracy does not remove an overlap.
+ */
+ destroy_pen(); //this sets the NULL_PEN, otherwise gradient slices may display with boundaries, see longer explanation below
+ Geom::Path cutter;
+ float rgb[3];
+ U_COLORREF wc, c1, c2;
+ FillRule frb = SPWR_to_LVFR((SPWindRule) style->fill_rule.computed);
+ double doff, doff_base, doff_range;
+ double divisions = 128.0;
+ int nstops;
+ int istop = 1;
+ float opa; // opacity at stop
+
+ SPRadialGradient *tg = (SPRadialGradient *)(gv.grad); // linear/radial are the same here
+ nstops = tg->vector.stops.size();
+ tg->vector.stops[0].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[0].opacity; // first stop
+ c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+ tg->vector.stops[nstops - 1].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[nstops - 1].opacity; // last stop
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+
+ doff = 0.0;
+ doff_base = 0.0;
+ doff_range = tg->vector.stops[1].offset; // next or last stop
+
+ if (gv.mode == DRAW_RADIAL_GRADIENT) {
+ Geom::Point xv = gv.p2 - gv.p1; // X' vector
+ Geom::Point yv = gv.p3 - gv.p1; // Y' vector
+ Geom::Point xuv = Geom::unit_vector(xv); // X' unit vector
+ double rx = hypot(xv[X], xv[Y]);
+ double ry = hypot(yv[X], yv[Y]);
+ double range = fmax(rx, ry); // length along the gradient
+ double step = range / divisions; // adequate approximation for gradient
+ double overlap = step / 4.0; // overlap slices slightly
+ double start;
+ double stop;
+ Geom::PathVector pathvc, pathvr;
+
+ /* radial gradient might stop part way through the shape, fill with outer color from there to "infinity".
+ Do this first so that outer colored ring will overlay it.
+ */
+ pathvc = center_elliptical_hole_as_SVG_PathV(gv.p1, rx * (1.0 - overlap / range), ry * (1.0 - overlap / range), asin(xuv[Y]));
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_oddEven, frb);
+ wc = weight_opacity(c2);
+ (void) create_brush(style, &wc);
+ print_pathv(pathvr, fill_transform);
+
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+
+ for (start = 0.0; start < range; start += step, doff += 1. / divisions) {
+ stop = start + step + overlap;
+ if (stop > range) {
+ stop = range;
+ }
+ wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base));
+ (void) create_brush(style, &wc);
+
+ pathvc = center_elliptical_ring_as_SVG_PathV(gv.p1, rx * start / range, ry * start / range, rx * stop / range, ry * stop / range, asin(xuv[Y]));
+
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform); // show the intersection
+
+ if (doff >= doff_range) {
+ istop++;
+ if (istop >= nstops) {
+ istop = nstops - 1;
+ continue; // could happen on a rounding error
+ }
+ doff_base = doff_range;
+ doff_range = tg->vector.stops[istop].offset; // next or last stop
+ c1 = c2;
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+ }
+ }
+ } else if (gv.mode == DRAW_LINEAR_GRADIENT) {
+ if(is_Rect){
+ int gMode;
+ Geom::Point ul, ur, lr;
+ Geom::Point outUL, outLR; // UL,LR corners of a stop rectangle, in OUTPUT coordinates
+ U_TRIVERTEX ut[2];
+ U_GRADIENT4 ug4;
+ U_RECTL rcb;
+ U_XFORM tmpTransform;
+ double wRect, hRect;
+
+ /* coordinates: upper left, upper right, and lower right corners of the rectangle.
+ inkscape transform already applied, but needs to be scaled to EMF coordinates. */
+ ul = get_pathrect_corner(pathRect, angle, 0) * PX2WORLD;
+ ur = get_pathrect_corner(pathRect, angle, 1) * PX2WORLD;
+ lr = get_pathrect_corner(pathRect, angle, 2) * PX2WORLD;
+ wRect = Geom::distance(ul,ur);
+ hRect = Geom::distance(ur,lr);
+
+ /* The basic rectangle for all of these is placed with its UL corner at 0,0 with a size wRect,hRect.
+ Apply a world transform to place/scale it into the appropriate position on the drawing.
+ Actual gradientfill records are either this entire rectangle or slices of it as defined by the stops.
+ This rectangle has already been transformed by tf (whatever rotation/scale) Inkscape had applied to it.
+ */
+
+ Geom::Affine tf2 = Geom::Rotate(-angle); // the rectangle may be drawn skewed to the coordinate system
+ tmpTransform.eM11 = tf2[0];
+ tmpTransform.eM12 = tf2[1];
+ tmpTransform.eM21 = tf2[2];
+ tmpTransform.eM22 = tf2[3];
+ tmpTransform.eDx = round((ul)[Geom::X]); // use explicit round for better stability
+ tmpTransform.eDy = round((ul)[Geom::Y]);
+
+ rec = U_EMRSAVEDC_set();
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at U_EMRSAVEDC_set");
+ }
+
+ rec = U_EMRMODIFYWORLDTRANSFORM_set(tmpTransform, U_MWT_LEFTMULTIPLY);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at EMRMODIFYWORLDTRANSFORM");
+ }
+
+ for(;istop<nstops;istop++){
+ doff_range = tg->vector.stops[istop].offset; // next or last stop
+ if(rectDir == 1 || rectDir == 2){
+ outUL = Geom::Point(doff_base *wRect, 0 );
+ outLR = Geom::Point(doff_range*wRect, hRect);
+ gMode = U_GRADIENT_FILL_RECT_H;
+ }
+ else {
+ outUL = Geom::Point(0, doff_base *hRect);
+ outLR = Geom::Point(wRect,doff_range*hRect);
+ gMode = U_GRADIENT_FILL_RECT_V;
+ }
+
+ doff_base = doff_range;
+ rcb.left = round(outUL[X]); // use explicit round for better stability
+ rcb.top = round(outUL[Y]);
+ rcb.right = round(outLR[X]);
+ rcb.bottom = round(outLR[Y]);
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+
+ if(rectDir == 2 || rectDir == 4){ // gradient is reversed, so swap colors
+ ut[0] = make_trivertex(outUL, c2);
+ ut[1] = make_trivertex(outLR, c1);
+ }
+ else {
+ ut[0] = make_trivertex(outUL, c1);
+ ut[1] = make_trivertex(outLR, c2);
+ }
+ c1 = c2; // for next stop
+ ug4.UpperLeft = 0;
+ ug4.LowerRight= 1;
+ rec = U_EMRGRADIENTFILL_set(rcb, 2, 1, gMode, ut, (uint32_t *) &ug4 );
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::fill at U_EMRGRADIENTFILL_set");
+ }
+ }
+
+ rec = U_EMRRESTOREDC_set(-1);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::fill at U_EMRRESTOREDC_set");
+ }
+ }
+ else {
+ Geom::Point uv = Geom::unit_vector(gv.p2 - gv.p1); // unit vector
+ Geom::Point puv = uv.cw(); // perp. to unit vector
+ double range = Geom::distance(gv.p1, gv.p2); // length along the gradient
+ double step = range / divisions; // adequate approximation for gradient
+ double overlap = step / 4.0; // overlap slices slightly
+ double start;
+ double stop;
+ Geom::PathVector pathvc, pathvr;
+
+ /* before lower end of gradient, overlap first slice position */
+ wc = weight_opacity(c1);
+ (void) create_brush(style, &wc);
+ pathvc = rect_cutter(gv.p1, uv * (overlap), uv * (-50000.0), puv * 50000.0);
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform);
+
+ /* after high end of gradient, overlap last slice position */
+ wc = weight_opacity(c2);
+ (void) create_brush(style, &wc);
+ pathvc = rect_cutter(gv.p2, uv * (-overlap), uv * (50000.0), puv * 50000.0);
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform);
+
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+
+ for (start = 0.0; start < range; start += step, doff += 1. / divisions) {
+ stop = start + step + overlap;
+ if (stop > range) {
+ stop = range;
+ }
+ pathvc = rect_cutter(gv.p1, uv * start, uv * stop, puv * 50000.0);
+
+ wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base));
+ (void) create_brush(style, &wc);
+ Geom::PathVector pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform); // show the intersection
+
+ if (doff >= doff_range) {
+ istop++;
+ if (istop >= nstops) {
+ istop = nstops - 1;
+ continue; // could happen on a rounding error
+ }
+ doff_base = doff_range;
+ doff_range = tg->vector.stops[istop].offset; // next or last stop
+ c1 = c2;
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+ }
+ }
+ }
+ } else {
+ g_error("Fatal programming error in PrintEmf::fill, invalid gradient type detected");
+ }
+ use_fill = false; // gradients handled, be sure stroke does not use stroke and fill
+ } else {
+ /*
+ Inkscape was not calling create_pen for objects with no border.
+ This was because it never called stroke() (next method).
+ PPT, and presumably others, pick whatever they want for the border if it is not specified, so no border can
+ become a visible border.
+ To avoid this force the pen to NULL_PEN if we can determine that no pen will be needed after the fill.
+ */
+ if (style->stroke.noneSet || style->stroke_width.computed == 0.0) {
+ destroy_pen(); //this sets the NULL_PEN
+ }
+
+ /* postpone fill in case stroke also required AND all stroke paths closed
+ Dashes converted to line segments will "open" a closed path.
+ */
+ bool all_closed = true;
+ for (const auto & pit : pathv) {
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ if (pit.end_default() != pit.end_closed()) {
+ all_closed = false;
+ }
+ }
+ }
+ if (
+ (style->stroke.isNone() || style->stroke.noneSet || style->stroke_width.computed == 0.0) ||
+ (!style->stroke_dasharray.values.empty() && FixPPTDashLine) ||
+ !all_closed
+ ) {
+ print_pathv(pathv, fill_transform); // do any fills. side effect: clears fill_pathv
+ use_fill = false;
+ }
+ }
+
+ return 0;
+}
+
+
+unsigned int PrintEmf::stroke(
+ Inkscape::Extension::Print * /*mod*/,
+ Geom::PathVector const &pathv, const Geom::Affine &/*transform*/, const SPStyle *style,
+ Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/)
+{
+
+ char *rec = nullptr;
+ Geom::Affine tf = m_tr_stack.top();
+ do_clip_if_present(style); // If clipping is needed set it up
+
+ use_stroke = true;
+ // use_fill was set in ::fill, if it is needed
+
+ if (create_pen(style, tf)) {
+ return 0;
+ }
+
+ if (!style->stroke_dasharray.values.empty() && FixPPTDashLine) {
+ // convert the path, gets its complete length, and then make a new path with parameter length instead of t
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw; // pathv-> sbasis
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw2; // sbasis using arc length parameter
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw3; // new (discontinuous) path, composed of dots/dashes
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > first_frag; // first fragment, will be appended at end
+ int n_dash = style->stroke_dasharray.values.size();
+ int i = 0; //dash index
+ double tlength; // length of tmp_pathpw
+ double slength = 0.0; // start of gragment
+ double elength; // end of gragment
+ for (const auto & i : pathv) {
+ tmp_pathpw.concat(i.toPwSb());
+ }
+ tlength = length(tmp_pathpw, 0.1);
+ tmp_pathpw2 = arc_length_parametrization(tmp_pathpw);
+
+ // go around the dash array repeatedly until the entire path is consumed (but not beyond).
+ while (slength < tlength) {
+ elength = slength + style->stroke_dasharray.values[i++].value;
+ if (elength > tlength) {
+ elength = tlength;
+ }
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > fragment(portion(tmp_pathpw2, slength, elength));
+ if (slength) {
+ tmp_pathpw3.concat(fragment);
+ } else {
+ first_frag = fragment;
+ }
+ slength = elength;
+ slength += style->stroke_dasharray.values[i++].value; // the gap
+ if (i >= n_dash) {
+ i = 0;
+ }
+ }
+ tmp_pathpw3.concat(first_frag); // may merge line around start point
+ Geom::PathVector out_pathv = Geom::path_from_piecewise(tmp_pathpw3, 0.01);
+ print_pathv(out_pathv, tf);
+ } else {
+ print_pathv(pathv, tf);
+ }
+
+ use_stroke = false;
+ use_fill = false;
+
+ if (usebk) { // OPAQUE was set, revert to TRANSPARENT
+ usebk = false;
+ rec = U_EMRSETBKMODE_set(U_TRANSPARENT);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::stroke at U_EMRSETBKMODE_set");
+ }
+ }
+
+ return 0;
+}
+
+
+// Draws simple_shapes, those with closed EMR_* primitives, like polygons, rectangles and ellipses.
+// These use whatever the current pen/brush are and need not be followed by a FILLPATH or STROKEPATH.
+// For other paths it sets a few flags and returns.
+bool PrintEmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Affine &transform)
+{
+
+ Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv * transform);
+
+ int nodes = 0;
+ int moves = 0;
+ int lines = 0;
+ int curves = 0;
+ char *rec = nullptr;
+
+ for (auto & pit : pv) {
+ moves++;
+ nodes++;
+
+ for (Geom::Path::iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ nodes++;
+
+ if (is_straight_curve(*cit)) {
+ lines++;
+ } else if (dynamic_cast<Geom::CubicBezier const *>(&*cit)) {
+ curves++;
+ }
+ }
+ }
+
+ if (!nodes) {
+ return false;
+ }
+
+ U_POINT *lpPoints = new U_POINT[moves + lines + curves * 3];
+ int i = 0;
+
+ /**
+ * For all Subpaths in the <path>
+ */
+ for (auto & pit : pv) {
+ using Geom::X;
+ using Geom::Y;
+
+ Geom::Point p0 = pit.initialPoint();
+
+ p0[X] = (p0[X] * PX2WORLD);
+ p0[Y] = (p0[Y] * PX2WORLD);
+
+ int32_t const x0 = (int32_t) round(p0[X]);
+ int32_t const y0 = (int32_t) round(p0[Y]);
+
+ lpPoints[i].x = x0;
+ lpPoints[i].y = y0;
+ i = i + 1;
+
+ /**
+ * For all segments in the subpath
+ */
+ for (Geom::Path::iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ if (is_straight_curve(*cit)) {
+ //Geom::Point p0 = cit->initialPoint();
+ Geom::Point p1 = cit->finalPoint();
+
+ //p0[X] = (p0[X] * PX2WORLD);
+ p1[X] = (p1[X] * PX2WORLD);
+ //p0[Y] = (p0[Y] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+
+ //int32_t const x0 = (int32_t) round(p0[X]);
+ //int32_t const y0 = (int32_t) round(p0[Y]);
+ int32_t const x1 = (int32_t) round(p1[X]);
+ int32_t const y1 = (int32_t) round(p1[Y]);
+
+ lpPoints[i].x = x1;
+ lpPoints[i].y = y1;
+ i = i + 1;
+ } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) {
+ std::vector<Geom::Point> points = cubic->controlPoints();
+ //Geom::Point p0 = points[0];
+ Geom::Point p1 = points[1];
+ Geom::Point p2 = points[2];
+ Geom::Point p3 = points[3];
+
+ //p0[X] = (p0[X] * PX2WORLD);
+ p1[X] = (p1[X] * PX2WORLD);
+ p2[X] = (p2[X] * PX2WORLD);
+ p3[X] = (p3[X] * PX2WORLD);
+ //p0[Y] = (p0[Y] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+ p2[Y] = (p2[Y] * PX2WORLD);
+ p3[Y] = (p3[Y] * PX2WORLD);
+
+ //int32_t const x0 = (int32_t) round(p0[X]);
+ //int32_t const y0 = (int32_t) round(p0[Y]);
+ int32_t const x1 = (int32_t) round(p1[X]);
+ int32_t const y1 = (int32_t) round(p1[Y]);
+ int32_t const x2 = (int32_t) round(p2[X]);
+ int32_t const y2 = (int32_t) round(p2[Y]);
+ int32_t const x3 = (int32_t) round(p3[X]);
+ int32_t const y3 = (int32_t) round(p3[Y]);
+
+ lpPoints[i].x = x1;
+ lpPoints[i].y = y1;
+ lpPoints[i + 1].x = x2;
+ lpPoints[i + 1].y = y2;
+ lpPoints[i + 2].x = x3;
+ lpPoints[i + 2].y = y3;
+ i = i + 3;
+ }
+ }
+ }
+
+ bool done = false;
+ bool closed = (lpPoints[0].x == lpPoints[i - 1].x) && (lpPoints[0].y == lpPoints[i - 1].y);
+ bool polygon = false;
+ bool rectangle = false;
+ bool ellipse = false;
+
+ if (moves == 1 && moves + lines == nodes && closed) {
+ polygon = true;
+ // if (nodes==5) { // disable due to LP Bug 407394
+ // if (lpPoints[0].x == lpPoints[3].x && lpPoints[1].x == lpPoints[2].x &&
+ // lpPoints[0].y == lpPoints[1].y && lpPoints[2].y == lpPoints[3].y)
+ // {
+ // rectangle = true;
+ // }
+ // }
+ } else if (moves == 1 && nodes == 5 && moves + curves == nodes && closed) {
+ // if (lpPoints[0].x == lpPoints[1].x && lpPoints[1].x == lpPoints[11].x &&
+ // lpPoints[5].x == lpPoints[6].x && lpPoints[6].x == lpPoints[7].x &&
+ // lpPoints[2].x == lpPoints[10].x && lpPoints[3].x == lpPoints[9].x && lpPoints[4].x == lpPoints[8].x &&
+ // lpPoints[2].y == lpPoints[3].y && lpPoints[3].y == lpPoints[4].y &&
+ // lpPoints[8].y == lpPoints[9].y && lpPoints[9].y == lpPoints[10].y &&
+ // lpPoints[5].y == lpPoints[1].y && lpPoints[6].y == lpPoints[0].y && lpPoints[7].y == lpPoints[11].y)
+ // { // disable due to LP Bug 407394
+ // ellipse = true;
+ // }
+ }
+
+ if (polygon || ellipse) {
+
+ if (use_fill && !use_stroke) { // only fill
+ rec = selectobject_set(U_NULL_PEN, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set pen");
+ }
+ } else if (!use_fill && use_stroke) { // only stroke
+ rec = selectobject_set(U_NULL_BRUSH, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set brush");
+ }
+ }
+
+ if (polygon) {
+ if (rectangle) {
+ U_RECTL rcl = rectl_set((U_POINTL) {
+ lpPoints[0].x, lpPoints[0].y
+ }, (U_POINTL) {
+ lpPoints[2].x, lpPoints[2].y
+ });
+ rec = U_EMRRECTANGLE_set(rcl);
+ } else {
+ rec = U_EMRPOLYGON_set(U_RCL_DEF, nodes, lpPoints);
+ }
+ } else if (ellipse) {
+ U_RECTL rcl = rectl_set((U_POINTL) {
+ lpPoints[6].x, lpPoints[3].y
+ }, (U_POINTL) {
+ lpPoints[0].x, lpPoints[9].y
+ });
+ rec = U_EMRELLIPSE_set(rcl);
+ }
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_simple_shape at retangle/ellipse/polygon");
+ }
+
+ done = true;
+
+ // replace the handle we moved above, assuming there was something set already
+ if (use_fill && !use_stroke && hpen) { // only fill
+ rec = selectobject_set(hpen, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set pen");
+ }
+ } else if (!use_fill && use_stroke && hbrush) { // only stroke
+ rec = selectobject_set(hbrush, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_simple_shape at selectobject_set brush");
+ }
+ }
+ }
+
+ delete[] lpPoints;
+
+ return done;
+}
+
+/** Some parts based on win32.cpp by Lauris Kaplinski <lauris@kaplinski.com>. Was a part of Inkscape
+ in the past (or will be in the future?) Not in current trunk. (4/19/2012)
+
+ Limitations of this code:
+ 1. Transparency is lost on export. (Apparently a limitation of the EMF format.)
+ 2. Probably messes up if row stride != w*4
+ 3. There is still a small memory leak somewhere, possibly in a pixbuf created in a routine
+ that calls this one and passes px, but never removes the rest of the pixbuf. The first time
+ this is called it leaked 5M (in one test) and each subsequent call leaked around 200K more.
+ If this routine is reduced to
+ if(1)return(0);
+ and called for a single 1280 x 1024 image then the program leaks 11M per call, or roughly the
+ size of two bitmaps.
+*/
+
+unsigned int PrintEmf::image(
+ Inkscape::Extension::Print * /* module */, /** not used */
+ unsigned char *rgba_px, /** array of pixel values, Gdk::Pixbuf bitmap format */
+ unsigned int w, /** width of bitmap */
+ unsigned int h, /** height of bitmap */
+ unsigned int rs, /** row stride (normally w*4) */
+ Geom::Affine const &tf_rect, /** affine transform only used for defining location and size of rect, for all other transforms, use the one from m_tr_stack */
+ SPStyle const *style) /** provides indirect link to image object */
+{
+ double x1, y1, dw, dh;
+ char *rec = nullptr;
+ Geom::Affine tf = m_tr_stack.top();
+
+ do_clip_if_present(style); // If clipping is needed set it up
+
+ rec = U_EMRSETSTRETCHBLTMODE_set(U_COLORONCOLOR);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at EMRHEADER");
+ }
+
+ x1 = tf_rect[4];
+ y1 = tf_rect[5];
+ dw = ((double) w) * tf_rect[0];
+ dh = ((double) h) * tf_rect[3];
+ Geom::Point pLL(x1, y1);
+ Geom::Point pLL2 = pLL * tf; //location of LL corner in Inkscape coordinates
+
+ char *px;
+ uint32_t cbPx;
+ uint32_t colortype;
+ PU_RGBQUAD ct;
+ int numCt;
+ U_BITMAPINFOHEADER Bmih;
+ PU_BITMAPINFO Bmi;
+ colortype = U_BCBM_COLOR32;
+ (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, (char *) rgba_px, w, h, w * 4, colortype, 0, 1);
+ Bmih = bitmapinfoheader_set(w, h, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0);
+ Bmi = bitmapinfo_set(Bmih, ct);
+
+ U_POINTL Dest = pointl_set(round(pLL2[Geom::X] * PX2WORLD), round(pLL2[Geom::Y] * PX2WORLD));
+ U_POINTL cDest = pointl_set(round(dw * PX2WORLD), round(dh * PX2WORLD));
+ U_POINTL Src = pointl_set(0, 0);
+ U_POINTL cSrc = pointl_set(w, h);
+ /* map the integer Dest coordinates back into pLL2, so that the rounded part does not destabilize the transform offset below */
+ pLL2[Geom::X] = Dest.x;
+ pLL2[Geom::Y] = Dest.y;
+ pLL2 /= PX2WORLD;
+ if (!FixImageRot) { /* Rotate images - some programs cannot read them in correctly if they are rotated */
+ tf[4] = tf[5] = 0.0; // get rid of the offset in the transform
+ Geom::Point pLL2prime = pLL2 * tf;
+ U_XFORM tmpTransform;
+ tmpTransform.eM11 = tf[0];
+ tmpTransform.eM12 = tf[1];
+ tmpTransform.eM21 = tf[2];
+ tmpTransform.eM22 = tf[3];
+ tmpTransform.eDx = (pLL2[Geom::X] - pLL2prime[Geom::X]) * PX2WORLD;
+ tmpTransform.eDy = (pLL2[Geom::Y] - pLL2prime[Geom::Y]) * PX2WORLD;
+
+ rec = U_EMRSAVEDC_set();
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at U_EMRSAVEDC_set");
+ }
+
+ rec = U_EMRMODIFYWORLDTRANSFORM_set(tmpTransform, U_MWT_LEFTMULTIPLY);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at EMRMODIFYWORLDTRANSFORM");
+ }
+ }
+ rec = U_EMRSTRETCHDIBITS_set(
+ U_RCL_DEF, //! Bounding rectangle in device units
+ Dest, //! Destination UL corner in logical units
+ cDest, //! Destination W & H in logical units
+ Src, //! Source UL corner in logical units
+ cSrc, //! Source W & H in logical units
+ U_DIB_RGB_COLORS, //! DIBColors Enumeration
+ U_SRCCOPY, //! RasterOPeration Enumeration
+ Bmi, //! (Optional) bitmapbuffer (U_BITMAPINFO section)
+ h * rs, //! size in bytes of px
+ px //! (Optional) bitmapbuffer (U_BITMAPINFO section)
+ );
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at U_EMRSTRETCHDIBITS_set");
+ }
+ free(px);
+ free(Bmi);
+ if (numCt) {
+ free(ct);
+ }
+
+ if (!FixImageRot) {
+ rec = U_EMRRESTOREDC_set(-1);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::image at U_EMRRESTOREDC_set");
+ }
+ }
+
+ return 0;
+}
+
+unsigned int PrintEmf::draw_pathv_to_EMF(Geom::PathVector const &pathv, const Geom::Affine &transform) {
+ char *rec;
+
+ /* inkscape to EMF scaling is done below, but NOT the rotation/translation transform,
+ that is handled by the EMF MODIFYWORLDTRANSFORM record
+ */
+
+ Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv * transform);
+
+ rec = U_EMRBEGINPATH_set();
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRBEGINPATH_set");
+ }
+
+ /**
+ * For all Subpaths in the <path>
+ */
+ for (const auto & pit : pv) {
+ using Geom::X;
+ using Geom::Y;
+
+
+ Geom::Point p0 = pit.initialPoint();
+
+ p0[X] = (p0[X] * PX2WORLD);
+ p0[Y] = (p0[Y] * PX2WORLD);
+
+ U_POINTL ptl = pointl_set((int32_t) round(p0[X]), (int32_t) round(p0[Y]));
+ rec = U_EMRMOVETOEX_set(ptl);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRMOVETOEX_set");
+ }
+
+ /**
+ * For all segments in the subpath
+ */
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ if (is_straight_curve(*cit)) {
+ //Geom::Point p0 = cit->initialPoint();
+ Geom::Point p1 = cit->finalPoint();
+
+ //p0[X] = (p0[X] * PX2WORLD);
+ p1[X] = (p1[X] * PX2WORLD);
+ //p0[Y] = (p0[Y] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+
+ //int32_t const x0 = (int32_t) round(p0[X]);
+ //int32_t const y0 = (int32_t) round(p0[Y]);
+
+ ptl = pointl_set((int32_t) round(p1[X]), (int32_t) round(p1[Y]));
+ rec = U_EMRLINETO_set(ptl);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRLINETO_set");
+ }
+ } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) {
+ std::vector<Geom::Point> points = cubic->controlPoints();
+ //Geom::Point p0 = points[0];
+ Geom::Point p1 = points[1];
+ Geom::Point p2 = points[2];
+ Geom::Point p3 = points[3];
+
+ //p0[X] = (p0[X] * PX2WORLD);
+ p1[X] = (p1[X] * PX2WORLD);
+ p2[X] = (p2[X] * PX2WORLD);
+ p3[X] = (p3[X] * PX2WORLD);
+ //p0[Y] = (p0[Y] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+ p2[Y] = (p2[Y] * PX2WORLD);
+ p3[Y] = (p3[Y] * PX2WORLD);
+
+ //int32_t const x0 = (int32_t) round(p0[X]);
+ //int32_t const y0 = (int32_t) round(p0[Y]);
+ int32_t const x1 = (int32_t) round(p1[X]);
+ int32_t const y1 = (int32_t) round(p1[Y]);
+ int32_t const x2 = (int32_t) round(p2[X]);
+ int32_t const y2 = (int32_t) round(p2[Y]);
+ int32_t const x3 = (int32_t) round(p3[X]);
+ int32_t const y3 = (int32_t) round(p3[Y]);
+
+ U_POINTL pt[3];
+ pt[0].x = x1;
+ pt[0].y = y1;
+ pt[1].x = x2;
+ pt[1].y = y2;
+ pt[2].x = x3;
+ pt[2].y = y3;
+
+ rec = U_EMRPOLYBEZIERTO_set(U_RCL_DEF, 3, pt);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRPOLYBEZIERTO_set");
+ }
+ } else {
+ g_warning("logical error, because pathv_to_linear_and_cubic_beziers was used");
+ }
+ }
+
+ if (pit.end_default() == pit.end_closed()) { // there may be multiples of this on a single path
+ rec = U_EMRCLOSEFIGURE_set();
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRCLOSEFIGURE_set");
+ }
+ }
+
+ }
+
+ rec = U_EMRENDPATH_set(); // there may be only be one of these on a single path
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::print_pathv at U_EMRENDPATH_set");
+ }
+ return(0);
+}
+
+// may also be called with a simple_shape or an empty path, whereupon it just returns without doing anything
+unsigned int PrintEmf::print_pathv(Geom::PathVector const &pathv, const Geom::Affine &transform)
+{
+ Geom::Affine tf = transform;
+ char *rec = nullptr;
+
+ simple_shape = print_simple_shape(pathv, tf);
+ if (simple_shape || pathv.empty()) {
+ if (use_fill) {
+ destroy_brush(); // these must be cleared even if nothing is drawn or hbrush,hpen fill up
+ }
+ if (use_stroke) {
+ destroy_pen();
+ }
+ return TRUE;
+ }
+
+ (void) draw_pathv_to_EMF(pathv, tf);
+
+ // explicit FILL/STROKE commands are needed for each sub section of the path
+ if (use_fill && !use_stroke) {
+ rec = U_EMRFILLPATH_set(U_RCL_DEF);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::fill at U_EMRFILLPATH_set");
+ }
+ } else if (use_fill && use_stroke) {
+ rec = U_EMRSTROKEANDFILLPATH_set(U_RCL_DEF);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::stroke at U_EMRSTROKEANDFILLPATH_set");
+ }
+ } else if (!use_fill && use_stroke) {
+ rec = U_EMRSTROKEPATH_set(U_RCL_DEF);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::stroke at U_EMRSTROKEPATH_set");
+ }
+ }
+
+ // clean out brush and pen, but only after all parts of the draw complete
+ if (use_fill) {
+ destroy_brush();
+ }
+ if (use_stroke) {
+ destroy_pen();
+ }
+
+ return TRUE;
+}
+
+
+unsigned int PrintEmf::text(Inkscape::Extension::Print * /*mod*/, char const *text, Geom::Point const &p,
+ SPStyle const *const style)
+{
+ if (!et || !text) {
+ return 0;
+ }
+
+ do_clip_if_present(style); // If clipping is needed set it up
+ char *rec = nullptr;
+ int ccount, newfont;
+ int fix90n = 0;
+ uint32_t hfont = 0;
+ Geom::Affine tf = m_tr_stack.top();
+ double rot = -1800.0 * std::atan2(tf[1], tf[0]) / M_PI; // 0.1 degree rotation, - sign for MM_TEXT
+ double rotb = -std::atan2(tf[1], tf[0]); // rotation for baseline offset for superscript/subscript, used below
+ double dx, dy;
+ double ky;
+
+ // the dx array is smuggled in like: text<nul>w1 w2 w3 ...wn<nul><nul>, where the widths are floats 7 characters wide, including the space
+ int ndx, rtl;
+ uint32_t *adx;
+ smuggle_adxkyrtl_out(text, &adx, &ky, &rtl, &ndx, PX2WORLD * std::min(tf.expansionX(), tf.expansionY())); // side effect: free() adx
+
+ uint32_t textalignment;
+ if (rtl > 0) {
+ textalignment = U_TA_BASELINE | U_TA_LEFT;
+ } else {
+ textalignment = U_TA_BASELINE | U_TA_RIGHT | U_TA_RTLREADING;
+ }
+ if (textalignment != htextalignment) {
+ htextalignment = textalignment;
+ rec = U_EMRSETTEXTALIGN_set(textalignment);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTALIGN_set");
+ }
+ }
+
+ char *text2 = strdup(text); // because U_Utf8ToUtf16le calls iconv which does not like a const char *
+ uint16_t *unicode_text = U_Utf8ToUtf16le(text2, 0, nullptr);
+ free(text2);
+ //translates Unicode to NonUnicode, if possible. If any translate, all will, and all to
+ //the same font, because of code in Layout::print
+ UnicodeToNon(unicode_text, &ccount, &newfont);
+
+ //PPT gets funky with text within +-1 degree of a multiple of 90, but only for SOME fonts.Snap those to the central value
+ //Some funky ones: Arial, Times New Roman
+ //Some not funky ones: Symbol and Verdana.
+ //Without a huge table we cannot catch them all, so just the most common problem ones.
+ FontfixParams params;
+
+ if (FixPPTCharPos) {
+ switch (newfont) {
+ case CVTSYM:
+ _lookup_ppt_fontfix("Convert To Symbol", params);
+ break;
+ case CVTZDG:
+ _lookup_ppt_fontfix("Convert To Zapf Dingbats", params);
+ break;
+ case CVTWDG:
+ _lookup_ppt_fontfix("Convert To Wingdings", params);
+ break;
+ default: //also CVTNON
+ _lookup_ppt_fontfix(style->font_family.value(), params);
+ break;
+ }
+ if (params.f2 != 0 || params.f3 != 0) {
+ int irem = ((int) round(rot)) % 900 ;
+ if (irem <= 9 && irem >= -9) {
+ fix90n = 1; //assume vertical
+ rot = (double)(((int) round(rot)) - irem);
+ rotb = rot * M_PI / 1800.0;
+ if (std::abs(rot) == 900.0) {
+ fix90n = 2;
+ }
+ }
+ }
+ }
+
+ /* Note that text font sizes are stored into the EMF as fairly small integers and that limits their precision.
+ The EMF output files produced here have been designed so that the integer valued pt sizes
+ land right on an integer value in the EMF file, so those are exact. However, something like 18.1 pt will be
+ somewhat off, so that when it is read back in it becomes 18.11 pt. (For instance.)
+ */
+ int textheight = round(-style->font_size.computed * PX2WORLD * std::min(tf.expansionX(), tf.expansionY()));
+
+ if (!hfont) {
+ // Get font face name. Use changed font name if unicode mapped to one
+ // of the special fonts.
+ uint16_t *wfacename;
+ if (!newfont) {
+ wfacename = U_Utf8ToUtf16le(style->font_family.value(), 0, nullptr);
+ } else {
+ wfacename = U_Utf8ToUtf16le(FontName(newfont), 0, nullptr);
+ }
+
+ // Scale the text to the minimum stretch. (It tends to stay within bounding rectangles even if
+ // it was streteched asymmetrically.) Few applications support text from EMF which is scaled
+ // differently by height/width, so leave lfWidth alone.
+
+ U_LOGFONT lf = logfont_set(
+ textheight,
+ 0,
+ round(rot),
+ round(rot),
+ _translate_weight(style->font_weight.computed),
+ (style->font_style.computed == SP_CSS_FONT_STYLE_ITALIC),
+ style->text_decoration_line.underline,
+ style->text_decoration_line.line_through,
+ U_DEFAULT_CHARSET,
+ U_OUT_DEFAULT_PRECIS,
+ U_CLIP_DEFAULT_PRECIS,
+ U_DEFAULT_QUALITY,
+ U_DEFAULT_PITCH | U_FF_DONTCARE,
+ wfacename);
+ free(wfacename);
+
+ rec = extcreatefontindirectw_set(&hfont, eht, (char *) &lf, nullptr);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::text at extcreatefontindirectw_set");
+ }
+ }
+
+ rec = selectobject_set(hfont, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::text at selectobject_set");
+ }
+
+ float rgb[3];
+ style->fill.value.color.get_rgb_floatv(rgb);
+ // only change the text color when it needs to be changed
+ if (memcmp(htextcolor_rgb, rgb, 3 * sizeof(float))) {
+ memcpy(htextcolor_rgb, rgb, 3 * sizeof(float));
+ rec = U_EMRSETTEXTCOLOR_set(U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]));
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::text at U_EMRSETTEXTCOLOR_set");
+ }
+ }
+
+ Geom::Point p2 = p * tf;
+
+ //Handle super/subscripts and vertical kerning
+ /* Previously used this, but vertical kerning was not supported
+ p2[Geom::X] -= style->baseline_shift.computed * std::sin( rotb );
+ p2[Geom::Y] -= style->baseline_shift.computed * std::cos( rotb );
+ */
+ p2[Geom::X] += ky * std::sin(rotb);
+ p2[Geom::Y] += ky * std::cos(rotb);
+
+ //Conditionally handle compensation for PPT EMF import bug (affects PPT 2003-2010, at least)
+ if (FixPPTCharPos) {
+ if (fix90n == 1) { //vertical
+ dx = 0.0;
+ dy = params.f3 * style->font_size.computed * std::cos(rotb);
+ } else if (fix90n == 2) { //horizontal
+ dx = params.f2 * style->font_size.computed * std::sin(rotb);
+ dy = 0.0;
+ } else {
+ dx = params.f1 * style->font_size.computed * std::sin(rotb);
+ dy = params.f1 * style->font_size.computed * std::cos(rotb);
+ }
+ p2[Geom::X] += dx;
+ p2[Geom::Y] += dy;
+ }
+
+ p2[Geom::X] = (p2[Geom::X] * PX2WORLD);
+ p2[Geom::Y] = (p2[Geom::Y] * PX2WORLD);
+
+ int32_t const xpos = (int32_t) round(p2[Geom::X]);
+ int32_t const ypos = (int32_t) round(p2[Geom::Y]);
+
+
+ // The number of characters in the string is a bit fuzzy. ndx, the number of entries in adx is
+ // the number of VISIBLE characters, since some may combine from the UTF (8 originally,
+ // now 16) encoding. Conversely strlen() or wchar16len() would give the absolute number of
+ // encoding characters. Unclear if emrtext wants the former or the latter but for now assume the former.
+
+ // This is currently being smuggled in from caller as part of text, works
+ // MUCH better than the fallback hack below
+ // uint32_t *adx = dx_set(textheight, U_FW_NORMAL, slen); // dx is needed, this makes one up
+ char *rec2;
+ if (rtl > 0) {
+ rec2 = emrtext_set((U_POINTL) {
+ xpos, ypos
+ }, ndx, 2, unicode_text, U_ETO_NONE, U_RCL_DEF, adx);
+ } else { // RTL text, U_TA_RTLREADING should be enough, but set this one too just in case
+ rec2 = emrtext_set((U_POINTL) {
+ xpos, ypos
+ }, ndx, 2, unicode_text, U_ETO_RTLREADING, U_RCL_DEF, adx);
+ }
+ free(unicode_text);
+ free(adx);
+ rec = U_EMREXTTEXTOUTW_set(U_RCL_DEF, U_GM_COMPATIBLE, 1.0, 1.0, (PU_EMRTEXT)rec2);
+ free(rec2);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::text at U_EMREXTTEXTOUTW_set");
+ }
+
+ // Must deselect an object before deleting it. Put the default font (back) in.
+ rec = selectobject_set(U_DEVICE_DEFAULT_FONT, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::text at selectobject_set");
+ }
+
+ if (hfont) {
+ rec = deleteobject_set(&hfont, eht);
+ if (!rec || emf_append((PU_ENHMETARECORD)rec, et, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintEmf::text at deleteobject_set");
+ }
+ }
+
+ return 0;
+}
+
+void PrintEmf::init()
+{
+ /* EMF print */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>Enhanced Metafile Print</name>\n"
+ "<id>org.inkscape.print.emf</id>\n"
+ "<param gui-hidden=\"true\" name=\"destination\" type=\"string\"></param>\n"
+ "<param gui-hidden=\"true\" name=\"textToPath\" type=\"bool\">true</param>\n"
+ "<param gui-hidden=\"true\" name=\"pageBoundingBox\" type=\"bool\">true</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTCharPos\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTDashLine\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTGrad2Polys\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTLinGrad\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTPatternAsHatch\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixImageRot\" type=\"bool\">false</param>\n"
+ "<print/>\n"
+ "</inkscape-extension>", new PrintEmf());
+ // clang-format on
+
+ return;
+}
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/emf-print.h b/src/extension/internal/emf-print.h
new file mode 100644
index 0000000..9370ef2
--- /dev/null
+++ b/src/extension/internal/emf-print.h
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Enhanced Metafile printing - implementation
+ */
+/* Authors:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * David Mathog
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_EMF_PRINT_H
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_EMF_PRINT_H
+
+#include <3rdparty/libuemf/uemf.h>
+#include "extension/internal/metafile-print.h"
+
+class SPItem;
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class PrintEmf : public PrintMetafile
+{
+ uint32_t hbrush, hbrushOld, hpen;
+
+ unsigned int print_pathv (Geom::PathVector const &pathv, const Geom::Affine &transform);
+ bool print_simple_shape (Geom::PathVector const &pathv, const Geom::Affine &transform);
+
+public:
+ PrintEmf();
+
+ /* Print functions */
+ unsigned int setup (Inkscape::Extension::Print * module) override;
+
+ unsigned int begin (Inkscape::Extension::Print * module, SPDocument *doc) override;
+ unsigned int finish (Inkscape::Extension::Print * module) override;
+
+ /* Rendering methods */
+ unsigned int fill (Inkscape::Extension::Print *module,
+ Geom::PathVector const &pathv,
+ Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox) override;
+ unsigned int stroke (Inkscape::Extension::Print * module,
+ Geom::PathVector const &pathv,
+ Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox) override;
+ unsigned int image(Inkscape::Extension::Print *module,
+ unsigned char *px,
+ unsigned int w,
+ unsigned int h,
+ unsigned int rs,
+ Geom::Affine const &transform,
+ SPStyle const *style) override;
+ unsigned int text(Inkscape::Extension::Print *module, char const *text,
+ Geom::Point const &p, SPStyle const *style) override;
+
+ static void init ();
+protected:
+ static void smuggle_adxkyrtl_out(const char *string, uint32_t **adx, double *ky, int *rtl, int *ndx, float scale);
+
+ void do_clip_if_present(SPStyle const *style);
+ Geom::PathVector merge_PathVector_with_group(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform);
+ Geom::PathVector merge_PathVector_with_shape(Geom::PathVector const &combined_pathvector, SPItem const *item, const Geom::Affine &transform);
+ unsigned int draw_pathv_to_EMF(Geom::PathVector const &pathv, const Geom::Affine &transform);
+ Geom::Path pathv_to_simple_polygon(Geom::PathVector const &pathv, int *vertices);
+ Geom::Path pathv_to_rect(Geom::PathVector const &pathv, bool *is_rect, double *angle);
+ Geom::Point get_pathrect_corner(Geom::Path pathRect, double angle, int corner);
+ U_TRIVERTEX make_trivertex(Geom::Point Pt, U_COLORREF uc);
+ int vector_rect_alignment(double angle, Geom::Point vtest);
+ int create_brush(SPStyle const *style, PU_COLORREF fcolor) override;
+ void destroy_brush() override;
+ int create_pen(SPStyle const *style, const Geom::Affine &transform) override;
+ void destroy_pen() override;
+};
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+
+#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_EMF_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/filter/BUILD_YOUR_OWN b/src/extension/internal/filter/BUILD_YOUR_OWN
new file mode 100644
index 0000000..7598499
--- /dev/null
+++ b/src/extension/internal/filter/BUILD_YOUR_OWN
@@ -0,0 +1,2 @@
+This directory contains filter effects. They're designed to be simple.
+Very, very simple. Here is how to build your own.
diff --git a/src/extension/internal/filter/bevels.h b/src/extension/internal/filter/bevels.h
new file mode 100644
index 0000000..a8382ba
--- /dev/null
+++ b/src/extension/internal/filter/bevels.h
@@ -0,0 +1,289 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BEVELS_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BEVELS_H__
+/* Change the 'BEVELS' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Bevel filters
+ * Diffuse light
+ * Matte jelly
+ * Specular light
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Diffuse light filter.
+
+ Basic diffuse bevel to use for building textures
+
+ Filter's parameters:
+ * Smoothness (0.->10., default 6.) -> blur (stdDeviation)
+ * Elevation (0->360, default 25) -> feDistantLight (elevation)
+ * Azimuth (0->360, default 235) -> feDistantLight (azimuth)
+ * Lighting color (guint, default -1 [white]) -> diffuse (lighting-color)
+*/
+
+class DiffuseLight : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ DiffuseLight ( ) : Filter() { };
+ ~DiffuseLight ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Diffuse Light") "</name>\n"
+ "<id>org.inkscape.effect.filter.DiffuseLight</id>\n"
+ "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"10\">6</param>\n"
+ "<param name=\"elevation\" gui-text=\"" N_("Elevation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">25</param>\n"
+ "<param name=\"azimuth\" gui-text=\"" N_("Azimuth (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">235</param>\n"
+ "<param name=\"color\" gui-text=\"" N_("Lighting color") "\" type=\"color\">-1</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Bevels") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Basic diffuse bevel to use for building textures") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new DiffuseLight());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+DiffuseLight::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream smooth;
+ std::ostringstream elevation;
+ std::ostringstream azimuth;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream a;
+
+ smooth << ext->get_param_float("smooth");
+ elevation << ext->get_param_int("elevation");
+ azimuth << ext->get_param_int("azimuth");
+ guint32 color = ext->get_param_color("color");
+
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Diffuse Light\">\n"
+ "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feDiffuseLighting diffuseConstant=\"1\" surfaceScale=\"10\" lighting-color=\"rgb(%s,%s,%s)\" result=\"diffuse\">\n"
+ "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n"
+ "</feDiffuseLighting>\n"
+ "<feComposite in=\"diffuse\" in2=\"diffuse\" operator=\"arithmetic\" k1=\"1\" result=\"composite1\" />\n"
+ "<feComposite in=\"composite1\" in2=\"SourceGraphic\" k1=\"%s\" operator=\"arithmetic\" k3=\"1\" result=\"composite2\" />\n"
+ "</filter>\n", smooth.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str());
+
+ return _filter;
+}; /* DiffuseLight filter */
+
+/**
+ \brief Custom predefined Matte jelly filter.
+
+ Bulging, matte jelly covering
+
+ Filter's parameters:
+ * Smoothness (0.0->10., default 7.) -> blur (stdDeviation)
+ * Brightness (0.0->5., default .9) -> specular (specularConstant)
+ * Elevation (0->360, default 60) -> feDistantLight (elevation)
+ * Azimuth (0->360, default 225) -> feDistantLight (azimuth)
+ * Lighting color (guint, default -1 [white]) -> specular (lighting-color)
+*/
+
+class MatteJelly : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ MatteJelly ( ) : Filter() { };
+ ~MatteJelly ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Matte Jelly") "</name>\n"
+ "<id>org.inkscape.effect.filter.MatteJelly</id>\n"
+ "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"10.00\">7</param>\n"
+ "<param name=\"bright\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"5.00\">0.9</param>\n"
+ "<param name=\"elevation\" gui-text=\"" N_("Elevation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">60</param>\n"
+ "<param name=\"azimuth\" gui-text=\"" N_("Azimuth (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">225</param>\n"
+ "<param name=\"color\" gui-text=\"" N_("Lighting color") "\" type=\"color\">-1</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Bevels") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Bulging, matte jelly covering") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new MatteJelly());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+MatteJelly::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream smooth;
+ std::ostringstream bright;
+ std::ostringstream elevation;
+ std::ostringstream azimuth;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream a;
+
+ smooth << ext->get_param_float("smooth");
+ bright << ext->get_param_float("bright");
+ elevation << ext->get_param_int("elevation");
+ azimuth << ext->get_param_int("azimuth");
+ guint32 color = ext->get_param_color("color");
+
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Matte Jelly\">\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.85 0\" result=\"color\" in=\"SourceGraphic\" />\n"
+ "<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feSpecularLighting in=\"blur\" specularExponent=\"25\" specularConstant=\"%s\" surfaceScale=\"5\" lighting-color=\"rgb(%s,%s,%s)\" result=\"specular\">\n"
+ "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n"
+ "</feSpecularLighting>\n"
+ "<feComposite in=\"specular\" in2=\"SourceGraphic\" k3=\"1\" k2=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n"
+ "<feComposite in=\"composite1\" in2=\"color\" operator=\"atop\" result=\"composite2\" />\n"
+ "</filter>\n", smooth.str().c_str(), bright.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str());
+
+ return _filter;
+}; /* MatteJelly filter */
+
+/**
+ \brief Custom predefined Specular light filter.
+
+ Basic specular bevel to use for building textures
+
+ Filter's parameters:
+ * Smoothness (0.0->10., default 6.) -> blur (stdDeviation)
+ * Brightness (0.0->5., default 1.) -> specular (specularConstant)
+ * Elevation (0->360, default 45) -> feDistantLight (elevation)
+ * Azimuth (0->360, default 235) -> feDistantLight (azimuth)
+ * Lighting color (guint, default -1 [white]) -> specular (lighting-color)
+*/
+
+class SpecularLight : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ SpecularLight ( ) : Filter() { };
+ ~SpecularLight ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Specular Light") "</name>\n"
+ "<id>org.inkscape.effect.filter.SpecularLight</id>\n"
+ "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"10\">6</param>\n"
+ "<param name=\"bright\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"5\">1</param>\n"
+ "<param name=\"elevation\" gui-text=\"" N_("Elevation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">45</param>\n"
+ "<param name=\"azimuth\" gui-text=\"" N_("Azimuth (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">235</param>\n"
+ "<param name=\"color\" gui-text=\"" N_("Lighting color") "\" type=\"color\">-1</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Bevels") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Basic specular bevel to use for building textures") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new SpecularLight());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+SpecularLight::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream smooth;
+ std::ostringstream bright;
+ std::ostringstream elevation;
+ std::ostringstream azimuth;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream a;
+
+ smooth << ext->get_param_float("smooth");
+ bright << ext->get_param_float("bright");
+ elevation << ext->get_param_int("elevation");
+ azimuth << ext->get_param_int("azimuth");
+ guint32 color = ext->get_param_color("color");
+
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Specular Light\">\n"
+ "<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feSpecularLighting in=\"blur\" specularExponent=\"25\" specularConstant=\"%s\" surfaceScale=\"10\" lighting-color=\"rgb(%s,%s,%s)\" result=\"specular\">\n"
+ "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n"
+ "</feSpecularLighting>\n"
+ "<feComposite in=\"specular\" in2=\"SourceGraphic\" k3=\"1\" k2=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n"
+ "<feComposite in=\"composite1\" in2=\"SourceAlpha\" operator=\"in\" result=\"composite2\" />\n"
+ "</filter>\n", smooth.str().c_str(), bright.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), elevation.str().c_str(), azimuth.str().c_str(), a.str().c_str());
+
+ return _filter;
+}; /* SpecularLight filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'BEVELS' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BEVELS_H__ */
diff --git a/src/extension/internal/filter/blurs.h b/src/extension/internal/filter/blurs.h
new file mode 100644
index 0000000..85f99fd
--- /dev/null
+++ b/src/extension/internal/filter/blurs.h
@@ -0,0 +1,440 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BLURS_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BLURS_H__
+/* Change the 'BLURS' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Blur filters
+ * Blur
+ * Clean edges
+ * Cross blur
+ * Feather
+ * Out of focus
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Blur filter.
+
+ Simple horizontal and vertical blur
+
+ Filter's parameters:
+ * Horizontal blur (0.01->100., default 2) -> blur (stdDeviation)
+ * Vertical blur (0.01->100., default 2) -> blur (stdDeviation)
+ * Blur content only (boolean, default false) ->
+*/
+
+class Blur : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Blur ( ) : Filter() { };
+ ~Blur ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Blur") "</name>\n"
+ "<id>org.inkscape.effect.filter.Blur</id>\n"
+ "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">2</param>\n"
+ "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">2</param>\n"
+ "<param name=\"content\" gui-text=\"" N_("Blur content only") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Blurs") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Simple vertical and horizontal blur effect") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Blur());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Blur::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream bbox;
+ std::ostringstream hblur;
+ std::ostringstream vblur;
+ std::ostringstream content;
+
+ hblur << ext->get_param_float("hblur");
+ vblur << ext->get_param_float("vblur");
+
+ if (ext->get_param_bool("content")) {
+ bbox << "height=\"1\" width=\"1\" y=\"0\" x=\"0\"";
+ content << "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 50 0 \" result=\"colormatrix\" />\n"
+ << "<feComposite in=\"colormatrix\" in2=\"SourceGraphic\" operator=\"in\" />\n";
+ } else {
+ bbox << "" ;
+ content << "" ;
+ }
+
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" %s style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Blur\">\n"
+ "<feGaussianBlur stdDeviation=\"%s %s\" result=\"blur\" />\n"
+ "%s"
+ "</filter>\n", bbox.str().c_str(), hblur.str().c_str(), vblur.str().c_str(), content.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Blur filter */
+
+/**
+ \brief Custom predefined Clean edges filter.
+
+ Removes or decreases glows and jaggeries around objects edges after applying some filters
+
+ Filter's parameters:
+ * Strength (0.01->2., default 0.4) -> blur (stdDeviation)
+*/
+
+class CleanEdges : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ CleanEdges ( ) : Filter() { };
+ ~CleanEdges ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Clean Edges") "</name>\n"
+ "<id>org.inkscape.effect.filter.CleanEdges</id>\n"
+ "<param name=\"blur\" gui-text=\"" N_("Strength") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"2.00\">0.4</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Blurs") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Removes or decreases glows and jaggeries around objects edges after applying some filters") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new CleanEdges());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+CleanEdges::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream blur;
+
+ blur << ext->get_param_float("blur");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Clean Edges\">\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"in\" result=\"composite1\" />\n"
+ "<feComposite in=\"composite1\" in2=\"composite1\" k2=\"1\" operator=\"in\" result=\"composite2\" />\n"
+ "</filter>\n", blur.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* CleanEdges filter */
+
+/**
+ \brief Custom predefined Cross blur filter.
+
+ Combine vertical and horizontal blur
+
+ Filter's parameters:
+ * Brightness (0.->10., default 0) -> composite (k3)
+ * Fading (0.->1., default 0) -> composite (k4)
+ * Horizontal blur (0.01->20., default 5) -> blur (stdDeviation)
+ * Vertical blur (0.01->20., default 5) -> blur (stdDeviation)
+ * Blend mode (enum, default Darken) -> blend (mode)
+*/
+
+class CrossBlur : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ CrossBlur ( ) : Filter() { };
+ ~CrossBlur ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Cross Blur") "</name>\n"
+ "<id>org.inkscape.effect.filter.CrossBlur</id>\n"
+ "<param name=\"bright\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"10.00\">0</param>\n"
+ "<param name=\"fade\" gui-text=\"" N_("Fading") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.00\">0</param>\n"
+ "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">5</param>\n"
+ "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">5</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Blurs") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Combine vertical and horizontal blur") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new CrossBlur());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+CrossBlur::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream bright;
+ std::ostringstream fade;
+ std::ostringstream hblur;
+ std::ostringstream vblur;
+ std::ostringstream blend;
+
+ bright << ext->get_param_float("bright");
+ fade << ext->get_param_float("fade");
+ hblur << ext->get_param_float("hblur");
+ vblur << ext->get_param_float("vblur");
+ blend << ext->get_param_optiongroup("blend");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Cross Blur\">\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"colormatrix\" />\n"
+ "<feComposite in=\"SourceGraphic\" in2=\"colormatrix\" operator=\"arithmetic\" k2=\"1\" k3=\"%s\" k4=\"%s\" result=\"composite\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s 0.01\" result=\"blur1\" />\n"
+ "<feGaussianBlur in=\"composite\" stdDeviation=\"0.01 %s\" result=\"blur2\" />\n"
+ "<feBlend in=\"blur2\" in2=\"blur1\" mode=\"%s\" result=\"blend\" />\n"
+ "</filter>\n", bright.str().c_str(), fade.str().c_str(), hblur.str().c_str(), vblur.str().c_str(), blend.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Cross blur filter */
+
+/**
+ \brief Custom predefined Feather filter.
+
+ Blurred mask on the edge without altering the contents
+
+ Filter's parameters:
+ * Strength (0.01->100., default 5) -> blur (stdDeviation)
+*/
+
+class Feather : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Feather ( ) : Filter() { };
+ ~Feather ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Feather") "</name>\n"
+ "<id>org.inkscape.effect.filter.Feather</id>\n"
+ "<param name=\"blur\" gui-text=\"" N_("Strength") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">5</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Blurs") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Blurred mask on the edge without altering the contents") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Feather());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Feather::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream blur;
+
+ blur << ext->get_param_float("blur");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Feather\">\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"atop\" result=\"composite1\" />\n"
+ "<feComposite in2=\"composite1\" operator=\"in\" result=\"composite2\" />\n"
+ "<feComposite in2=\"composite2\" operator=\"in\" result=\"composite3\" />\n"
+ "</filter>\n", blur.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Feather filter */
+
+/**
+ \brief Custom predefined Out of Focus filter.
+
+ Blur eroded by white or transparency
+
+ Filter's parameters:
+ * Horizontal blur (0.01->10., default 3) -> blur (stdDeviation)
+ * Vertical blur (0.01->10., default 3) -> blur (stdDeviation)
+ * Dilatation (n-1th value, 0.->100., default 6) -> colormatrix2 (matrix)
+ * Erosion (nth value, 0.->100., default 2) -> colormatrix2 (matrix)
+ * Opacity (0.->1., default 1.) -> composite1 (k2)
+ * Background color (guint, default -1) -> flood (flood-opacity, flood-color)
+ * Blend type (enum, default normal) -> blend (mode)
+ * Blend to background (boolean, default false) -> blend (false: in2="flood", true: in2="BackgroundImage")
+
+*/
+
+class ImageBlur : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ImageBlur ( ) : Filter() { };
+ ~ImageBlur ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Out of Focus") "</name>\n"
+ "<id>org.inkscape.effect.filter.ImageBlur</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"50.00\">3</param>\n"
+ "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"50.00\">3</param>\n"
+ "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">6</param>\n"
+ "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">2</param>\n"
+ "<param name=\"opacity\" gui-text=\"" N_("Opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n"
+ "</page>\n"
+ "<page name=\"backgroundtab\" gui-text=\"Background\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Background color") "\" type=\"color\">-1</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Blend type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "</param>\n"
+ "<param name=\"background\" gui-text=\"" N_("Blend to background") "\" type=\"bool\" >false</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Blurs") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Blur eroded by white or transparency") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ImageBlur());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+ImageBlur::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream hblur;
+ std::ostringstream vblur;
+ std::ostringstream dilat;
+ std::ostringstream erosion;
+ std::ostringstream opacity;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream a;
+ std::ostringstream blend;
+ std::ostringstream background;
+
+ hblur << ext->get_param_float("hblur");
+ vblur << ext->get_param_float("vblur");
+ dilat << ext->get_param_float("dilat");
+ erosion << -ext->get_param_float("erosion");
+ opacity << ext->get_param_float("opacity");
+
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+ blend << ext->get_param_optiongroup("blend");
+
+ if (ext->get_param_bool("background")) {
+ background << "BackgroundImage" ;
+ } else {
+ background << "flood" ;
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Out of Focus\">\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"colormatrix1\" />\n"
+ "<feGaussianBlur in=\"colormatrix1\" stdDeviation=\"%s %s\" result=\"blur\" />\n"
+ "<feColorMatrix in=\"blur\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix2\" />\n"
+ "<feBlend in=\"colormatrix2\" in2=\"%s\" mode=\"%s\" result=\"blend\" />\n"
+ "<feComposite in=\"blend\" in2=\"blend\" operator=\"arithmetic\" k2=\"%s\" result=\"composite1\" />\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" />\n"
+ "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ hblur.str().c_str(), vblur.str().c_str(), dilat.str().c_str(), erosion.str().c_str(),
+ background.str().c_str(), blend.str().c_str(), opacity.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Out of Focus filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'BLURS' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BLURS_H__ */
diff --git a/src/extension/internal/filter/bumps.h b/src/extension/internal/filter/bumps.h
new file mode 100644
index 0000000..4db33d6
--- /dev/null
+++ b/src/extension/internal/filter/bumps.h
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BUMPS_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BUMPS_H__
+/* Change the 'BUMPS' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Bump filters
+ * Bump
+ * Wax bump
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Bump filter.
+
+ All purpose bump filter
+
+ Filter's parameters:
+ Options
+ * Image simplification (0.01->10., default 0.01) -> blur1 (stdDeviation)
+ * Bump simplification (0.01->10., default 0.01) -> blur2 (stdDeviation)
+ * Crop (-50.->50., default 0.) -> composite1 (k3)
+ * Red (-50.->50., default 0.) -> colormatrix1 (values)
+ * Green (-50.->50., default 0.) -> colormatrix1 (values)
+ * Blue (-50.->50., default 0.) -> colormatrix1 (values)
+ * Bump from background (boolean, default false) -> colormatrix1 (false: in="SourceGraphic", true: in="BackgroundImage")
+ Lighting
+ * Lighting type (enum, default specular) -> lighting block
+ * Height (0.->50., default 5.) -> lighting (surfaceScale)
+ * Lightness (0.->5., default 1.) -> lighting [diffuselighting (diffuseConstant)|specularlighting (specularConstant)]
+ * Precision (1->128, default 15) -> lighting (specularExponent)
+ * Color (guint, default -1 (RGB:255,255,255))-> lighting (lighting-color)
+ Light source
+ * Azimuth (0->360, default 225) -> lightsOptions (distantAzimuth)
+ * Elevation (0->180, default 45) -> lightsOptions (distantElevation)
+ * X location [point] (-5000->5000, default 526) -> lightsOptions (x)
+ * Y location [point] (-5000->5000, default 372) -> lightsOptions (y)
+ * Z location [point] (0->5000, default 150) -> lightsOptions (z)
+ * X location [spot] (-5000->5000, default 526) -> lightsOptions (x)
+ * Y location [spot] (-5000->5000, default 372) -> lightsOptions (y)
+ * Z location [spot] (-5000->5000, default 150) -> lightsOptions (z)
+ * X target (-5000->5000, default 0) -> lightsOptions (pointsAtX)
+ * Y target (-5000->5000, default 0) -> lightsOptions (pointsAtX)
+ * Z target (-5000->0, default -1000) -> lightsOptions (pointsAtX)
+ * Specular exponent (1->100, default 1) -> lightsOptions (specularExponent)
+ * Cone angle (0->100, default 50) -> lightsOptions (limitingConeAngle)
+ Color bump
+ * Blend type (enum, default normal) -> blend (mode)
+ * Image color (guint, default -987158017 (RGB:197,41,41)) -> flood (flood-color)
+ * Color bump (boolean, default false) -> composite2 (false: in="diffuselighting", true in="flood")
+*/
+
+class Bump : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Bump ( ) : Filter() { };
+ ~Bump ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Bump") "</name>\n"
+ "<id>org.inkscape.effect.filter.Bump</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"simplifyImage\" gui-text=\"" N_("Image simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">0.01</param>\n"
+ "<param name=\"simplifyBump\" gui-text=\"" N_("Bump simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">0.01</param>\n"
+ "<param name=\"crop\" gui-text=\"" N_("Crop") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n"
+ "<label appearance=\"header\">" N_("Bump source") "</label>\n"
+ "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n"
+ "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n"
+ "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">0</param>\n"
+ "<param name=\"background\" gui-text=\"" N_("Bump from background") "\" indent=\"1\" type=\"bool\">false</param>\n"
+ "</page>\n"
+ "<page name=\"lightingtab\" gui-text=\"Lighting\">\n"
+ "<param name=\"lightType\" gui-text=\"" N_("Lighting type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"specular\">" N_("Specular") "</option>\n"
+ "<option value=\"diffuse\">" N_("Diffuse") "</option>\n"
+ "</param>\n"
+ "<param name=\"height\" gui-text=\"" N_("Height") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"50.\">5</param>\n"
+ "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"5.\">1</param>\n"
+ "<param name=\"precision\" gui-text=\"" N_("Precision") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"128\">15</param>\n"
+ "<param name=\"lightingColor\" gui-text=\"" N_("Color") "\" type=\"color\">-1</param>\n"
+ "</page>\n"
+ "<page name=\"lightsourcetab\" gui-text=\"" N_("Light source") "\">\n"
+ "<param name=\"lightSource\" gui-text=\"" N_("Light source:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"distant\">" N_("Distant") "</option>\n"
+ "<option value=\"point\">" N_("Point") "</option>\n"
+ "<option value=\"spot\">" N_("Spot") "</option>\n"
+ "</param>\n"
+ "<label appearance=\"header\">" N_("Distant light options") "</label>\n"
+ "<param name=\"distantAzimuth\" gui-text=\"" N_("Azimuth") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"360\">225</param>\n"
+ "<param name=\"distantElevation\" gui-text=\"" N_("Elevation") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"180\">45</param>\n"
+ "<label appearance=\"header\">" N_("Point light options") "</label>\n"
+ "<param name=\"pointX\" gui-text=\"" N_("X location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">526</param>\n"
+ "<param name=\"pointY\" gui-text=\"" N_("Y location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">372</param>\n"
+ "<param name=\"pointZ\" gui-text=\"" N_("Z location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"5000\">150</param>\n"
+ "<label appearance=\"header\">" N_("Spot light options") "</label>\n"
+ "<param name=\"spotX\" gui-text=\"" N_("X location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">526</param>\n"
+ "<param name=\"spotY\" gui-text=\"" N_("Y location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">372</param>\n"
+ "<param name=\"spotZ\" gui-text=\"" N_("Z location") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">150</param>\n"
+ "<param name=\"spotAtX\" gui-text=\"" N_("X target") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">0</param>\n"
+ "<param name=\"spotAtY\" gui-text=\"" N_("Y target") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"5000\">0</param>\n"
+ "<param name=\"spotAtZ\" gui-text=\"" N_("Z target") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"-5000\" max=\"0\">-1000</param>\n"
+ "<param name=\"spotExponent\" gui-text=\"" N_("Specular exponent") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"100\">1</param>\n"
+ "<param name=\"spotConeAngle\" gui-text=\"" N_("Cone angle") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"100\">50</param>\n"
+ "</page>\n"
+ "<page name=\"colortab\" gui-text=\"Color bump\">\n"
+ "<param name=\"imageColor\" gui-text=\"" N_("Image color") "\" type=\"color\">-987158017</param>\n"
+ "<param name=\"colorize\" gui-text=\"" N_("Color bump") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Blend type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Bumps") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("All purposes bump filter") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Bump());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Bump::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream simplifyImage;
+ std::ostringstream simplifyBump;
+ std::ostringstream red;
+ std::ostringstream green;
+ std::ostringstream blue;
+ std::ostringstream crop;
+ std::ostringstream bumpSource;
+ std::ostringstream blend;
+
+ std::ostringstream lightStart;
+ std::ostringstream lightOptions;
+ std::ostringstream lightEnd;
+
+ std::ostringstream floodRed;
+ std::ostringstream floodGreen;
+ std::ostringstream floodBlue;
+ std::ostringstream floodAlpha;
+ std::ostringstream colorize;
+
+
+ simplifyImage << ext->get_param_float("simplifyImage");
+ simplifyBump << ext->get_param_float("simplifyBump");
+ red << ext->get_param_float("red");
+ green << ext->get_param_float("green");
+ blue << ext->get_param_float("blue");
+ crop << ext->get_param_float("crop");
+ blend << ext->get_param_optiongroup("blend");
+
+ guint32 lightingColor = ext->get_param_color("lightingColor");
+ guint32 imageColor = ext->get_param_color("imageColor");
+
+ if (ext->get_param_bool("background")) {
+ bumpSource << "BackgroundImage" ;
+ } else {
+ bumpSource << "blur1" ;
+ }
+
+ const gchar *lightType = ext->get_param_optiongroup("lightType");
+ if ((g_ascii_strcasecmp("specular", lightType) == 0)) {
+ // Specular
+ lightStart << "<feSpecularLighting lighting-color=\"rgb(" << ((lightingColor >> 24) & 0xff) << ","
+ << ((lightingColor >> 16) & 0xff) << "," << ((lightingColor >> 8) & 0xff) << ")\" surfaceScale=\""
+ << ext->get_param_float("height") << "\" specularConstant=\"" << ext->get_param_float("lightness")
+ << "\" specularExponent=\"" << ext->get_param_int("precision") << "\" result=\"lighting\">";
+ lightEnd << "</feSpecularLighting>";
+ } else {
+ // Diffuse
+ lightStart << "<feDiffuseLighting lighting-color=\"rgb(" << ((lightingColor >> 24) & 0xff) << ","
+ << ((lightingColor >> 16) & 0xff) << "," << ((lightingColor >> 8) & 0xff) << ")\" surfaceScale=\""
+ << ext->get_param_float("height") << "\" diffuseConstant=\"" << ext->get_param_float("lightness")
+ << "\" result=\"lighting\">";
+ lightEnd << "</feDiffuseLighting>";
+ }
+
+ const gchar *lightSource = ext->get_param_optiongroup("lightSource");
+ if ((g_ascii_strcasecmp("distant", lightSource) == 0)) {
+ // Distant
+ lightOptions << "<feDistantLight azimuth=\"" << ext->get_param_int("distantAzimuth") << "\" elevation=\""
+ << ext->get_param_int("distantElevation") << "\" />";
+ } else if ((g_ascii_strcasecmp("point", lightSource) == 0)) {
+ // Point
+ lightOptions << "<fePointLight z=\"" << ext->get_param_int("pointX") << "\" y=\"" << ext->get_param_int("pointY")
+ << "\" x=\"" << ext->get_param_int("pointZ") << "\" />";
+ } else {
+ // Spot
+ lightOptions << "<feSpotLight x=\"" << ext->get_param_int("pointX") << "\" y=\"" << ext->get_param_int("pointY")
+ << "\" z=\"" << ext->get_param_int("pointZ") << "\" pointsAtX=\"" << ext->get_param_int("spotAtX")
+ << "\" pointsAtY=\"" << ext->get_param_int("spotAtY") << "\" pointsAtZ=\"" << ext->get_param_int("spotAtZ")
+ << "\" specularExponent=\"" << ext->get_param_int("spotExponent")
+ << "\" limitingConeAngle=\"" << ext->get_param_int("spotConeAngle")
+ << "\" />";
+ }
+
+ floodRed << ((imageColor >> 24) & 0xff);
+ floodGreen << ((imageColor >> 16) & 0xff);
+ floodBlue << ((imageColor >> 8) & 0xff);
+ floodAlpha << (imageColor & 0xff) / 255.0F;
+
+ if (ext->get_param_bool("colorize")) {
+ colorize << "flood" ;
+ } else {
+ colorize << "blur1" ;
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Bump\">\n"
+ "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feColorMatrix in=\"%s\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s %s 1 0 \" result=\"colormatrix1\" />\n"
+ "<feColorMatrix in=\"colormatrix1\" type=\"luminanceToAlpha\" result=\"colormatrix2\" />\n"
+ "<feComposite in2=\"blur1\" operator=\"arithmetic\" k2=\"1\" k3=\"%s\" result=\"composite1\" />\n"
+ "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "%s\n"
+ "%s\n"
+ "%s\n"
+ "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood\" />\n"
+ "<feComposite in=\"lighting\" in2=\"%s\" operator=\"arithmetic\" k3=\"1\" k2=\"1\" result=\"composite2\" />\n"
+ "<feBlend in2=\"SourceGraphic\" mode=\"%s\" result=\"blend\" />\n"
+ "<feComposite in=\"blend\" in2=\"SourceGraphic\" operator=\"in\" k2=\"1\" result=\"composite3\" />\n"
+ "</filter>\n", simplifyImage.str().c_str(), bumpSource.str().c_str(), red.str().c_str(), green.str().c_str(), blue.str().c_str(),
+ crop.str().c_str(), simplifyBump.str().c_str(),
+ lightStart.str().c_str(), lightOptions.str().c_str(), lightEnd.str().c_str(),
+ floodRed.str().c_str(), floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(),
+ colorize.str().c_str(), blend.str().c_str());
+ // clang-format on
+
+ return _filter;
+
+}; /* Bump filter */
+
+/**
+ \brief Custom predefined Wax Bump filter.
+
+ Turns an image to jelly
+
+ Filter's parameters:
+ Options
+ * Image simplification (0.01->10., default 1.5) -> blur1 (stdDeviation)
+ * Bump simplification (0.01->10., default 1) -> blur2 (stdDeviation)
+ * Crop (-10.->10., default 1.) -> colormatrix2 (4th value of the last line)
+ * Red (-10.->10., default 0.) -> colormatrix2 (values, substract 0.21)
+ * Green (-10.->10., default 0.) -> colormatrix2 (values, substract 0.72)
+ * Blue (-10.->10., default 0.) -> colormatrix2 (values, substract 0.07)
+ * Background (enum, default color) ->
+ * color: colormatrix1 (in="flood1")
+ * image: colormatrix1 (in="SourceGraphic")
+ * blurred image: colormatrix1 (in="blur1")
+ * Background opacity (0.->1., default 0) -> colormatrix1 (last value)
+ Lighting (specular, distant light)
+ * Color (guint, default -1 (RGB:255,255,255))-> lighting (lighting-color)
+ * Height (-50.->50., default 5.) -> lighting (surfaceScale)
+ * Lightness (0.->10., default 1.4) -> lighting [diffuselighting (diffuseConstant)|specularlighting (specularConstant)]
+ * Precision (0->50, default 35) -> lighting (specularExponent)
+ * Azimuth (0->360, default 225) -> lightsOptions (distantAzimuth)
+ * Elevation (0->180, default 60) -> lightsOptions (distantElevation)
+ * Lighting blend (enum, default screen) -> blend1 (mode)
+ * Highlight blend (enum, default screen) -> blend2 (mode)
+ Bump
+ * Transparency type (enum [in,atop], default atop) -> composite2 (operator)
+ * Color (guint, default -520083713 (RGB:225,0,38)) -> flood2 (flood-color)
+ * Revert bump (boolean, default false) -> composite1 (false: operator="out", true operator="in")
+*/
+
+class WaxBump : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ WaxBump ( ) : Filter() { };
+ ~WaxBump ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Wax Bump") "</name>\n"
+ "<id>org.inkscape.effect.filter.WaxBump</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"simplifyImage\" gui-text=\"" N_("Image simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">1.5</param>\n"
+ "<param name=\"simplifyBump\" gui-text=\"" N_("Bump simplification") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">1</param>\n"
+ "<param name=\"crop\" gui-text=\"" N_("Crop") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1</param>\n"
+ "<label appearance=\"header\">" N_("Bump source") "</label>\n"
+ "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0</param>\n"
+ "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0</param>\n"
+ "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0</param>\n"
+ "<param name=\"background\" gui-text=\"" N_("Background:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"flood1\">" N_("Color") "</option>\n"
+ "<option value=\"SourceGraphic\">" N_("Image") "</option>\n"
+ "<option value=\"blur1\">" N_("Blurred image") "</option>\n"
+ "</param>\n"
+ "<param name=\"bgopacity\" gui-text=\"" N_("Background opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">0</param>\n"
+ "</page>\n"
+ "<page name=\"lightingtab\" gui-text=\"" N_("Lighting") "\">\n"
+ "<param name=\"lightingColor\" gui-text=\"" N_("Color") "\" type=\"color\">-1</param>\n"
+ "<param name=\"height\" gui-text=\"" N_("Height") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-50.\" max=\"50.\">5</param>\n"
+ "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10.\">1.4</param>\n"
+ "<param name=\"precision\" gui-text=\"" N_("Precision") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"50\">35</param>\n"
+ "<param name=\"distantAzimuth\" gui-text=\"" N_("Azimuth") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"360\">225</param>\n"
+ "<param name=\"distantElevation\" gui-text=\"" N_("Elevation") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"180\">60</param>\n"
+ "<param name=\"lightingblend\" gui-text=\"" N_("Lighting blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "</param>\n"
+ "<param name=\"highlightblend\" gui-text=\"" N_("Highlight blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "<page name=\"colortab\" gui-text=\"Bump\">\n"
+ "<param name=\"imageColor\" gui-text=\"" N_("Bump color") "\" type=\"color\">-520083713</param>\n"
+ "<param name=\"revert\" gui-text=\"" N_("Revert bump") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"transparency\" gui-text=\"" N_("Transparency type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"atop\">" N_("Atop") "</option>\n"
+ "<option value=\"in\">" N_("In") "</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Bumps") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Turns an image to jelly") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new WaxBump());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+WaxBump::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream simplifyImage;
+ std::ostringstream simplifyBump;
+ std::ostringstream crop;
+
+ std::ostringstream red;
+ std::ostringstream green;
+ std::ostringstream blue;
+
+ std::ostringstream background;
+ std::ostringstream bgopacity;
+
+ std::ostringstream height;
+ std::ostringstream lightness;
+ std::ostringstream precision;
+ std::ostringstream distantAzimuth;
+ std::ostringstream distantElevation;
+
+ std::ostringstream lightRed;
+ std::ostringstream lightGreen;
+ std::ostringstream lightBlue;
+
+ std::ostringstream floodRed;
+ std::ostringstream floodGreen;
+ std::ostringstream floodBlue;
+ std::ostringstream floodAlpha;
+
+ std::ostringstream revert;
+ std::ostringstream lightingblend;
+ std::ostringstream highlightblend;
+ std::ostringstream transparency;
+
+ simplifyImage << ext->get_param_float("simplifyImage");
+ simplifyBump << ext->get_param_float("simplifyBump");
+ crop << ext->get_param_float("crop");
+
+ red << ext->get_param_float("red") - 0.21;
+ green << ext->get_param_float("green") - 0.72;
+ blue << ext->get_param_float("blue") - 0.07;
+
+ background << ext->get_param_optiongroup("background");
+ bgopacity << ext->get_param_float("bgopacity");
+
+ height << ext->get_param_float("height");
+ lightness << ext->get_param_float("lightness");
+ precision << ext->get_param_int("precision");
+ distantAzimuth << ext->get_param_int("distantAzimuth");
+ distantElevation << ext->get_param_int("distantElevation");
+
+ guint32 lightingColor = ext->get_param_color("lightingColor");
+ lightRed << ((lightingColor >> 24) & 0xff);
+ lightGreen << ((lightingColor >> 16) & 0xff);
+ lightBlue << ((lightingColor >> 8) & 0xff);
+
+ guint32 imageColor = ext->get_param_color("imageColor");
+ floodRed << ((imageColor >> 24) & 0xff);
+ floodGreen << ((imageColor >> 16) & 0xff);
+ floodBlue << ((imageColor >> 8) & 0xff);
+ floodAlpha << (imageColor & 0xff) / 255.0F;
+
+ if (ext->get_param_bool("revert")) {
+ revert << "in" ;
+ } else {
+ revert << "out" ;
+ }
+
+ lightingblend << ext->get_param_optiongroup("lightingblend");
+ highlightblend << ext->get_param_optiongroup("highlightblend");
+ transparency << ext->get_param_optiongroup("transparency");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Wax Bump\">\n"
+ "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feFlood flood-opacity=\"1\" flood-color=\"rgb(255,255,255)\" result=\"flood1\" />\n"
+ "<feColorMatrix in=\"%s\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 %s \" result=\"colormatrix1\" />\n"
+ "<feColorMatrix in=\"blur1\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 %s %s %s %s 0 \" result=\"colormatrix2\" />\n"
+ "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood2\" />\n"
+ "<feComposite in=\"flood2\" in2=\"colormatrix2\" operator=\"%s\" result=\"composite1\" />\n"
+ "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "<feSpecularLighting in=\"blur2\" lighting-color=\"rgb(%s,%s,%s)\" specularConstant=\"%s\" surfaceScale=\"%s\" specularExponent=\"%s\" result=\"specular\">\n"
+ "<feDistantLight elevation=\"%s\" azimuth=\"%s\" />\n"
+ "</feSpecularLighting>\n"
+ "<feBlend in=\"specular\" in2=\"blur2\" specularConstant=\"1\" mode=\"%s\" result=\"blend1\" />\n"
+ "<feComposite in=\"blend1\" in2=\"blur2\" k2=\"0\" operator=\"%s\" k1=\"0.5\" k3=\"0.5\" k4=\"0\" result=\"composite2\" />\n"
+ "<feMerge result=\"merge\">\n"
+ "<feMergeNode in=\"colormatrix1\" />\n"
+ "<feMergeNode in=\"composite2\" />\n"
+ "</feMerge>\n"
+ "<feBlend in2=\"composite2\" mode=\"%s\" result=\"blend2\" />\n"
+ "<feComposite in=\"blend2\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite3\" />\n"
+ "</filter>\n", simplifyImage.str().c_str(), background.str().c_str(), bgopacity.str().c_str(),
+ red.str().c_str(), green.str().c_str(), blue.str().c_str(), crop.str().c_str(),
+ floodRed.str().c_str(), floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(),
+ revert.str().c_str(), simplifyBump.str().c_str(),
+ lightRed.str().c_str(), lightGreen.str().c_str(), lightBlue.str().c_str(),
+ lightness.str().c_str(), height.str().c_str(), precision.str().c_str(),
+ distantElevation.str().c_str(), distantAzimuth.str().c_str(),
+ lightingblend.str().c_str(), transparency.str().c_str(), highlightblend.str().c_str() );
+ // clang-format on
+
+ return _filter;
+
+}; /* Wax bump filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'BUMPS' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_BUMPS_H__ */
diff --git a/src/extension/internal/filter/color.h b/src/extension/internal/filter/color.h
new file mode 100644
index 0000000..9f4f872
--- /dev/null
+++ b/src/extension/internal/filter/color.h
@@ -0,0 +1,1963 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_COLOR_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_COLOR_H__
+/* Change the 'COLOR' above to be your file name */
+
+/*
+ * Copyright (C) 2013-2015 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Color filters
+ * Brilliance
+ * Channel painting
+ * Color blindness
+ * Color shift
+ * Colorize
+ * Component transfer
+ * Duochrome
+ * Extract channel
+ * Fade to black or white
+ * Greyscale
+ * Invert
+ * Lighting
+ * Lightness-contrast
+ * Nudge RGB
+ * Nudge CMY
+ * Quadritone
+ * Simple blend
+ * Solarize
+ * Tritone
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Brilliance filter.
+
+ Brilliance filter.
+
+ Filter's parameters:
+ * Brilliance (1.->10., default 2.) -> colorMatrix (RVB entries)
+ * Over-saturation (0.->10., default 0.5) -> colorMatrix (6 other entries)
+ * Lightness (-10.->10., default 0.) -> colorMatrix (last column)
+ * Inverted (boolean, default false) -> colorMatrix
+
+ Matrix:
+ St Vi Vi 0 Li
+ Vi St Vi 0 Li
+ Vi Vi St 0 Li
+ 0 0 0 1 0
+*/
+class Brilliance : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Brilliance ( ) : Filter() { };
+ ~Brilliance ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Brilliance") "</name>\n"
+ "<id>org.inkscape.effect.filter.Brilliance</id>\n"
+ "<param name=\"brightness\" gui-text=\"" N_("Brightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"10.00\">2</param>\n"
+ "<param name=\"sat\" gui-text=\"" N_("Over-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.0\" max=\"10.00\">0.5</param>\n"
+ "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0</param>\n"
+ "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Brightness filter") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Brilliance());
+ // clang-format on
+ };
+};
+
+gchar const *
+Brilliance::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream brightness;
+ std::ostringstream sat;
+ std::ostringstream lightness;
+
+ if (ext->get_param_bool("invert")) {
+ brightness << -ext->get_param_float("brightness");
+ sat << 1 + ext->get_param_float("sat");
+ lightness << -ext->get_param_float("lightness");
+ } else {
+ brightness << ext->get_param_float("brightness");
+ sat << -ext->get_param_float("sat");
+ lightness << ext->get_param_float("lightness");
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Brilliance\">\n"
+ "<feColorMatrix values=\"%s %s %s 0 %s %s %s %s 0 %s %s %s %s 0 %s 0 0 0 1 0 \" />\n"
+ "</filter>\n", brightness.str().c_str(), sat.str().c_str(), sat.str().c_str(),
+ lightness.str().c_str(), sat.str().c_str(), brightness.str().c_str(),
+ sat.str().c_str(), lightness.str().c_str(), sat.str().c_str(),
+ sat.str().c_str(), brightness.str().c_str(), lightness.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Brilliance filter */
+
+/**
+ \brief Custom predefined Channel Painting filter.
+
+ Channel Painting filter.
+
+ Filter's parameters:
+ * Saturation (0.->1., default 1.) -> colormatrix1 (values)
+ * Red (-10.->10., default -1.) -> colormatrix2 (values)
+ * Green (-10.->10., default 0.5) -> colormatrix2 (values)
+ * Blue (-10.->10., default 0.5) -> colormatrix2 (values)
+ * Alpha (-10.->10., default 1.) -> colormatrix2 (values)
+ * Flood colors (guint, default 16777215) -> flood (flood-opacity, flood-color)
+ * Inverted (boolean, default false) -> composite1 (operator, true='in', false='out')
+
+ Matrix:
+ 1 0 0 0 0
+ 0 1 0 0 0
+ 0 0 1 0 0
+ R G B A 0
+*/
+class ChannelPaint : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ChannelPaint ( ) : Filter() { };
+ ~ChannelPaint ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Channel Painting") "</name>\n"
+ "<id>org.inkscape.effect.filter.ChannelPaint</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"saturation\" gui-text=\"" N_("Saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">1</param>\n"
+ "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">-1</param>\n"
+ "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n"
+ "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n"
+ "<param name=\"alpha\" gui-text=\"" N_("Alpha") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1</param>\n"
+ "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n"
+ "</page>\n"
+ "<page name=\"colortab\" gui-text=\"Color\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">16777215</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Replace RGB by any color") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ChannelPaint());
+ // clang-format on
+ };
+};
+
+gchar const *
+ChannelPaint::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream saturation;
+ std::ostringstream red;
+ std::ostringstream green;
+ std::ostringstream blue;
+ std::ostringstream alpha;
+ std::ostringstream invert;
+ std::ostringstream floodRed;
+ std::ostringstream floodGreen;
+ std::ostringstream floodBlue;
+ std::ostringstream floodAlpha;
+
+ saturation << ext->get_param_float("saturation");
+ red << ext->get_param_float("red");
+ green << ext->get_param_float("green");
+ blue << ext->get_param_float("blue");
+ alpha << ext->get_param_float("alpha");
+
+ guint32 color = ext->get_param_color("color");
+ floodRed << ((color >> 24) & 0xff);
+ floodGreen << ((color >> 16) & 0xff);
+ floodBlue << ((color >> 8) & 0xff);
+ floodAlpha << (color & 0xff) / 255.0F;
+
+ if (ext->get_param_bool("invert")) {
+ invert << "in";
+ } else {
+ invert << "out";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Channel Painting\">\n"
+ "<feColorMatrix values=\"%s\" type=\"saturate\" result=\"colormatrix1\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s %s %s 0 \" in=\"SourceGraphic\" result=\"colormatrix2\" />\n"
+ "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood\" />\n"
+ "<feComposite in2=\"colormatrix2\" operator=\"%s\" result=\"composite1\" />\n"
+ "<feMerge result=\"merge\">\n"
+ "<feMergeNode in=\"colormatrix1\" />\n"
+ "<feMergeNode in=\"composite1\" />\n"
+ "</feMerge>\n"
+ "<feComposite in=\"merge\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n"
+ "</filter>\n", saturation.str().c_str(), red.str().c_str(), green.str().c_str(),
+ blue.str().c_str(), alpha.str().c_str(), floodRed.str().c_str(),
+ floodGreen.str().c_str(), floodBlue.str().c_str(), floodAlpha.str().c_str(),
+ invert.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Channel Painting filter */
+
+/**
+ \brief Custom predefined Color Blindness filter.
+
+ Color Blindness filter.
+ Based on https://openclipart.org/detail/22299/Color%20Blindness%20filters
+
+ Filter's parameters:
+ * Blindness type (enum, default Achromatomaly) -> colormatrix
+*/
+class ColorBlindness : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ColorBlindness ( ) : Filter() { };
+ ~ColorBlindness ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Color Blindness") "</name>\n"
+ "<id>org.inkscape.effect.filter.ColorBlindness</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"type\" gui-text=\"" N_("Blindness type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"0.618 0.32 0.062 0 0 0.163 0.775 0.062 0 0 0.163 0.32 0.516 0 0 0 0 0 1 0 \">" N_("Rod monochromacy (atypical achromatopsia)") "</option>\n"
+ "<option value=\"0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0 0 0 1 0 \">" N_("Cone monochromacy (typical achromatopsia)") "</option>\n"
+ "<option value=\"0.8 0.2 0 0 0 0.2583 0.74167 0 0 0 0 0.14167 0.85833 0 0 0 0 0 1 0 \">" N_("Green weak (deuteranomaly)") "</option>\n"
+ "<option value=\"0.625 0.375 0 0 0 0.7 0.3 0 0 0 0 0.3 0.7 0 0 0 0 0 1 0 \">" N_("Green blind (deuteranopia)") "</option>\n"
+ "<option value=\"0.8166 0.1833 0 0 0 0.333 0.666 0 0 0 0 0.125 0.875 0 0 0 0 0 1 0 \">" N_("Red weak (protanomaly)") "</option>\n"
+ "<option value=\"0.566 0.43333 0 0 0 0.55833 0.4416 0 0 0 0 0.24167 0.75833 0 0 0 0 0 1 0 \">" N_("Red blind (protanopia)") "</option>\n"
+ "<option value=\"0.966 0.033 0 0 0 0 0.733 0.266 0 0 0 0.1833 0.816 0 0 0 0 0 1 0 \">" N_("Blue weak (tritanomaly)") "</option>\n"
+ "<option value=\"0.95 0.05 0 0 0 0.2583 0.4333 0.5667 0 0 0 0.475 0.525 0 0 0 0 0 1 0 \">" N_("Blue blind (tritanopia)") "</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "<page name=\"helptab\" gui-text=\"Help\">\n"
+ "<label xml:space=\"preserve\">\n"
+"Filters based on https://openclipart.org/detail/22299/Color%20Blindness%20filters\n"
+"\n"
+"These filters don't correctly reflect actual color blindness for two main reasons:\n"
+" * Everyone is different, and is not affected exactly the same way.\n"
+" * The filters are in the RGB color space, and ignore confusion lines.\n"
+ "</label>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Simulate color blindness") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ColorBlindness());
+ // clang-format on
+ };
+};
+
+gchar const *
+ColorBlindness::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream type;
+ type << ext->get_param_optiongroup("type");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" height=\"1\" width=\"1\" y=\"0\" x=\"0\" inkscape:label=\"Color Blindness\">\n"
+ "<feColorMatrix values=\"%s\" type=\"matrix\" result=\"colormatrix1\" />\n"
+ "</filter>\n", type.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Color Blindness filter */
+
+/**
+ \brief Custom predefined Color shift filter.
+
+ Rotate and desaturate hue
+
+ Filter's parameters:
+ * Shift (0->360, default 330) -> color1 (values)
+ * Saturation (0.->1., default 0.6) -> color2 (values)
+*/
+
+class ColorShift : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ColorShift ( ) : Filter() { };
+ ~ColorShift ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Color Shift") "</name>\n"
+ "<id>org.inkscape.effect.filter.ColorShift</id>\n"
+ "<param name=\"shift\" gui-text=\"" N_("Shift (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">330</param>\n"
+ "<param name=\"sat\" gui-text=\"" N_("Saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1\">0.6</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Rotate and desaturate hue") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ColorShift());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+ColorShift::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream shift;
+ std::ostringstream sat;
+
+ shift << ext->get_param_int("shift");
+ sat << ext->get_param_float("sat");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Color Shift\">\n"
+ "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"color1\" />\n"
+ "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"color2\" />\n"
+ "</filter>\n", shift.str().c_str(), sat.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* ColorShift filter */
+
+/**
+ \brief Custom predefined Colorize filter.
+
+ Blend image or object with a flood color.
+
+ Filter's parameters:
+ * Harsh light (0.->10., default 0) -> composite1 (k1)
+ * Normal light (0.->10., default 1) -> composite2 (k2)
+ * Duotone (boolean, default false) -> colormatrix1 (values="0")
+ * Filtered greys (boolean, default false) -> colormatrix2 (values="0")
+ * Blend mode 1 (enum, default Multiply) -> blend1 (mode)
+ * Blend mode 2 (enum, default Screen) -> blend2 (mode)
+*/
+
+class Colorize : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Colorize ( ) : Filter() { };
+ ~Colorize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Colorize") "</name>\n"
+ "<id>org.inkscape.effect.filter.Colorize</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"hlight\" gui-text=\"" N_("Harsh light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">0</param>\n"
+ "<param name=\"nlight\" gui-text=\"" N_("Normal light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">1</param>\n"
+ "<param name=\"duotone\" gui-text=\"" N_("Duotone") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"blend1\" gui-text=\"" N_("Blend 1:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "</param>\n"
+ "<param name=\"blend2\" gui-text=\"" N_("Blend 2:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "<page name=\"colortab\" gui-text=\"Color\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">-1639776001</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Blend image or object with a flood color") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Colorize());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Colorize::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream a;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream hlight;
+ std::ostringstream nlight;
+ std::ostringstream duotone;
+ std::ostringstream blend1;
+ std::ostringstream blend2;
+
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+
+ hlight << ext->get_param_float("hlight");
+ nlight << ext->get_param_float("nlight");
+ blend1 << ext->get_param_optiongroup("blend1");
+ blend2 << ext->get_param_optiongroup("blend2");
+ if (ext->get_param_bool("duotone")) {
+ duotone << "0";
+ } else {
+ duotone << "1";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Colorize\">\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"arithmetic\" k1=\"%s\" k2=\"%s\" result=\"composite1\" />\n"
+ "<feColorMatrix in=\"composite1\" values=\"%s\" type=\"saturate\" result=\"colormatrix1\" />\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood1\" />\n"
+ "<feBlend in=\"flood1\" in2=\"colormatrix1\" mode=\"%s\" result=\"blend1\" />\n"
+ "<feBlend in2=\"blend1\" mode=\"%s\" result=\"blend2\" />\n"
+ "<feColorMatrix in=\"blend2\" values=\"1\" type=\"saturate\" result=\"colormatrix2\" />\n"
+ "<feComposite in=\"colormatrix2\" in2=\"SourceGraphic\" operator=\"in\" k2=\"1\" result=\"composite2\" />\n"
+ "</filter>\n", hlight.str().c_str(), nlight.str().c_str(), duotone.str().c_str(),
+ a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ blend1.str().c_str(), blend2.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Colorize filter */
+
+/**
+ \brief Custom predefined ComponentTransfer filter.
+
+ Basic component transfer structure.
+
+ Filter's parameters:
+ * Type (enum, default identity) -> component function
+
+*/
+class ComponentTransfer : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ComponentTransfer ( ) : Filter() { };
+ ~ComponentTransfer ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Component Transfer") "</name>\n"
+ "<id>org.inkscape.effect.filter.ComponentTransfer</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"identity\">" N_("Identity") "</option>\n"
+ "<option value=\"table\">" N_("Table") "</option>\n"
+ "<option value=\"discrete\">" N_("Discrete") "</option>\n"
+ "<option value=\"linear\">" N_("Linear") "</option>\n"
+ "<option value=\"gamma\">" N_("Gamma") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Basic component transfer structure") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ComponentTransfer());
+ // clang-format on
+ };
+};
+
+gchar const *
+ComponentTransfer::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream CTfunction;
+ const gchar *type = ext->get_param_optiongroup("type");
+
+ if ((g_ascii_strcasecmp("identity", type) == 0)) {
+ CTfunction << "<feFuncR type=\"identity\" tableValues=\"1 0\" />\n"
+ << "<feFuncG type=\"identity\" tableValues=\"1 0\" />\n"
+ << "<feFuncB type=\"identity\" tableValues=\"1 0\" />\n"
+ << "<feFuncA type=\"identity\" tableValues=\"0 1\" />\n";
+ } else if ((g_ascii_strcasecmp("table", type) == 0)) {
+ CTfunction << "<feFuncR type=\"table\" tableValues=\"0 1 0\" />\n"
+ << "<feFuncG type=\"table\" tableValues=\"0 1 0\" />\n"
+ << "<feFuncB type=\"table\" tableValues=\"0 1 0\" />\n";
+ } else if ((g_ascii_strcasecmp("discrete", type) == 0)) {
+ CTfunction << "<feFuncR tableValues=\"0 0.2 0.4 0.6 0.8 1 1\" type=\"discrete\" />\n"
+ << "<feFuncG tableValues=\"0 0.2 0.4 0.6 0.8 1 1\" type=\"discrete\" />\n"
+ << "<feFuncB tableValues=\"0 0.2 0.4 0.6 0.8 1 1\" type=\"discrete\" />\n";
+ } else if ((g_ascii_strcasecmp("linear", type) == 0)) {
+ CTfunction << "<feFuncR type=\"linear\" slope=\".5\" intercept=\".10\" />\n"
+ << "<feFuncG type=\"linear\" slope=\".5\" intercept=\".10\" />\n"
+ << "<feFuncB type=\"linear\" slope=\".5\" intercept=\".10\" />\n";
+ } else { //Gamma
+ CTfunction << "<feFuncR type=\"gamma\" amplitude=\"3\" exponent=\"3\" offset=\"0.1\" />\n"
+ << "<feFuncG type=\"gamma\" amplitude=\"3\" exponent=\"3\" offset=\"0.1\" />\n"
+ << "<feFuncB type=\"gamma\" amplitude=\"3\" exponent=\"3\" offset=\"0.1\" />\n";
+ }
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Component Transfer\">\n"
+ "<feComponentTransfer>\n"
+ "%s\n"
+ "</feComponentTransfer>\n"
+ "</filter>\n", CTfunction.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* ComponentTransfer filter */
+
+/**
+ \brief Custom predefined Duochrome filter.
+
+ Convert luminance values to a duochrome palette.
+
+ Filter's parameters:
+ * Fluorescence level (0.->2., default 0) -> composite4 (k2)
+ * Swap (enum, default "No swap") -> composite1, composite2 (operator)
+ * Color 1 (guint, default 1364325887) -> flood1 (flood-opacity, flood-color)
+ * Color 2 (guint, default -65281) -> flood2 (flood-opacity, flood-color)
+*/
+
+class Duochrome : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Duochrome ( ) : Filter() { };
+ ~Duochrome ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Duochrome") "</name>\n"
+ "<id>org.inkscape.effect.filter.Duochrome</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"fluo\" gui-text=\"" N_("Fluorescence level") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"2\">0</param>\n"
+ "<param name=\"swap\" gui-text=\"" N_("Swap:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"none\">" N_("No swap") "</option>\n"
+ "<option value=\"full\">" N_("Color and alpha") "</option>\n"
+ "<option value=\"color\">" N_("Color only") "</option>\n"
+ "<option value=\"alpha\">" N_("Alpha only") "</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "<page name=\"co11tab\" gui-text=\"Color 1\">\n"
+ "<param name=\"color1\" gui-text=\"" N_("Color 1") "\" type=\"color\">1364325887</param>\n"
+ "</page>\n"
+ "<page name=\"co12tab\" gui-text=\"Color 2\">\n"
+ "<param name=\"color2\" gui-text=\"" N_("Color 2") "\" type=\"color\">-65281</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Convert luminance values to a duochrome palette") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Duochrome());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Duochrome::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream a1;
+ std::ostringstream r1;
+ std::ostringstream g1;
+ std::ostringstream b1;
+ std::ostringstream a2;
+ std::ostringstream r2;
+ std::ostringstream g2;
+ std::ostringstream b2;
+ std::ostringstream fluo;
+ std::ostringstream swap1;
+ std::ostringstream swap2;
+ guint32 color1 = ext->get_param_color("color1");
+ guint32 color2 = ext->get_param_color("color2");
+ double fluorescence = ext->get_param_float("fluo");
+ const gchar *swaptype = ext->get_param_optiongroup("swap");
+
+ r1 << ((color1 >> 24) & 0xff);
+ g1 << ((color1 >> 16) & 0xff);
+ b1 << ((color1 >> 8) & 0xff);
+ r2 << ((color2 >> 24) & 0xff);
+ g2 << ((color2 >> 16) & 0xff);
+ b2 << ((color2 >> 8) & 0xff);
+ fluo << fluorescence;
+
+ if ((g_ascii_strcasecmp("full", swaptype) == 0)) {
+ swap1 << "in";
+ swap2 << "out";
+ a1 << (color1 & 0xff) / 255.0F;
+ a2 << (color2 & 0xff) / 255.0F;
+ } else if ((g_ascii_strcasecmp("color", swaptype) == 0)) {
+ swap1 << "in";
+ swap2 << "out";
+ a1 << (color2 & 0xff) / 255.0F;
+ a2 << (color1 & 0xff) / 255.0F;
+ } else if ((g_ascii_strcasecmp("alpha", swaptype) == 0)) {
+ swap1 << "out";
+ swap2 << "in";
+ a1 << (color2 & 0xff) / 255.0F;
+ a2 << (color1 & 0xff) / 255.0F;
+ } else {
+ swap1 << "out";
+ swap2 << "in";
+ a1 << (color1 & 0xff) / 255.0F;
+ a2 << (color2 & 0xff) / 255.0F;
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Duochrome\">\n"
+ "<feColorMatrix type=\"luminanceToAlpha\" result=\"colormatrix1\" />\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood1\" />\n"
+ "<feComposite in2=\"colormatrix1\" operator=\"%s\" result=\"composite1\" />\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood2\" />\n"
+ "<feComposite in2=\"colormatrix1\" result=\"composite2\" operator=\"%s\" />\n"
+ "<feComposite in=\"composite2\" in2=\"composite1\" k2=\"1\" k3=\"1\" operator=\"arithmetic\" result=\"composite3\" />\n"
+ "<feColorMatrix in=\"composite3\" type=\"matrix\" values=\"2 -1 0 0 0 0 2 -1 0 0 -1 0 2 0 0 0 0 0 1 0 \" result=\"colormatrix2\" />\n"
+ "<feComposite in=\"colormatrix2\" in2=\"composite3\" operator=\"arithmetic\" k2=\"%s\" result=\"composite4\" />\n"
+ "<feBlend in=\"composite4\" in2=\"composite3\" mode=\"normal\" result=\"blend\" />\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" />\n"
+ "</filter>\n", a1.str().c_str(), r1.str().c_str(), g1.str().c_str(), b1.str().c_str(), swap1.str().c_str(),
+ a2.str().c_str(), r2.str().c_str(), g2.str().c_str(), b2.str().c_str(), swap2.str().c_str(),
+ fluo.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Duochrome filter */
+
+/**
+ \brief Custom predefined Extract Channel filter.
+
+ Extract color channel as a transparent image.
+
+ Filter's parameters:
+ * Channel (enum, all colors, default Red) -> colormatrix (values)
+ * Background blend (enum, Normal, Multiply, Screen, default Normal) -> blend (mode)
+ * Channel to alpha (boolean, default false) -> colormatrix (values)
+
+*/
+class ExtractChannel : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ExtractChannel ( ) : Filter() { };
+ ~ExtractChannel ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Extract Channel") "</name>\n"
+ "<id>org.inkscape.effect.filter.ExtractChannel</id>\n"
+ "<param name=\"source\" gui-text=\"" N_("Channel:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"r\">" N_("Red") "</option>\n"
+ "<option value=\"g\">" N_("Green") "</option>\n"
+ "<option value=\"b\">" N_("Blue") "</option>\n"
+ "<option value=\"c\">" N_("Cyan") "</option>\n"
+ "<option value=\"m\">" N_("Magenta") "</option>\n"
+ "<option value=\"y\">" N_("Yellow") "</option>\n"
+ "</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Background blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "</param>\n"
+ "<param name=\"alpha\" gui-text=\"" N_("Channel to alpha") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Extract color channel as a transparent image") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ExtractChannel());
+ // clang-format on
+ };
+};
+
+gchar const *
+ExtractChannel::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream blend;
+ std::ostringstream colors;
+
+ blend << ext->get_param_optiongroup("blend");
+
+ const gchar *channel = ext->get_param_optiongroup("source");
+ if (ext->get_param_bool("alpha")) {
+ if ((g_ascii_strcasecmp("r", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0";
+ } else if ((g_ascii_strcasecmp("g", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0";
+ } else if ((g_ascii_strcasecmp("b", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0";
+ } else if ((g_ascii_strcasecmp("c", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 0 0 1 0";
+ } else if ((g_ascii_strcasecmp("m", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 0 1 0";
+ } else {
+ colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1 1 0";
+ }
+ } else {
+ if ((g_ascii_strcasecmp("r", channel) == 0)) {
+ colors << "0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0";
+ } else if ((g_ascii_strcasecmp("g", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0";
+ } else if ((g_ascii_strcasecmp("b", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0";
+ } else if ((g_ascii_strcasecmp("c", channel) == 0)) {
+ colors << "0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 -1 0 0 1 0";
+ } else if ((g_ascii_strcasecmp("m", channel) == 0)) {
+ colors << "0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 -1 0 1 0";
+ } else {
+ colors << "0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 -1 1 0";
+ }
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Extract Channel\">\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"%s 0 \" result=\"colormatrix\" />\n"
+ "<feBlend in2=\"BackgroundImage\" mode=\"%s\" result=\"blend\" />\n"
+ "</filter>\n", colors.str().c_str(), blend.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* ExtractChannel filter */
+
+/**
+ \brief Custom predefined Fade to Black or White filter.
+
+ Fade to black or white.
+
+ Filter's parameters:
+ * Level (0.->1., default 1.) -> colorMatrix (RVB entries)
+ * Fade to (enum [black|white], default black) -> colorMatrix (RVB entries)
+
+ Matrix
+ black white
+ Lv 0 0 0 0 Lv 0 0 1-lv 0
+ 0 Lv 0 0 0 0 Lv 0 1-lv 0
+ 0 0 Lv 0 0 0 0 Lv 1-lv 0
+ 0 0 0 1 0 0 0 0 1 0
+*/
+class FadeToBW : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ FadeToBW ( ) : Filter() { };
+ ~FadeToBW ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Fade to Black or White") "</name>\n"
+ "<id>org.inkscape.effect.filter.FadeToBW</id>\n"
+ "<param name=\"level\" gui-text=\"" N_("Level") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n"
+ "<param name=\"fadeto\" gui-text=\"" N_("Fade to:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"black\">" N_("Black") "</option>\n"
+ "<option value=\"white\">" N_("White") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Fade to black or white") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new FadeToBW());
+ // clang-format on
+ };
+};
+
+gchar const *
+FadeToBW::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream level;
+ std::ostringstream wlevel;
+
+ level << ext->get_param_float("level");
+
+ const gchar *fadeto = ext->get_param_optiongroup("fadeto");
+ if ((g_ascii_strcasecmp("white", fadeto) == 0)) {
+ // White
+ wlevel << (1 - ext->get_param_float("level"));
+ } else {
+ // Black
+ wlevel << "0";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Fade to Black or White\">\n"
+ "<feColorMatrix values=\"%s 0 0 0 %s 0 %s 0 0 %s 0 0 %s 0 %s 0 0 0 1 0\" />\n"
+ "</filter>\n", level.str().c_str(), wlevel.str().c_str(),
+ level.str().c_str(), wlevel.str().c_str(),
+ level.str().c_str(), wlevel.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Fade to black or white filter */
+
+/**
+ \brief Custom predefined Greyscale filter.
+
+ Customize greyscale components.
+
+ Filter's parameters:
+ * Red (-10.->10., default .21) -> colorMatrix (values)
+ * Green (-10.->10., default .72) -> colorMatrix (values)
+ * Blue (-10.->10., default .072) -> colorMatrix (values)
+ * Lightness (-10.->10., default 0.) -> colorMatrix (values)
+ * Transparent (boolean, default false) -> matrix structure
+
+ Matrix:
+ normal transparency
+ R G B St 0 0 0 0 0 0
+ R G B St 0 0 0 0 0 0
+ R G B St 0 0 0 0 0 0
+ 0 0 0 1 0 R G B 1-St 0
+*/
+class Greyscale : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Greyscale ( ) : Filter() { };
+ ~Greyscale ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Greyscale") "</name>\n"
+ "<id>org.inkscape.effect.filter.Greyscale</id>\n"
+ "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0.21</param>\n"
+ "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0.72</param>\n"
+ "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0.072</param>\n"
+ "<param name=\"strength\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.00\" max=\"10.00\">0</param>\n"
+ "<param name=\"transparent\" gui-text=\"" N_("Transparent") "\" type=\"bool\" >false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Customize greyscale components") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Greyscale());
+ // clang-format on
+ };
+};
+
+gchar const *
+Greyscale::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream red;
+ std::ostringstream green;
+ std::ostringstream blue;
+ std::ostringstream strength;
+ std::ostringstream redt;
+ std::ostringstream greent;
+ std::ostringstream bluet;
+ std::ostringstream strengtht;
+ std::ostringstream transparency;
+ std::ostringstream line;
+
+ red << ext->get_param_float("red");
+ green << ext->get_param_float("green");
+ blue << ext->get_param_float("blue");
+ strength << ext->get_param_float("strength");
+
+ redt << - ext->get_param_float("red");
+ greent << - ext->get_param_float("green");
+ bluet << - ext->get_param_float("blue");
+ strengtht << 1 - ext->get_param_float("strength");
+
+ if (ext->get_param_bool("transparent")) {
+ line << "0 0 0 0";
+ transparency << redt.str().c_str() << " " << greent.str().c_str() << " " << bluet.str().c_str() << " " << strengtht.str().c_str();
+ } else {
+ line << red.str().c_str() << " " << green.str().c_str() << " " << blue.str().c_str() << " " << strength.str().c_str();
+ transparency << "0 0 0 1";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Greyscale\">\n"
+ "<feColorMatrix values=\"%s 0 %s 0 %s 0 %s 0 \" />\n"
+ "</filter>\n", line.str().c_str(), line.str().c_str(), line.str().c_str(), transparency.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Greyscale filter */
+
+/**
+ \brief Custom predefined Invert filter.
+
+ Manage hue, lightness and transparency inversions
+
+ Filter's parameters:
+ * Invert hue (boolean, default false) -> color1 (values, true: 180, false: 0)
+ * Invert lightness (boolean, default false) -> color1 (values, true: 180, false: 0; XOR with Invert hue),
+ color2 (values: from a00 to a22, if 1, set -1 and set 1 in ax4, if -1, set 1 and set 0 in ax4)
+ * Invert transparency (boolean, default false) -> color2 (values: negate a30, a31 and a32, substract 1 from a33)
+ * Invert channels (enum, default Red and blue) -> color2 (values -for R&B: swap ax0 and ax2 in the first 3 lines)
+ * Light transparency (0.->1., default 0.) -> color2 (values: a33=a33-x)
+*/
+
+class Invert : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Invert ( ) : Filter() { };
+ ~Invert ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Invert") "</name>\n"
+ "<id>org.inkscape.effect.filter.Invert</id>\n"
+ "<param name=\"channels\" gui-text=\"" N_("Invert channels:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"0\">" N_("No inversion") "</option>\n"
+ "<option value=\"1\">" N_("Red and blue") "</option>\n"
+ "<option value=\"2\">" N_("Red and green") "</option>\n"
+ "<option value=\"3\">" N_("Green and blue") "</option>\n"
+ "</param>\n"
+ "<param name=\"opacify\" gui-text=\"" N_("Light transparency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1\">0</param>\n"
+ "<param name=\"hue\" gui-text=\"" N_("Invert hue") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"lightness\" gui-text=\"" N_("Invert lightness") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"transparency\" gui-text=\"" N_("Invert transparency") "\" type=\"bool\" >false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Manage hue, lightness and transparency inversions") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Invert());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Invert::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream line1;
+ std::ostringstream line2;
+ std::ostringstream line3;
+
+ std::ostringstream col5;
+ std::ostringstream transparency;
+ std::ostringstream hue;
+
+ if (ext->get_param_bool("hue") ^ ext->get_param_bool("lightness")) {
+ hue << "<feColorMatrix type=\"hueRotate\" values=\"180\" result=\"color1\" />\n";
+ } else {
+ hue << "";
+ }
+
+ if (ext->get_param_bool("transparency")) {
+ transparency << "0.21 0.72 0.07 " << 1 - ext->get_param_float("opacify");
+ } else {
+ transparency << "-0.21 -0.72 -0.07 " << 2 - ext->get_param_float("opacify");
+ }
+
+ if (ext->get_param_bool("lightness")) {
+ switch (atoi(ext->get_param_optiongroup("channels"))) {
+ case 1:
+ line1 << "0 0 -1";
+ line2 << "0 -1 0";
+ line3 << "-1 0 0";
+ break;
+ case 2:
+ line1 << "0 -1 0";
+ line2 << "-1 0 0";
+ line3 << "0 0 -1";
+ break;
+ case 3:
+ line1 << "-1 0 0";
+ line2 << "0 0 -1";
+ line3 << "0 -1 0";
+ break;
+ default:
+ line1 << "-1 0 0";
+ line2 << "0 -1 0";
+ line3 << "0 0 -1";
+ break;
+ }
+ col5 << "1";
+ } else {
+ switch (atoi(ext->get_param_optiongroup("channels"))) {
+ case 1:
+ line1 << "0 0 1";
+ line2 << "0 1 0";
+ line3 << "1 0 0";
+ break;
+ case 2:
+ line1 << "0 1 0";
+ line2 << "1 0 0";
+ line3 << "0 0 1";
+ break;
+ case 3:
+ line1 << "1 0 0";
+ line2 << "0 0 1";
+ line3 << "0 1 0";
+ break;
+ default:
+ line1 << "1 0 0";
+ line2 << "0 1 0";
+ line3 << "0 0 1";
+ break;
+ }
+ col5 << "0";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Invert\">\n"
+ "%s"
+ "<feColorMatrix values=\"%s 0 %s %s 0 %s %s 0 %s %s 0 \" result=\"color2\" />\n"
+ "</filter>\n", hue.str().c_str(),
+ line1.str().c_str(), col5.str().c_str(),
+ line2.str().c_str(), col5.str().c_str(),
+ line3.str().c_str(), col5.str().c_str(),
+ transparency.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Invert filter */
+
+/**
+ \brief Custom predefined Lighting filter.
+
+ Modify lights and shadows separately.
+
+ Filter's parameters:
+ * Lightness (0.->20., default 1.) -> component (amplitude)
+ * Shadow (0.->20., default 1.) -> component (exponent)
+ * Offset (-1.->1., default 0.) -> component (offset)
+*/
+class Lighting : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Lighting ( ) : Filter() { };
+ ~Lighting ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Lighting") "</name>\n"
+ "<id>org.inkscape.effect.filter.Lighting</id>\n"
+ "<param name=\"amplitude\" gui-text=\"" N_("Lights") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"20.00\">1</param>\n"
+ "<param name=\"exponent\" gui-text=\"" N_("Shadows") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"20.00\">1</param>\n"
+ "<param name=\"offset\" gui-text=\"" N_("Offset") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-1.00\" max=\"1.00\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Modify lights and shadows separately") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Lighting());
+ // clang-format on
+ };
+};
+
+gchar const *
+Lighting::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream amplitude;
+ std::ostringstream exponent;
+ std::ostringstream offset;
+
+ amplitude << ext->get_param_float("amplitude");
+ exponent << ext->get_param_float("exponent");
+ offset << ext->get_param_float("offset");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Lighting\">\n"
+ "<feComponentTransfer in=\"blur\" result=\"component\" >\n"
+ "<feFuncR type=\"gamma\" amplitude=\"%s\" exponent=\"%s\" offset=\"%s\" />\n"
+ "<feFuncG type=\"gamma\" amplitude=\"%s\" exponent=\"%s\" offset=\"%s\" />\n"
+ "<feFuncB type=\"gamma\" amplitude=\"%s\" exponent=\"%s\" offset=\"%s\" />\n"
+ "</feComponentTransfer>\n"
+ "</filter>\n", amplitude.str().c_str(), exponent.str().c_str(), offset.str().c_str(),
+ amplitude.str().c_str(), exponent.str().c_str(), offset.str().c_str(),
+ amplitude.str().c_str(), exponent.str().c_str(), offset.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Lighting filter */
+
+/**
+ \brief Custom predefined Lightness-Contrast filter.
+
+ Modify lightness and contrast separately.
+
+ Filter's parameters:
+ * Lightness (0.->100., default 0.) -> colorMatrix
+ * Contrast (0.->100., default 0.) -> colorMatrix
+
+ Matrix:
+ Co/10 0 0 1+(Co-1)*Li/2000 -(Co-1)/20
+ 0 Co/10 0 1+(Co-1)*Li/2000 -(Co-1)/20
+ 0 0 Co/10 1+(Co-1)*Li/2000 -(Co-1)/20
+ 0 0 0 1 0
+*/
+class LightnessContrast : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ LightnessContrast ( ) : Filter() { };
+ ~LightnessContrast ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Lightness-Contrast") "</name>\n"
+ "<id>org.inkscape.effect.filter.LightnessContrast</id>\n"
+ "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-100\" max=\"100\">0</param>\n"
+ "<param name=\"contrast\" gui-text=\"" N_("Contrast") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-100\" max=\"100\">0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Modify lightness and contrast separately") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new LightnessContrast());
+ // clang-format on
+ };
+};
+
+gchar const *
+LightnessContrast::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream lightness;
+ std::ostringstream contrast;
+ std::ostringstream contrast5;
+
+ double c5;
+ if (ext->get_param_float("contrast") > 0) {
+ contrast << (1 + ext->get_param_float("contrast") / 10);
+ c5 = (- ext->get_param_float("contrast") / 20);
+ } else {
+ contrast << (1 + ext->get_param_float("contrast") / 100);
+ c5 =(- ext->get_param_float("contrast") / 200);
+ }
+
+ contrast5 << c5;
+ lightness << ((1 - c5) * ext->get_param_float("lightness") / 100);
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Lightness-Contrast\">\n"
+ "<feColorMatrix values=\"%s 0 0 %s %s 0 %s 0 %s %s 0 0 %s %s %s 0 0 0 1 0\" />\n"
+ "</filter>\n", contrast.str().c_str(), lightness.str().c_str(), contrast5.str().c_str(),
+ contrast.str().c_str(), lightness.str().c_str(), contrast5.str().c_str(),
+ contrast.str().c_str(), lightness.str().c_str(), contrast5.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Lightness-Contrast filter */
+
+/**
+ \brief Custom predefined Nudge RGB filter.
+
+ Nudge RGB channels separately and blend them to different types of backgrounds
+
+ Filter's parameters:
+ Offsets
+ * Red
+ * x (-100.->100., default -6) -> offset1 (dx)
+ * y (-100.->100., default -6) -> offset1 (dy)
+ * Green
+ * x (-100.->100., default 6) -> offset2 (dx)
+ * y (-100.->100., default 7) -> offset2 (dy)
+ * Blue
+ * x (-100.->100., default 1) -> offset3 (dx)
+ * y (-100.->100., default -16) -> offset3 (dy)
+ Color
+ * Background color (guint, default 255)-> flood (flood-color, flood-opacity)
+
+*/
+class NudgeRGB : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ NudgeRGB ( ) : Filter() { };
+ ~NudgeRGB ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Nudge RGB") "</name>\n"
+ "<id>org.inkscape.effect.filter.NudgeRGB</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"offsettab\" gui-text=\"Offset\">\n"
+ "<label appearance=\"header\">" N_("Red offset") "</label>\n"
+ "<param name=\"rx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n"
+ "<param name=\"ry\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n"
+ "<label appearance=\"header\">" N_("Green offset") "</label>\n"
+ "<param name=\"gx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">6</param>\n"
+ "<param name=\"gy\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">7</param>\n"
+ "<label appearance=\"header\">" N_("Blue offset") "</label>\n"
+ "<param name=\"bx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">1</param>\n"
+ "<param name=\"by\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-16</param>\n"
+ "</page>\n"
+ "<page name=\"coltab\" gui-text=\"Color\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Background color") "\" type=\"color\">255</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Nudge RGB channels separately and blend them to different types of backgrounds") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new NudgeRGB());
+ // clang-format on
+ };
+};
+
+gchar const *
+NudgeRGB::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream rx;
+ std::ostringstream ry;
+ std::ostringstream gx;
+ std::ostringstream gy;
+ std::ostringstream bx;
+ std::ostringstream by;
+
+ std::ostringstream a;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+
+ rx << ext->get_param_float("rx");
+ ry << ext->get_param_float("ry");
+ gx << ext->get_param_float("gx");
+ gy << ext->get_param_float("gy");
+ bx << ext->get_param_float("bx");
+ by << ext->get_param_float("by");
+
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Nudge RGB\">\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 \" result=\"colormatrix1\" />\n"
+ "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset1\" />\n"
+ "<feBlend in2=\"flood\" mode=\"screen\" result=\"blend1\" />\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 \" result=\"colormatrix2\" />\n"
+ "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset2\" />\n"
+ "<feBlend in2=\"blend1\" mode=\"screen\" result=\"blend2\" />\n"
+ "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset3\" />\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 \" result=\"colormatrix3\" />\n"
+ "<feBlend in2=\"offset3\" mode=\"screen\" result=\"blend3\" />\n"
+ "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ rx.str().c_str(), ry.str().c_str(),
+ gx.str().c_str(), gy.str().c_str(),
+ bx.str().c_str(), by.str().c_str() );
+ // clang-format on
+
+ return _filter;
+
+}; /* Nudge RGB filter */
+
+/**
+ \brief Custom predefined Nudge CMY filter.
+
+ Nudge CMY channels separately and blend them to different types of backgrounds
+
+ Filter's parameters:
+ Offsets
+ * Cyan
+ * x (-100.->100., default -6) -> offset1 (dx)
+ * y (-100.->100., default -6) -> offset1 (dy)
+ * Magenta
+ * x (-100.->100., default 6) -> offset2 (dx)
+ * y (-100.->100., default 7) -> offset2 (dy)
+ * Yellow
+ * x (-100.->100., default 1) -> offset3 (dx)
+ * y (-100.->100., default -16) -> offset3 (dy)
+ Color
+ * Background color (guint, default -1)-> flood (flood-color, flood-opacity)
+*/
+class NudgeCMY : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ NudgeCMY ( ) : Filter() { };
+ ~NudgeCMY ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Nudge CMY") "</name>\n"
+ "<id>org.inkscape.effect.filter.NudgeCMY</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"offsettab\" gui-text=\"Offset\">\n"
+ "<label appearance=\"header\">" N_("Cyan offset") "</label>\n"
+ "<param name=\"cx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n"
+ "<param name=\"cy\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-6</param>\n"
+ "<label appearance=\"header\">" N_("Magenta offset") "</label>\n"
+ "<param name=\"mx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">6</param>\n"
+ "<param name=\"my\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">7</param>\n"
+ "<label appearance=\"header\">" N_("Yellow offset") "</label>\n"
+ "<param name=\"yx\" gui-text=\"" N_("X") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">1</param>\n"
+ "<param name=\"yy\" gui-text=\"" N_("Y") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-100.\" max=\"100.\">-16</param>\n"
+ "</page>\n"
+ "<page name=\"coltab\" gui-text=\"Color\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Background color") "\" type=\"color\">-1</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Nudge CMY channels separately and blend them to different types of backgrounds") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new NudgeCMY());
+ // clang-format on
+ };
+};
+
+gchar const *
+NudgeCMY::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream cx;
+ std::ostringstream cy;
+ std::ostringstream mx;
+ std::ostringstream my;
+ std::ostringstream yx;
+ std::ostringstream yy;
+
+ std::ostringstream a;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+
+ cx << ext->get_param_float("cx");
+ cy << ext->get_param_float("cy");
+ mx << ext->get_param_float("mx");
+ my << ext->get_param_float("my");
+ yx << ext->get_param_float("yx");
+ yy << ext->get_param_float("yy");
+
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Nudge CMY\">\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 -1 0 0 1 0 \" result=\"colormatrix1\" />\n"
+ "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset1\" />\n"
+ "<feBlend in2=\"flood\" mode=\"multiply\" result=\"blend1\" />\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 -1 0 1 0 \" result=\"colormatrix2\" />\n"
+ "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset2\" />\n"
+ "<feBlend in2=\"blend1\" mode=\"multiply\" result=\"blend2\" />\n"
+ "<feOffset dy=\"%s\" dx=\"%s\" result=\"offset3\" />\n"
+ "<feColorMatrix in=\"SourceGraphic\" values=\"0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 -1 1 0 \" result=\"colormatrix3\" />\n"
+ "<feBlend in2=\"offset3\" mode=\"multiply\" result=\"blend3\" />\n"
+ "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ cx.str().c_str(), cy.str().c_str(),
+ mx.str().c_str(), my.str().c_str(),
+ yx.str().c_str(), yy.str().c_str() );
+ // clang-format on
+
+ return _filter;
+
+}; /* Nudge CMY filter */
+
+/**
+ \brief Custom predefined Quadritone filter.
+
+ Replace hue by two colors.
+
+ Filter's parameters:
+ * Hue distribution (0->360, default 280) -> colormatrix1 (values)
+ * Colors (0->360, default 100) -> colormatrix3 (values)
+ * Blend mode 1 (enum, default Normal) -> blend1 (mode)
+ * Over-saturation (0.->1., default 0) -> composite1 (k2)
+ * Blend mode 2 (enum, default Normal) -> blend2 (mode)
+*/
+
+class Quadritone : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Quadritone ( ) : Filter() { };
+ ~Quadritone ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Quadritone Fantasy") "</name>\n"
+ "<id>org.inkscape.effect.filter.Quadritone</id>\n"
+ "<param name=\"dist\" gui-text=\"" N_("Hue distribution (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">280</param>\n"
+ "<param name=\"colors\" gui-text=\"" N_("Colors") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">100</param>\n"
+ "<param name=\"blend1\" gui-text=\"" N_("Blend 1:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "</param>\n"
+ "<param name=\"sat\" gui-text=\"" N_("Over-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"1.00\">0</param>\n"
+ "<param name=\"blend2\" gui-text=\"" N_("Blend 2:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Replace hue by two colors") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Quadritone());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Quadritone::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream dist;
+ std::ostringstream colors;
+ std::ostringstream blend1;
+ std::ostringstream sat;
+ std::ostringstream blend2;
+
+ dist << ext->get_param_int("dist");
+ colors << ext->get_param_int("colors");
+ blend1 << ext->get_param_optiongroup("blend1");
+ sat << ext->get_param_float("sat");
+ blend2 << ext->get_param_optiongroup("blend2");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Quadritone fantasy\">\n"
+ "<feColorMatrix in=\"SourceGraphic\" type=\"hueRotate\" values=\"%s\" result=\"colormatrix1\" />\n"
+ "<feColorMatrix type=\"matrix\" values=\"0.5 0 0.5 0 0 0 1 0 0 0 0.5 0 0.5 0 0 0 0 0 1 0 \" result=\"colormatrix2\" />\n"
+ "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"colormatrix3\" />\n"
+ "<feBlend in2=\"colormatrix3\" mode=\"%s\" result=\"blend1\" />\n"
+ "<feColorMatrix type=\"matrix\" values=\"2.5 -0.75 -0.75 0 0 -0.75 2.5 -0.75 0 0 -0.75 -0.75 2.5 0 0 0 0 0 1 0 \" result=\"colormatrix4\" />\n"
+ "<feComposite in=\"colormatrix4\" in2=\"blend1\" operator=\"arithmetic\" k2=\"%s\" result=\"composite1\" />\n"
+ "<feBlend in2=\"blend1\" mode=\"%s\" result=\"blend2\" />\n"
+ "</filter>\n", dist.str().c_str(), colors.str().c_str(), blend1.str().c_str(), sat.str().c_str(), blend2.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Quadritone filter */
+
+
+/**
+ \brief Custom predefined Simple blend filter.
+
+ Simple blend filter.
+
+ Filter's parameters:
+ * Color (guint, default 16777215) -> flood1 (flood-opacity, flood-color)
+ * Blend mode (enum, default Hue) -> blend1 (mode)
+*/
+class SimpleBlend : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ SimpleBlend ( ) : Filter() { };
+ ~SimpleBlend ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Simple blend") "</name>\n"
+ "<id>org.inkscape.effect.filter.SimpleBlend</id>\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">16777215</param>\n"
+ "<param name=\"blendmode\" gui-text=\"" N_("Blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"saturation\">" N_("Saturation") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "<option value=\"difference\">" N_("Difference") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "<option value=\"luminosity\">" N_("Luminosity") "</option>\n"
+ "<option value=\"overlay\">" N_("Overlay") "</option>\n"
+ "<option value=\"color-dodge\">" N_("Color Dodge") "</option>\n"
+ "<option value=\"color-burn\">" N_("Color Burn") "</option>\n"
+ "<option value=\"color\">" N_("Color") "</option>\n"
+ "<option value=\"hard-light\">" N_("Hard Light") "</option>\n"
+ "<option value=\"hue\">" N_("Hue") "</option>\n"
+ "<option value=\"exclusion\">" N_("Exclusion") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Simple blend filter") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new SimpleBlend());
+ // clang-format on
+ };
+};
+
+gchar const *
+SimpleBlend::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream a;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream blend;
+
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+ blend << ext->get_param_optiongroup("blendmode");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Simple blend\">\n"
+ "<feFlood result=\"flood1\" flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" />\n"
+ "<feBlend result=\"blend1\" in=\"flood1\" in2=\"SourceGraphic\" mode=\"%s\" />\n"
+ "<feComposite operator=\"in\" in=\"blend1\" in2=\"SourceGraphic\" />\n"
+ "</filter>\n", r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ a.str().c_str(), blend.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* SimpleBlend filter */
+
+/**
+ \brief Custom predefined Solarize filter.
+
+ Classic photographic solarization effect.
+
+ Filter's parameters:
+ * Type (enum, default "Solarize") ->
+ Solarize = blend1 (mode="darken"), blend2 (mode="screen")
+ Moonarize = blend1 (mode="lighten"), blend2 (mode="multiply") [No other access to the blend modes]
+ * Hue rotation (0->360, default 0) -> colormatrix1 (values)
+*/
+
+
+class Solarize : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Solarize ( ) : Filter() { };
+ ~Solarize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Solarize") "</name>\n"
+ "<id>org.inkscape.effect.filter.Solarize</id>\n"
+ "<param name=\"rotate\" gui-text=\"" N_("Hue rotation (°)") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">0</param>\n"
+ "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"solarize\">" N_("Solarize") "</option>\n"
+ "<option value=\"moonarize\">" N_("Moonarize") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Classic photographic solarization effect") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Solarize());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Solarize::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream rotate;
+ std::ostringstream blend1;
+ std::ostringstream blend2;
+
+ rotate << ext->get_param_int("rotate");
+ const gchar *type = ext->get_param_optiongroup("type");
+ if ((g_ascii_strcasecmp("solarize", type) == 0)) {
+ // Solarize
+ blend1 << "darken";
+ blend2 << "screen";
+ } else {
+ // Moonarize
+ blend1 << "lighten";
+ blend2 << "multiply";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Solarize\">\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 1 \" />\n"
+ "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"colormatrix2\" />\n"
+ "<feColorMatrix in=\"colormatrix2\" values=\"-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0 \" result=\"colormatrix3\" />\n"
+ "<feBlend in=\"colormatrix3\" in2=\"colormatrix2\" mode=\"%s\" result=\"blend1\" />\n"
+ "<feBlend in2=\"blend1\" mode=\"%s\" result=\"blend2\" />\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" />\n"
+ "</filter>\n", rotate.str().c_str(), blend1.str().c_str(), blend2.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Solarize filter */
+
+/**
+ \brief Custom predefined Tritone filter.
+
+ Create a custom tritone palette with additional glow, blend modes and hue moving.
+
+ Filter's parameters:
+ * Option (enum, default Normal) ->
+ Normal = composite1 (in2="flood"), composite2 (in="p", in2="blend6"), blend6 (in2="composite1")
+ Enhance hue = Normal + composite2 (in="SourceGraphic")
+ Phosphorescence = Normal + blend6 (in2="SourceGraphic") composite2 (in="blend6", in2="composite1")
+ PhosphorescenceB = Normal + blend6 (in2="flood") composite1 (in2="SourceGraphic")
+ Hue to background = Normal + composite1 (in2="BackgroundImage") [a template with an activated background is needed, or colors become black]
+ * Hue distribution (0->360, default 0) -> colormatrix1 (values)
+ * Colors (guint, default -73203457) -> flood (flood-opacity, flood-color)
+ * Global blend (enum, default Lighten) -> blend5 (mode) [Multiply, Screen, Darken, Lighten only!]
+ * Glow (0.01->10., default 0.01) -> blur (stdDeviation)
+ * Glow & blend (enum, default Normal) -> blend6 (mode) [Normal, Multiply and Darken only!]
+ * Local light (0.->10., default 0) -> composite2 (k1)
+ * Global light (0.->10., default 1) -> composite2 (k3) [k2 must be fixed to 1].
+*/
+
+class Tritone : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Tritone ( ) : Filter() { };
+ ~Tritone ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Tritone") "</name>\n"
+ "<id>org.inkscape.effect.filter.Tritone</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"enhue\">" N_("Enhance hue") "</option>\n"
+ "<option value=\"phospho\">" N_("Phosphorescence") "</option>\n"
+ "<option value=\"phosphoB\">" N_("Colored nights") "</option>\n"
+ "<option value=\"htb\">" N_("Hue to background") "</option>\n"
+ "</param>\n"
+ "<param name=\"globalblend\" gui-text=\"" N_("Global blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "</param>\n"
+ "<param name=\"glow\" gui-text=\"" N_("Glow") "\" type=\"float\" appearance=\"full\" min=\"0.01\" max=\"10\">0.01</param>\n"
+ "<param name=\"glowblend\" gui-text=\"" N_("Glow blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "</param>\n"
+ "<param name=\"llight\" gui-text=\"" N_("Local light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">0</param>\n"
+ "<param name=\"glight\" gui-text=\"" N_("Global light") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"10\">1</param>\n"
+ "</page>\n"
+ "<page name=\"co1tab\" gui-text=\"Color\">\n"
+ "<param name=\"dist\" gui-text=\"" N_("Hue distribution (°):") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"360\">0</param>\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">-73203457</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Color") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Create a custom tritone palette with additional glow, blend modes and hue moving") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Tritone());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Tritone::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream dist;
+ std::ostringstream a;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream globalblend;
+ std::ostringstream glow;
+ std::ostringstream glowblend;
+ std::ostringstream llight;
+ std::ostringstream glight;
+ std::ostringstream c1in2;
+ std::ostringstream c2in;
+ std::ostringstream c2in2;
+ std::ostringstream b6in2;
+
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+ globalblend << ext->get_param_optiongroup("globalblend");
+ dist << ext->get_param_int("dist");
+ glow << ext->get_param_float("glow");
+ glowblend << ext->get_param_optiongroup("glowblend");
+ llight << ext->get_param_float("llight");
+ glight << ext->get_param_float("glight");
+
+ const gchar *type = ext->get_param_optiongroup("type");
+ if ((g_ascii_strcasecmp("enhue", type) == 0)) {
+ // Enhance hue
+ c1in2 << "flood";
+ c2in << "SourceGraphic";
+ c2in2 << "blend6";
+ b6in2 << "composite1";
+ } else if ((g_ascii_strcasecmp("phospho", type) == 0)) {
+ // Phosphorescence
+ c1in2 << "flood";
+ c2in << "blend6";
+ c2in2 << "composite1";
+ b6in2 << "SourceGraphic";
+ } else if ((g_ascii_strcasecmp("phosphoB", type) == 0)) {
+ // Phosphorescence B
+ c1in2 << "SourceGraphic";
+ c2in << "blend6";
+ c2in2 << "composite1";
+ b6in2 << "flood";
+ } else if ((g_ascii_strcasecmp("htb", type) == 0)) {
+ // Hue to background
+ c1in2 << "BackgroundImage";
+ c2in << "blend2";
+ c2in2 << "blend6";
+ b6in2 << "composite1";
+ } else {
+ // Normal
+ c1in2 << "flood";
+ c2in << "blend2";
+ c2in2 << "blend6";
+ b6in2 << "composite";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Tritone\">\n"
+ "<feColorMatrix type=\"hueRotate\" values=\"%s\" result=\"colormatrix1\" />\n"
+ "<feColorMatrix in=\"colormatrix1\" type=\"matrix\" values=\"1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1 \" result=\"colormatrix2\" />\n"
+ "<feColorMatrix in=\"colormatrix1\" type=\"matrix\" values=\"0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 \" result=\"colormatrix3\" />\n"
+ "<feColorMatrix in=\"colormatrix1\" type=\"matrix\" values=\"0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 1 \" result=\"colormatrix4\" />\n"
+ "<feBlend in=\"colormatrix2\" in2=\"colormatrix3\" mode=\"darken\" result=\"blend1\" />\n"
+ "<feBlend in=\"blend1\" in2=\"colormatrix4\" mode=\"darken\" result=\"blend2\" />\n"
+ "<feBlend in=\"colormatrix2\" in2=\"colormatrix3\" mode=\"lighten\" result=\"blend3\" />\n"
+ "<feBlend in=\"blend3\" in2=\"colormatrix4\" mode=\"lighten\" result=\"blend4\" />\n"
+ "<feComponentTransfer in=\"blend4\" result=\"componentTransfer\">\n"
+ "<feFuncR type=\"linear\" slope=\"0\" />\n"
+ "</feComponentTransfer>\n"
+ "<feBlend in=\"blend2\" in2=\"componentTransfer\" mode=\"%s\" result=\"blend5\" />\n"
+ "<feColorMatrix in=\"blend5\" type=\"matrix\" values=\"-1 1 0 0 0 -1 1 0 0 0 -1 1 0 0 0 0 0 0 0 1 \" result=\"colormatrix5\" />\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feComposite in=\"colormatrix5\" in2=\"%s\" operator=\"arithmetic\" k1=\"1\" result=\"composite1\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feBlend in2=\"%s\" mode=\"%s\" result=\"blend6\" />\n"
+ "<feComposite in=\"%s\" in2=\"%s\" operator=\"arithmetic\" k1=\"%s\" k2=\"1\" k3=\"%s\" k4=\"0\" result=\"composite2\" />\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite3\" />\n"
+ "</filter>\n", dist.str().c_str(), globalblend.str().c_str(),
+ a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ c1in2.str().c_str(), glow.str().c_str(), b6in2.str().c_str(), glowblend.str().c_str(),
+ c2in.str().c_str(), c2in2.str().c_str(), llight.str().c_str(), glight.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Tritone filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'COLOR' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_COLOR_H__ */
diff --git a/src/extension/internal/filter/distort.h b/src/extension/internal/filter/distort.h
new file mode 100644
index 0000000..c27dba5
--- /dev/null
+++ b/src/extension/internal/filter/distort.h
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_DISTORT_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_DISTORT_H__
+/* Change the 'DISTORT' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Distort filters
+ * Felt Feather
+ * Roughen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined FeltFeather filter.
+
+ Blur and displace edges of shapes and pictures
+
+ Filter's parameters:
+ * Type (enum, default "In") ->
+ in = map (in="composite3")
+ out = map (in="blur")
+ * Horizontal blur (0.01->30., default 15) -> blur (stdDeviation)
+ * Vertical blur (0.01->30., default 15) -> blur (stdDeviation)
+ * Dilatation (n-1th value, 0.->100., default 1) -> colormatrix (matrix)
+ * Erosion (nth value, 0.->100., default 0) -> colormatrix (matrix)
+ * Stroke (enum, default "Normal") ->
+ Normal = composite4 (operator="atop")
+ Wide = composite4 (operator="over")
+ Narrow = composite4 (operator="in")
+ No fill = composite4 (operator="xor")
+ * Roughness (group)
+ * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type)
+ * Horizontal frequency (0.001->1., default 0.05) -> turbulence (baseFrequency [/100])
+ * Vertical frequency (0.001->1., default 0.05) -> turbulence (baseFrequency [/100])
+ * Complexity (1->5, default 3) -> turbulence (numOctaves)
+ * Variation (0->100, default 0) -> turbulence (seed)
+ * Intensity (0.0->100., default 30) -> displacement (scale)
+*/
+
+class FeltFeather : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ FeltFeather ( ) : Filter() { };
+ ~FeltFeather ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Felt Feather") "</name>\n"
+ "<id>org.inkscape.effect.filter.FeltFeather</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"in\">" N_("In") "</option>\n"
+ "<option value=\"out\">" N_("Out") "</option>\n"
+ "</param>\n"
+ "<param name=\"hblur\" gui-text=\"" N_("Horizontal blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">15</param>\n"
+ "<param name=\"vblur\" gui-text=\"" N_("Vertical blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">15</param>\n"
+ "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">1</param>\n"
+ "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">0</param>\n"
+ "<param name=\"stroke\" gui-text=\"" N_("Stroke:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"atop\">" N_("Normal") "</option>\n"
+ "<option value=\"over\">" N_("Wide") "</option>\n"
+ "<option value=\"in\">" N_("Narrow") "</option>\n"
+ "<option value=\"xor\">" N_("No fill") "</option>\n"
+ "</param>\n"
+ "<param name=\"turbulence\" indent=\"1\" gui-text=\"" N_("Turbulence:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n"
+ "<option value=\"turbulence\">" N_("Turbulence") "</option>\n"
+ "</param>\n"
+ "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.\">5</param>\n"
+ "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.\">5</param>\n"
+ "<param name=\"complexity\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">3</param>\n"
+ "<param name=\"variation\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"100\">0</param>\n"
+ "<param name=\"intensity\" gui-text=\"" N_("Intensity") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"100\">30</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Distort") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Blur and displace edges of shapes and pictures") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new FeltFeather());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+FeltFeather::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+
+ std::ostringstream hblur;
+ std::ostringstream vblur;
+ std::ostringstream dilat;
+ std::ostringstream erosion;
+
+ std::ostringstream turbulence;
+ std::ostringstream hfreq;
+ std::ostringstream vfreq;
+ std::ostringstream complexity;
+ std::ostringstream variation;
+ std::ostringstream intensity;
+
+ std::ostringstream map;
+ std::ostringstream stroke;
+
+ hblur << ext->get_param_float("hblur");
+ vblur << ext->get_param_float("vblur");
+ dilat << ext->get_param_float("dilat");
+ erosion << -ext->get_param_float("erosion");
+
+ turbulence << ext->get_param_optiongroup("turbulence");
+ hfreq << ext->get_param_float("hfreq") / 100;
+ vfreq << ext->get_param_float("vfreq") / 100;
+ complexity << ext->get_param_int("complexity");
+ variation << ext->get_param_int("variation");
+ intensity << ext->get_param_float("intensity");
+
+ stroke << ext->get_param_optiongroup("stroke");
+
+ const gchar *maptype = ext->get_param_optiongroup("type");
+ if (g_ascii_strcasecmp("in", maptype) == 0) {
+ map << "composite3";
+ } else {
+ map << "blur";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" x=\"-0.3\" width=\"1.6\" y=\"-0.3\" height=\"1.6\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Felt Feather\">\n"
+ "<feGaussianBlur stdDeviation=\"%s %s\" result=\"blur\" />\n"
+ "<feComposite in=\"SourceGraphic\" in2=\"blur\" operator=\"atop\" result=\"composite1\" />\n"
+ "<feComposite in2=\"composite1\" operator=\"in\" result=\"composite2\" />\n"
+ "<feComposite in2=\"composite2\" operator=\"in\" result=\"composite3\" />\n"
+ "<feTurbulence type=\"%s\" numOctaves=\"%s\" seed=\"%s\" baseFrequency=\"%s %s\" result=\"turbulence\" />\n"
+ "<feDisplacementMap in=\"%s\" in2=\"turbulence\" xChannelSelector=\"R\" scale=\"%s\" yChannelSelector=\"G\" result=\"map\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix\" />\n"
+ "<feComposite in=\"composite3\" in2=\"colormatrix\" operator=\"%s\" result=\"composite4\" />\n"
+ "</filter>\n", hblur.str().c_str(), vblur.str().c_str(),
+ turbulence.str().c_str(), complexity.str().c_str(), variation.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(),
+ map.str().c_str(), intensity.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), stroke.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Felt feather filter */
+
+/**
+ \brief Custom predefined Roughen filter.
+
+ Small-scale roughening to edges and content
+
+ Filter's parameters:
+ * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type)
+ * Horizontal frequency (0.001->10., default 0.013) -> turbulence (baseFrequency [/100])
+ * Vertical frequency (0.001->10., default 0.013) -> turbulence (baseFrequency [/100])
+ * Complexity (1->5, default 5) -> turbulence (numOctaves)
+ * Variation (1->360, default 1) -> turbulence (seed)
+ * Intensity (0.0->50., default 6.6) -> displacement (scale)
+*/
+
+class Roughen : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Roughen ( ) : Filter() { };
+ ~Roughen ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Roughen") "</name>\n"
+ "<id>org.inkscape.effect.filter.Roughen</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n"
+ "<option value=\"turbulence\">" N_("Turbulence") "</option>\n"
+ "</param>\n"
+ "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"1000.00\">1.3</param>\n"
+ "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"1000.00\">1.3</param>\n"
+ "<param name=\"complexity\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">5</param>\n"
+ "<param name=\"variation\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"360\">0</param>\n"
+ "<param name=\"intensity\" gui-text=\"" N_("Intensity") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"50\">6.6</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Distort") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Small-scale roughening to edges and content") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Roughen());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Roughen::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream type;
+ std::ostringstream hfreq;
+ std::ostringstream vfreq;
+ std::ostringstream complexity;
+ std::ostringstream variation;
+ std::ostringstream intensity;
+
+ type << ext->get_param_optiongroup("type");
+ hfreq << ext->get_param_float("hfreq") / 100;
+ vfreq << ext->get_param_float("vfreq") / 100;
+ complexity << ext->get_param_int("complexity");
+ variation << ext->get_param_int("variation");
+ intensity << ext->get_param_float("intensity");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Roughen\">\n"
+ "<feTurbulence type=\"%s\" numOctaves=\"%s\" seed=\"%s\" baseFrequency=\"%s %s\" result=\"turbulence\" />\n"
+ "<feDisplacementMap in=\"SourceGraphic\" in2=\"turbulence\" scale=\"%s\" yChannelSelector=\"G\" xChannelSelector=\"R\" />\n"
+ "</filter>\n", type.str().c_str(), complexity.str().c_str(), variation.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), intensity.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Roughen filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'DISTORT' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_DISTORT_H__ */
diff --git a/src/extension/internal/filter/filter-all.cpp b/src/extension/internal/filter/filter-all.cpp
new file mode 100644
index 0000000..5aa3900
--- /dev/null
+++ b/src/extension/internal/filter/filter-all.cpp
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2008 Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "filter.h"
+
+/* Put your filter here */
+#include "bevels.h"
+#include "blurs.h"
+#include "bumps.h"
+#include "color.h"
+#include "distort.h"
+#include "image.h"
+#include "morphology.h"
+#include "overlays.h"
+#include "paint.h"
+#include "protrusions.h"
+#include "shadows.h"
+#include "textures.h"
+#include "transparency.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+
+void
+Filter::filters_all ( )
+{
+ // Here come the filters which are coded in C++ in order to present a parameters dialog
+
+ /* Experimental custom predefined filters */
+
+ // Bevels
+ DiffuseLight::init();
+ MatteJelly::init();
+ SpecularLight::init();
+
+ // Blurs
+ Blur::init();
+ CleanEdges::init();
+ CrossBlur::init();
+ Feather::init();
+ ImageBlur::init();
+
+ // Bumps
+ Bump::init();
+ WaxBump::init();
+
+ // Color
+ Brilliance::init();
+ ChannelPaint::init();
+ ColorBlindness::init();
+ ColorShift::init();
+ Colorize::init();
+ ComponentTransfer::init();
+ Duochrome::init();
+ ExtractChannel::init();
+ FadeToBW::init();
+ Greyscale::init();
+ Invert::init();
+ Lighting::init();
+ LightnessContrast::init();
+ NudgeRGB::init();
+ NudgeCMY::init();
+ Quadritone::init();
+ SimpleBlend::init();
+ Solarize::init();
+ Tritone::init();
+
+ // Distort
+ FeltFeather::init();
+ Roughen::init();
+
+ // Image effect
+ EdgeDetect::init();
+
+ // Image paint and draw
+ Chromolitho::init();
+ CrossEngraving::init();
+ Drawing::init();
+ Electrize::init();
+ NeonDraw::init();
+ PointEngraving::init();
+ Posterize::init();
+ PosterizeBasic::init();
+
+ // Morphology
+ Crosssmooth::init();
+ Outline::init();
+
+ // Overlays
+ NoiseFill::init();
+
+ // Protrusions
+ Snow::init();
+
+ // Shadows and glows
+ ColorizableDropShadow::init();
+
+ // Textures
+ InkBlot::init();
+
+ // Fill and transparency
+ Blend::init();
+ ChannelTransparency::init();
+ LightEraser::init();
+ Opacity::init();
+ Silhouette::init();
+
+ // Here come the rest of the filters that are read from SVG files in share/filters and
+ // .config/Inkscape/filters
+ /* This should always be last, don't put stuff below this
+ * line. */
+ Filter::filters_all_files();
+
+ return;
+}
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/internal/filter/filter-file.cpp b/src/extension/internal/filter/filter-file.cpp
new file mode 100644
index 0000000..0776350
--- /dev/null
+++ b/src/extension/internal/filter/filter-file.cpp
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2008 Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "filter.h"
+
+#include "io/sys.h"
+#include "io/resource.h"
+#include "io/stream/inkscapestream.h"
+
+/* Directory includes */
+#include "path-prefix.h"
+#include "inkscape.h"
+
+/* Extension */
+#include "extension/extension.h"
+#include "extension/system.h"
+
+/* System includes */
+#include <glibmm/i18n.h>
+#include <glibmm/fileutils.h>
+
+using namespace Inkscape::IO::Resource;
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+void
+filters_load_file (Glib::ustring filename, gchar * menuname)
+{
+ Inkscape::XML::Document *doc = sp_repr_read_file(filename.c_str(), INKSCAPE_EXTENSION_URI);
+ if (doc == nullptr) {
+ g_warning("File (%s) is not parseable as XML. Ignored.", filename.c_str());
+ return;
+ }
+
+ Inkscape::XML::Node * root = doc->root();
+ if (strcmp(root->name(), "svg:svg")) {
+ Inkscape::GC::release(doc);
+ g_warning("File (%s) is not SVG. Ignored.", filename.c_str());
+ return;
+ }
+
+ for (Inkscape::XML::Node * child = root->firstChild();
+ child != nullptr; child = child->next()) {
+ if (!strcmp(child->name(), "svg:defs")) {
+ for (Inkscape::XML::Node * defs = child->firstChild();
+ defs != nullptr; defs = defs->next()) {
+ if (!strcmp(defs->name(), "svg:filter")) {
+ Filter::filters_load_node(defs, menuname);
+ } // oh! a filter
+ } //defs
+ } // is defs
+ } // children of root
+
+ Inkscape::GC::release(doc);
+ return;
+}
+
+void Filter::filters_all_files()
+{
+ for(auto &filename: get_filenames(USER, FILTERS, {".svg"})) {
+ filters_load_file(filename, _("Personal"));
+ }
+ for(auto &filename: get_filenames(SYSTEM, FILTERS, {".svg"})) {
+ filters_load_file(filename, _("Bundled"));
+ }
+}
+
+
+#include "extension/internal/clear-n_.h"
+
+class mywriter : public Inkscape::IO::BasicWriter {
+ Glib::ustring _str;
+public:
+ void close() override;
+ void flush() override;
+ void put (char ch) override;
+ gchar const * c_str () { return _str.c_str(); }
+};
+
+void mywriter::close () { return; }
+void mywriter::flush () { return; }
+void mywriter::put (char ch) { _str += ch; }
+
+
+void
+Filter::filters_load_node (Inkscape::XML::Node *node, gchar * menuname)
+{
+ gchar const * label = node->attribute("inkscape:label");
+ gchar const * menu = node->attribute("inkscape:menu");
+ gchar const * menu_tooltip = node->attribute("inkscape:menu-tooltip");
+ gchar const * id = node->attribute("id");
+
+ if (label == nullptr) {
+ label = id;
+ }
+
+ // clang-format off
+ gchar * xml_str = g_strdup_printf(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>%s</name>\n"
+ "<id>org.inkscape.effect.filter.%s</id>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"%s\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>%s</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", label, id, menu? menu : menuname, menu_tooltip? menu_tooltip : label);
+ // clang-format on
+
+ // FIXME: Bad hack: since we pull out a single filter node out of SVG file and
+ // serialize it, it loses the namespace declarations from the root, so we must provide
+ // one right here for our inkscape attributes
+ node->setAttribute("xmlns:inkscape", SP_INKSCAPE_NS_URI);
+
+ mywriter writer;
+ sp_repr_write_stream(node, writer, 0, FALSE, g_quark_from_static_string("svg"), 0, 0);
+
+ Inkscape::Extension::build_from_mem(xml_str, new Filter(g_strdup(writer.c_str())));
+ g_free(xml_str);
+ return;
+}
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
diff --git a/src/extension/internal/filter/filter.cpp b/src/extension/internal/filter/filter.cpp
new file mode 100644
index 0000000..c9779c7
--- /dev/null
+++ b/src/extension/internal/filter/filter.cpp
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "desktop.h"
+#include "selection.h"
+#include "extension/extension.h"
+#include "extension/effect.h"
+#include "extension/system.h"
+#include "xml/repr.h"
+#include "xml/simple-node.h"
+#include "xml/attribute-record.h"
+#include "object/sp-defs.h"
+
+#include "filter.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+Filter::Filter() :
+ Inkscape::Extension::Implementation::Implementation(),
+ _filter(nullptr) {
+ return;
+}
+
+Filter::Filter(gchar const * filter) :
+ Inkscape::Extension::Implementation::Implementation(),
+ _filter(filter) {
+ return;
+}
+
+Filter::~Filter () {
+ if (_filter != nullptr) {
+ _filter = nullptr;
+ }
+
+ return;
+}
+
+bool Filter::load(Inkscape::Extension::Extension * /*module*/)
+{
+ return true;
+}
+
+Inkscape::Extension::Implementation::ImplementationDocumentCache *Filter::newDocCache(Inkscape::Extension::Extension * /*ext*/,
+ Inkscape::UI::View::View * /*doc*/)
+{
+ return nullptr;
+}
+
+gchar const *Filter::get_filter_text(Inkscape::Extension::Extension * /*ext*/)
+{
+ return _filter;
+}
+
+Inkscape::XML::Document *
+Filter::get_filter (Inkscape::Extension::Extension * ext) {
+ gchar const * filter = get_filter_text(ext);
+ return sp_repr_read_mem(filter, strlen(filter), nullptr);
+}
+
+void
+Filter::merge_filters( Inkscape::XML::Node * to, Inkscape::XML::Node * from,
+ Inkscape::XML::Document * doc,
+ gchar const * srcGraphic, gchar const * srcGraphicAlpha)
+{
+ if (from == nullptr) return;
+
+ // copy attributes
+ for ( const auto & iter : from->attributeList()) {
+ gchar const * attr = g_quark_to_string(iter.key);
+ //printf("Attribute List: %s\n", attr);
+ if (!strcmp(attr, "id")) continue; // nope, don't copy that one!
+ to->setAttribute(attr, from->attribute(attr));
+
+ if (!strcmp(attr, "in") || !strcmp(attr, "in2") || !strcmp(attr, "in3")) {
+ if (srcGraphic != nullptr && !strcmp(from->attribute(attr), "SourceGraphic")) {
+ to->setAttribute(attr, srcGraphic);
+ }
+
+ if (srcGraphicAlpha != nullptr && !strcmp(from->attribute(attr), "SourceAlpha")) {
+ to->setAttribute(attr, srcGraphicAlpha);
+ }
+ }
+ }
+
+ // for each child call recursively
+ for (Inkscape::XML::Node * from_child = from->firstChild();
+ from_child != nullptr ; from_child = from_child->next()) {
+ Glib::ustring name = "svg:";
+ name += from_child->name();
+
+ Inkscape::XML::Node * to_child = doc->createElement(name.c_str());
+ to->appendChild(to_child);
+ merge_filters(to_child, from_child, doc, srcGraphic, srcGraphicAlpha);
+
+ if (from_child == from->firstChild() && !strcmp("filter", from->name()) && srcGraphic != nullptr && to_child->attribute("in") == nullptr) {
+ to_child->setAttribute("in", srcGraphic);
+ }
+ Inkscape::GC::release(to_child);
+ }
+}
+
+#define FILTER_SRC_GRAPHIC "fbSourceGraphic"
+#define FILTER_SRC_GRAPHIC_ALPHA "fbSourceGraphicAlpha"
+
+void Filter::effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document,
+ Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ Inkscape::XML::Document *filterdoc = get_filter(module);
+ if (filterdoc == nullptr) {
+ return; // could not parse the XML source of the filter; typically parser will stderr a warning
+ }
+
+ //printf("Calling filter effect\n");
+ Inkscape::Selection * selection = ((SPDesktop *)document)->selection;
+
+ // TODO need to properly refcount the items, at least
+ std::vector<SPItem*> items(selection->items().begin(), selection->items().end());
+
+ Inkscape::XML::Document * xmldoc = document->doc()->getReprDoc();
+ Inkscape::XML::Node * defsrepr = document->doc()->getDefs()->getRepr();
+
+ for(auto spitem : items) {
+ Inkscape::XML::Node *node = spitem->getRepr();
+
+ SPCSSAttr * css = sp_repr_css_attr(node, "style");
+ gchar const * filter = sp_repr_css_property(css, "filter", nullptr);
+
+ if (filter == nullptr) {
+
+ Inkscape::XML::Node * newfilterroot = xmldoc->createElement("svg:filter");
+ merge_filters(newfilterroot, filterdoc->root(), xmldoc);
+ defsrepr->appendChild(newfilterroot);
+ document->doc()->resources_changed_signals[g_quark_from_string("filter")].emit();
+
+ Glib::ustring url = "url(#"; url += newfilterroot->attribute("id"); url += ")";
+
+
+ Inkscape::GC::release(newfilterroot);
+
+ sp_repr_css_set_property(css, "filter", url.c_str());
+ sp_repr_css_set(node, css, "style");
+ } else {
+ if (strncmp(filter, "url(#", strlen("url(#")) || filter[strlen(filter) - 1] != ')') {
+ // This is not url(#id) -- we can't handle it
+ continue;
+ }
+
+ gchar * lfilter = g_strndup(filter + 5, strlen(filter) - 6);
+ Inkscape::XML::Node * filternode = nullptr;
+ for (Inkscape::XML::Node * child = defsrepr->firstChild(); child != nullptr; child = child->next()) {
+ if (!strcmp(lfilter, child->attribute("id"))) {
+ filternode = child;
+ break;
+ }
+ }
+ g_free(lfilter);
+
+ // no filter
+ if (filternode == nullptr) {
+ g_warning("no assigned filter found!");
+ continue;
+ }
+
+ if (filternode->lastChild() == nullptr) {
+ // empty filter, we insert
+ merge_filters(filternode, filterdoc->root(), xmldoc);
+ } else {
+ // existing filter, we merge
+ filternode->lastChild()->setAttribute("result", FILTER_SRC_GRAPHIC);
+ Inkscape::XML::Node * alpha = xmldoc->createElement("svg:feColorMatrix");
+ alpha->setAttribute("result", FILTER_SRC_GRAPHIC_ALPHA);
+ alpha->setAttribute("in", FILTER_SRC_GRAPHIC); // not required, but we're being explicit
+ alpha->setAttribute("values", "0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0");
+
+ filternode->appendChild(alpha);
+
+ merge_filters(filternode, filterdoc->root(), xmldoc, FILTER_SRC_GRAPHIC, FILTER_SRC_GRAPHIC_ALPHA);
+
+ Inkscape::GC::release(alpha);
+ }
+ }
+ }
+
+ return;
+}
+
+#include "extension/internal/clear-n_.h"
+
+void
+Filter::filter_init (gchar const * id, gchar const * name, gchar const * submenu, gchar const * tip, gchar const * filter)
+{
+ // clang-format off
+ gchar * xml_str = g_strdup_printf(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>%s</name>\n"
+ "<id>org.inkscape.effect.filter.%s</id>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\" />\n"
+ "<submenu name=\"%s\"/>\n"
+ "</effects-menu>\n"
+ "<menu-tip>%s</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", name, id, submenu, tip);
+ // clang-format on
+ Inkscape::Extension::build_from_mem(xml_str, new Filter(filter));
+ g_free(xml_str);
+ return;
+}
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/filter/filter.h b/src/extension/internal/filter/filter.h
new file mode 100644
index 0000000..cb3ed36
--- /dev/null
+++ b/src/extension/internal/filter/filter.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INKSCAPE_EXTENSION_INTERNAL_FILTER_FILTER_H
+#define INKSCAPE_EXTENSION_INTERNAL_FILTER_FILTER_H
+
+/*
+ * Copyright (C) 2008 Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/i18n.h>
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+
+namespace XML {
+ struct Document;
+}
+
+namespace Extension {
+
+class Effect;
+class Extension;
+
+namespace Internal {
+namespace Filter {
+
+class Filter : public Inkscape::Extension::Implementation::Implementation {
+protected:
+ gchar const * _filter;
+ virtual gchar const * get_filter_text (Inkscape::Extension::Extension * ext);
+
+private:
+ Inkscape::XML::Document * get_filter (Inkscape::Extension::Extension * ext);
+ void merge_filters (Inkscape::XML::Node * to, Inkscape::XML::Node * from, Inkscape::XML::Document * doc, gchar const * srcGraphic = nullptr, gchar const * srcGraphicAlpha = nullptr);
+
+public:
+ Filter();
+ Filter(gchar const * filter);
+ ~Filter() override;
+
+ bool load(Inkscape::Extension::Extension *module) override;
+ Inkscape::Extension::Implementation::ImplementationDocumentCache * newDocCache (Inkscape::Extension::Extension * ext, Inkscape::UI::View::View * doc) override;
+ void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+
+ static void filter_init(gchar const * id, gchar const * name, gchar const * submenu, gchar const * tip, gchar const * filter);
+ static void filters_all();
+
+ /* File loader related */
+ static void filters_all_files();
+ static void filters_load_node(Inkscape::XML::Node *node, gchar * menuname);
+
+};
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+#endif // INKSCAPE_EXTENSION_INTERNAL_FILTER_FILTER_H
diff --git a/src/extension/internal/filter/image.h b/src/extension/internal/filter/image.h
new file mode 100644
index 0000000..8820122
--- /dev/null
+++ b/src/extension/internal/filter/image.h
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_IMAGE_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_IMAGE_H__
+/* Change the 'IMAGE' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Image filters
+ * Edge detect
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Edge detect filter.
+
+ Detect color edges in object.
+
+ Filter's parameters:
+ * Detection type (enum, default Full) -> convolve (kernelMatrix)
+ * Level (0.01->10., default 1.) -> convolve (divisor)
+ * Inverted (boolean, default false) -> convolve (bias)
+*/
+class EdgeDetect : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ EdgeDetect ( ) : Filter() { };
+ ~EdgeDetect ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Edge Detect") "</name>\n"
+ "<id>org.inkscape.effect.filter.EdgeDetect</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Detect:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value=\"all\">" N_("All") "</option>\n"
+ "<option value=\"vertical\">" N_("Vertical lines") "</option>\n"
+ "<option value=\"horizontal\">" N_("Horizontal lines") "</option>\n"
+ "</param>\n"
+ "<param name=\"level\" gui-text=\"" N_("Level") "\" type=\"float\" appearance=\"full\" min=\"0.1\" max=\"100.0\">1.0</param>\n"
+ "<param name=\"inverted\" gui-text=\"" N_("Invert colors") "\" type=\"bool\" >false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Effects") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Detect color edges in object") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new EdgeDetect());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+EdgeDetect::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream matrix;
+ std::ostringstream inverted;
+ std::ostringstream level;
+
+ const gchar *type = ext->get_param_optiongroup("type");
+
+ level << 1 / ext->get_param_float("level");
+
+ if ((g_ascii_strcasecmp("vertical", type) == 0)) {
+ matrix << "0 0 0 1 -2 1 0 0 0";
+ } else if ((g_ascii_strcasecmp("horizontal", type) == 0)) {
+ matrix << "0 1 0 0 -2 0 0 1 0";
+ } else {
+ matrix << "0 1 0 1 -4 1 0 1 0";
+ }
+
+ if (ext->get_param_bool("inverted")) {
+ inverted << "1";
+ } else {
+ inverted << "0";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Edge Detect\">\n"
+ "<feConvolveMatrix in=\"SourceGraphic\" kernelMatrix=\"%s\" order=\"3 3\" bias=\"%s\" divisor=\"%s\" targetX=\"1\" targetY=\"1\" preserveAlpha=\"true\" result=\"convolve\" />\n"
+ "</filter>\n", matrix.str().c_str(), inverted.str().c_str(), level.str().c_str());
+ // clang-format on
+
+ return _filter;
+};
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'IMAGE' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_IMAGE_H__ */
diff --git a/src/extension/internal/filter/morphology.h b/src/extension/internal/filter/morphology.h
new file mode 100644
index 0000000..e2bfa15
--- /dev/null
+++ b/src/extension/internal/filter/morphology.h
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_MORPHOLOGY_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_MORPHOLOGY_H__
+/* Change the 'MORPHOLOGY' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Morphology filters
+ * Cross-smooth
+ * Outline
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Cross-smooth filter.
+
+ Smooth the outside of shapes and pictures.
+
+ Filter's parameters:
+ * Type (enum, default "Smooth edges") ->
+ Inner = composite1 (operator="in")
+ Outer = composite1 (operator="over")
+ Open = composite1 (operator="XOR")
+ * Width (0.01->30., default 10.) -> blur (stdDeviation)
+ * Level (0.2->2., default 1.) -> composite2 (k2)
+ * Dilatation (1.->100., default 10.) -> colormatrix1 (last-1 value)
+ * Erosion (1.->100., default 1.) -> colormatrix1 (last value)
+ * Antialiasing (0.01->1., default 1) -> blur2 (stdDeviation)
+ * Blur content (boolean, default false) -> blend (true: in="colormatrix2", false: in="SourceGraphic")
+*/
+
+class Crosssmooth : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Crosssmooth ( ) : Filter() { };
+ ~Crosssmooth ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Cross-smooth") "</name>\n"
+ "<id>org.inkscape.effect.filter.crosssmooth</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"in\">" N_("Inner") "</option>\n"
+ "<option value=\"over\">" N_("Outer") "</option>\n"
+ "<option value=\"xor\">" N_("Open") "</option>\n"
+ "</param>\n"
+ "<param name=\"width\" gui-text=\"" N_("Width") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.\">10</param>\n"
+ "<param name=\"level\" gui-text=\"" N_("Level") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.2\" max=\"2\">1</param>\n"
+ "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">10</param>\n"
+ "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">1</param>\n"
+ "<param name=\"antialias\" gui-text=\"" N_("Antialiasing") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"1\">1</param>\n"
+ "<param name=\"content\" gui-text=\"" N_("Blur content") "\" type=\"bool\" >false</param>\n"
+
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Morphology") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Smooth edges and angles of shapes") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Crosssmooth());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Crosssmooth::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream type;
+ std::ostringstream width;
+ std::ostringstream level;
+ std::ostringstream dilat;
+ std::ostringstream erosion;
+ std::ostringstream antialias;
+ std::ostringstream content;
+
+ type << ext->get_param_optiongroup("type");
+ width << ext->get_param_float("width");
+ level << ext->get_param_float("level");
+ dilat << ext->get_param_float("dilat");
+ erosion << (1 - ext->get_param_float("erosion"));
+ antialias << ext->get_param_float("antialias");
+
+ if (ext->get_param_bool("content")) {
+ content << "colormatrix2";
+ } else {
+ content << "SourceGraphic";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Cross-smooth\">\n"
+ "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feComposite in=\"blur1\" in2=\"blur1\" operator=\"%s\" result=\"composite1\" />\n"
+ "<feComposite in=\"composite1\" in2=\"composite1\" k2=\"%s\" operator=\"arithmetic\" result=\"composite2\" />\n"
+ "<feColorMatrix in=\"composite2\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix1\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "<feColorMatrix in=\"blur2\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 5 -1 \" result=\"colormatrix2\" />\n"
+ "<feBlend in=\"%s\" in2=\"colormatrix2\" stdDeviation=\"17\" mode=\"normal\" result=\"blend\" />\n"
+ "<feComposite in=\"blend\" in2=\"colormatrix2\" operator=\"in\" result=\"composite3\" />\n"
+ "</filter>\n", width.str().c_str(), type.str().c_str(), level.str().c_str(),
+ dilat.str().c_str(), erosion.str().c_str(), antialias.str().c_str(),
+ content.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Cross-smooth filter */
+
+/**
+ \brief Custom predefined Outline filter.
+
+ Adds a colorizable outline
+
+ Filter's parameters:
+ * Fill image (boolean, default false) -> true: composite2 (in="SourceGraphic"), false: composite2 (in="blur2")
+ * Hide image (boolean, default false) -> true: composite4 (in="composite3"), false: composite4 (in="SourceGraphic")
+ * Stroke type (enum, default over) -> composite2 (operator)
+ * Stroke position (enum, default inside)
+ * inside -> composite1 (operator="out", in="SourceGraphic", in2="blur1")
+ * outside -> composite1 (operator="out", in="blur1", in2="SourceGraphic")
+ * overlayed -> composite1 (operator="xor", in="blur1", in2="SourceGraphic")
+ * Width 1 (0.01->20., default 4) -> blur1 (stdDeviation)
+ * Dilatation 1 (1.->100., default 100) -> colormatrix1 (n-1th value)
+ * Erosion 1 (0.->100., default 1) -> colormatrix1 (nth value 0->-100)
+ * Width 2 (0.01->20., default 0.5) -> blur2 (stdDeviation)
+ * Dilatation 2 (1.->100., default 50) -> colormatrix2 (n-1th value)
+ * Erosion 2 (0.->100., default 5) -> colormatrix2 (nth value 0->-100)
+ * Antialiasing (0.01->1., default 1) -> blur3 (stdDeviation)
+ * Color (guint, default 0,0,0,255) -> flood (flood-color, flood-opacity)
+ * Fill opacity (0.->1., default 1) -> composite5 (k2)
+ * Stroke opacity (0.->1., default 1) -> composite5 (k3)
+
+*/
+
+class Outline : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Outline ( ) : Filter() { };
+ ~Outline ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Outline") "</name>\n"
+ "<id>org.inkscape.effect.filter.Outline</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"fill\" gui-text=\"" N_("Fill image") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"outline\" gui-text=\"" N_("Hide image") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"type\" gui-text=\"" N_("Composite type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"over\">" N_("Over") "</option>\n"
+ "<option value=\"in\">" N_("In") "</option>\n"
+ "<option value=\"out\">" N_("Out") "</option>\n"
+ "<option value=\"atop\">" N_("Atop") "</option>\n"
+ "<option value=\"xor\">" N_("XOR") "</option>\n"
+ "</param>\n"
+ "<param name=\"position\" gui-text=\"" N_("Position:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"inside\">" N_("Inside") "</option>\n"
+ "<option value=\"outside\">" N_("Outside") "</option>\n"
+ "<option value=\"overlayed\">" N_("Overlayed") "</option>\n"
+ "</param>\n"
+ "<param name=\"width1\" gui-text=\"" N_("Width 1") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">4</param>\n"
+ "<param name=\"dilat1\" gui-text=\"" N_("Dilatation 1") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">100</param>\n"
+ "<param name=\"erosion1\" gui-text=\"" N_("Erosion 1") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">1</param>\n"
+ "<param name=\"width2\" gui-text=\"" N_("Width 2") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">0.5</param>\n"
+ "<param name=\"dilat2\" gui-text=\"" N_("Dilatation 2") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"100\">50</param>\n"
+ "<param name=\"erosion2\" gui-text=\"" N_("Erosion 2") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100\">5</param>\n"
+ "<param name=\"antialias\" gui-text=\"" N_("Antialiasing") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"1\">1</param>\n"
+ "<param name=\"smooth\" gui-text=\"" N_("Smooth") "\" type=\"bool\" >false</param>\n"
+ "</page>\n"
+ "<page name=\"co11tab\" gui-text=\"Color\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">255</param>\n"
+ "<param name=\"fopacity\" gui-text=\"" N_("Fill opacity:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n"
+ "<param name=\"sopacity\" gui-text=\"" N_("Stroke opacity:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">1</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Morphology") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Adds a colorizable outline") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Outline());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Outline::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream width1;
+ std::ostringstream dilat1;
+ std::ostringstream erosion1;
+ std::ostringstream width2;
+ std::ostringstream dilat2;
+ std::ostringstream erosion2;
+ std::ostringstream antialias;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream a;
+ std::ostringstream fopacity;
+ std::ostringstream sopacity;
+ std::ostringstream smooth;
+
+ std::ostringstream c1in;
+ std::ostringstream c1in2;
+ std::ostringstream c1op;
+ std::ostringstream c2in;
+ std::ostringstream c2op;
+ std::ostringstream c4in;
+
+
+ width1 << ext->get_param_float("width1");
+ dilat1 << ext->get_param_float("dilat1");
+ erosion1 << (- ext->get_param_float("erosion1"));
+ width2 << ext->get_param_float("width2");
+ dilat2 << ext->get_param_float("dilat2");
+ erosion2 << (- ext->get_param_float("erosion2"));
+ antialias << ext->get_param_float("antialias");
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+
+ fopacity << ext->get_param_float("fopacity");
+ sopacity << ext->get_param_float("sopacity");
+
+ const gchar *position = ext->get_param_optiongroup("position");
+ if((g_ascii_strcasecmp("inside", position) == 0)) {
+ // Inside
+ c1in << "SourceGraphic";
+ c1in2 << "blur1";
+ c1op << "out";
+ } else if((g_ascii_strcasecmp("outside", position) == 0)) {
+ // Outside
+ c1in << "blur1";
+ c1in2 << "SourceGraphic";
+ c1op << "out";
+ } else {
+ // Overlayed
+ c1in << "blur1";
+ c1in2 << "SourceGraphic";
+ c1op << "xor";
+ }
+
+ if (ext->get_param_bool("fill")) {
+ c2in << "SourceGraphic";
+ } else {
+ c2in << "blur2";
+ }
+
+ c2op << ext->get_param_optiongroup("type");
+
+ if (ext->get_param_bool("outline")) {
+ c4in << "composite3";
+ } else {
+ c4in << "SourceGraphic";
+ }
+
+ if (ext->get_param_bool("smooth")) {
+ smooth << "1 0";
+ } else {
+ smooth << "5 -1";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" height=\"1.4\" width=\"1.4\" y=\"-0.2\" x=\"-0.2\" inkscape:label=\"Outline\">\n"
+ "<feGaussianBlur in=\"SourceAlpha\" stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feComposite in=\"%s\" in2=\"%s\" operator=\"%s\" result=\"composite1\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix1\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "<feComposite in=\"%s\" in2=\"blur2\" operator=\"%s\" result=\"composite2\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix2\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur3\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s \" result=\"colormatrix3\" />\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feComposite in=\"flood\" in2=\"colormatrix3\" k2=\"1\" operator=\"in\" result=\"composite3\" />\n"
+ "<feComposite in=\"%s\" in2=\"colormatrix3\" operator=\"out\" result=\"composite4\" />\n"
+ "<feComposite in=\"composite4\" in2=\"composite3\" k2=\"%s\" k3=\"%s\" operator=\"arithmetic\" result=\"composite5\" />\n"
+ "</filter>\n", width1.str().c_str(), c1in.str().c_str(), c1in2.str().c_str(), c1op.str().c_str(),
+ dilat1.str().c_str(), erosion1.str().c_str(),
+ width2.str().c_str(), c2in.str().c_str(), c2op.str().c_str(),
+ dilat2.str().c_str(), erosion2.str().c_str(), antialias.str().c_str(), smooth.str().c_str(),
+ a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ c4in.str().c_str(), fopacity.str().c_str(), sopacity.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Outline filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'MORPHOLOGY' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_MORPHOLOGY_H__ */
diff --git a/src/extension/internal/filter/overlays.h b/src/extension/internal/filter/overlays.h
new file mode 100644
index 0000000..b93c070
--- /dev/null
+++ b/src/extension/internal/filter/overlays.h
@@ -0,0 +1,150 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_OVERLAYS_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_OVERLAYS_H__
+/* Change the 'OVERLAYS' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Overlays filters
+ * Noise fill
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Noise fill filter.
+
+ Basic noise fill and transparency texture
+
+ Filter's parameters:
+ * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type)
+ * Horizontal frequency (*1000) (0.01->10000., default 20) -> turbulence (baseFrequency [/1000])
+ * Vertical frequency (*1000) (0.01->10000., default 40) -> turbulence (baseFrequency [/1000])
+ * Complexity (1->5, default 5) -> turbulence (numOctaves)
+ * Variation (1->360, default 1) -> turbulence (seed)
+ * Dilatation (1.->50., default 3) -> color (n-1th value)
+ * Erosion (0.->50., default 1) -> color (nth value 0->-50)
+ * Color (guint, default 148,115,39,255) -> flood (flood-color, flood-opacity)
+ * Inverted (boolean, default false) -> composite1 (operator, true="in", false="out")
+*/
+
+class NoiseFill : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ NoiseFill ( ) : Filter() { };
+ ~NoiseFill ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Noise Fill") "</name>\n"
+ "<id>org.inkscape.effect.filter.NoiseFill</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"" N_("Options") "\">\n"
+ "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n"
+ "<option value=\"turbulence\">" N_("Turbulence") "</option>\n"
+ "</param>\n"
+ "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100.00\">20</param>\n"
+ "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"100.00\">40</param>\n"
+ "<param name=\"complexity\" gui-text=\"" N_("Complexity:") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">5</param>\n"
+ "<param name=\"variation\" gui-text=\"" N_("Variation:") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"360\">0</param>\n"
+ "<param name=\"dilat\" gui-text=\"" N_("Dilatation:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"50\">3</param>\n"
+ "<param name=\"erosion\" gui-text=\"" N_("Erosion:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"50\">1</param>\n"
+ "<param name=\"inverted\" gui-text=\"" N_("Inverted") "\" type=\"bool\" >false</param>\n"
+ "</page>\n"
+ "<page name=\"co11tab\" gui-text=\"" N_("Noise color") "\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">354957823</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Overlays") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Basic noise fill and transparency texture") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new NoiseFill());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+NoiseFill::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream type;
+ std::ostringstream hfreq;
+ std::ostringstream vfreq;
+ std::ostringstream complexity;
+ std::ostringstream variation;
+ std::ostringstream dilat;
+ std::ostringstream erosion;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream a;
+ std::ostringstream inverted;
+
+ type << ext->get_param_optiongroup("type");
+ hfreq << (ext->get_param_float("hfreq"));
+ vfreq << (ext->get_param_float("vfreq"));
+ complexity << ext->get_param_int("complexity");
+ variation << ext->get_param_int("variation");
+ dilat << ext->get_param_float("dilat");
+ erosion << (- ext->get_param_float("erosion"));
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+ if (ext->get_param_bool("inverted"))
+ inverted << "out";
+ else
+ inverted << "in";
+
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Noise Fill\">\n"
+ "<feTurbulence type=\"%s\" baseFrequency=\"%s %s\" numOctaves=\"%s\" seed=\"%s\" result=\"turbulence\"/>\n"
+ "<feComposite in=\"SourceGraphic\" in2=\"turbulence\" operator=\"%s\" result=\"composite1\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color\" />\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feMerge result=\"merge\">\n"
+ "<feMergeNode in=\"flood\" />\n"
+ "<feMergeNode in=\"color\" />\n"
+ "</feMerge>\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n"
+ "</filter>\n", type.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), complexity.str().c_str(), variation.str().c_str(), inverted.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str());
+
+ return _filter;
+}; /* NoiseFill filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'OVERLAYS' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_OVERLAYS_H__ */
diff --git a/src/extension/internal/filter/paint.h b/src/extension/internal/filter/paint.h
new file mode 100644
index 0000000..920b275
--- /dev/null
+++ b/src/extension/internal/filter/paint.h
@@ -0,0 +1,1061 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PAINT_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PAINT_H__
+/* Change the 'PAINT' above to be your file name */
+
+/*
+ * Copyright (C) 2012 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Image paint and draw filters
+ * Chromolitho
+ * Cross engraving
+ * Drawing
+ * Electrize
+ * Neon draw
+ * Point engraving
+ * Posterize
+ * Posterize basic
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Chromolitho filter.
+
+ Chromo effect with customizable edge drawing and graininess
+
+ Filter's parameters:
+ * Drawing (boolean, default checked) -> Checked = blend1 (in="convolve1"), unchecked = blend1 (in="composite1")
+ * Transparent (boolean, default unchecked) -> Checked = colormatrix5 (in="colormatrix4"), Unchecked = colormatrix5 (in="component1")
+ * Invert (boolean, default false) -> component1 (tableValues) [adds a trailing 0]
+ * Dented (boolean, default false) -> component1 (tableValues) [adds intermediate 0s]
+ * Lightness (0.->10., default 0.) -> composite1 (k1)
+ * Saturation (0.->1., default 1.) -> colormatrix3 (values)
+ * Noise reduction (1->1000, default 20) -> convolve (kernelMatrix, central value -1001->-2000, default -1020)
+ * Drawing blend (enum, default Normal) -> blend1 (mode)
+ * Smoothness (0.01->10, default 1) -> blur1 (stdDeviation)
+ * Grain (boolean, default unchecked) -> Checked = blend2 (in="colormatrix2"), Unchecked = blend2 (in="blur1")
+ * Grain x frequency (0.->1000, default 1000) -> turbulence1 (baseFrequency, first value)
+ * Grain y frequency (0.->1000, default 1000) -> turbulence1 (baseFrequency, second value)
+ * Grain complexity (1->5, default 1) -> turbulence1 (numOctaves)
+ * Grain variation (0->1000, default 0) -> turbulence1 (seed)
+ * Grain expansion (1.->50., default 1.) -> colormatrix1 (n-1 value)
+ * Grain erosion (0.->40., default 0.) -> colormatrix1 (nth value) [inverted]
+ * Grain color (boolean, default true) -> colormatrix2 (values)
+ * Grain blend (enum, default Normal) -> blend2 (mode)
+*/
+class Chromolitho : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Chromolitho ( ) : Filter() { };
+ ~Chromolitho ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Chromolitho") "</name>\n"
+ "<id>org.inkscape.effect.filter.Chromolitho</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<param name=\"drawing\" gui-text=\"" N_("Drawing mode") "\" type=\"bool\" >true</param>\n"
+ "<param name=\"dblend\" gui-text=\"" N_("Drawing blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"darken\">Darken</option>\n"
+ "<option value=\"normal\">Normal</option>\n"
+ "<option value=\"multiply\">Multiply</option>\n"
+ "<option value=\"screen\">Screen</option>\n"
+ "<option value=\"lighten\">Lighten</option>\n"
+ "</param>\n"
+ "<param name=\"transparent\" gui-text=\"" N_("Transparent") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"dented\" gui-text=\"" N_("Dented") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"inverted\" gui-text=\"" N_("Inverted") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"light\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10\">0</param>\n"
+ "<param name=\"saturation\" gui-text=\"" N_("Saturation") "\" type=\"float\" precision=\"2\" appearance=\"full\" min=\"0\" max=\"1\">1</param>\n"
+ "<param name=\"noise\" gui-text=\"" N_("Noise reduction") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"1000\">10</param>\n"
+ "<param name=\"smooth\" gui-text=\"" N_("Smoothness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"10.00\">1</param>\n"
+ "</page>\n"
+ "<page name=\"graintab\" gui-text=\"" N_("Grain") "\">\n"
+ "<param name=\"grain\" gui-text=\"" N_("Grain mode") "\" type=\"bool\" >true</param>\n"
+ "<param name=\"grainxf\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1000\">1000</param>\n"
+ "<param name=\"grainyf\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1000\">1000</param>\n"
+ "<param name=\"grainc\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">1</param>\n"
+ "<param name=\"grainv\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"1000\">0</param>\n"
+ "<param name=\"grainexp\" gui-text=\"" N_("Expansion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"1\" max=\"50\">1</param>\n"
+ "<param name=\"grainero\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"40\">0</param>\n"
+ "<param name=\"graincol\" gui-text=\"" N_("Color") "\" type=\"bool\" >true</param>\n"
+ "<param name=\"gblend\" gui-text=\"" N_("Grain blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">Normal</option>\n"
+ "<option value=\"multiply\">Multiply</option>\n"
+ "<option value=\"screen\">Screen</option>\n"
+ "<option value=\"lighten\">Lighten</option>\n"
+ "<option value=\"darken\">Darken</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Chromo effect with customizable edge drawing and graininess") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Chromolitho());
+ // clang-format on
+ };
+};
+
+gchar const *
+Chromolitho::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream b1in;
+ std::ostringstream b2in;
+ std::ostringstream col3in;
+ std::ostringstream transf;
+ std::ostringstream light;
+ std::ostringstream saturation;
+ std::ostringstream noise;
+ std::ostringstream dblend;
+ std::ostringstream smooth;
+ std::ostringstream grainxf;
+ std::ostringstream grainyf;
+ std::ostringstream grainc;
+ std::ostringstream grainv;
+ std::ostringstream gblend;
+ std::ostringstream grainexp;
+ std::ostringstream grainero;
+ std::ostringstream graincol;
+
+ if (ext->get_param_bool("drawing"))
+ b1in << "convolve1";
+ else
+ b1in << "composite1";
+
+ if (ext->get_param_bool("transparent"))
+ col3in << "colormatrix4";
+ else
+ col3in << "component1";
+ light << ext->get_param_float("light");
+ saturation << ext->get_param_float("saturation");
+ noise << (-1000 - ext->get_param_int("noise"));
+ dblend << ext->get_param_optiongroup("dblend");
+ smooth << ext->get_param_float("smooth");
+
+ if (ext->get_param_bool("dented")) {
+ transf << "0 1 0 1";
+ } else {
+ transf << "0 1 1";
+ }
+ if (ext->get_param_bool("inverted"))
+ transf << " 0";
+
+ if (ext->get_param_bool("grain"))
+ b2in << "colormatrix2";
+ else
+ b2in << "blur1";
+ grainxf << (ext->get_param_float("grainxf") / 1000);
+ grainyf << (ext->get_param_float("grainyf") / 1000);
+ grainc << ext->get_param_int("grainc");
+ grainv << ext->get_param_int("grainv");
+ gblend << ext->get_param_optiongroup("gblend");
+ grainexp << ext->get_param_float("grainexp");
+ grainero << (-ext->get_param_float("grainero"));
+ if (ext->get_param_bool("graincol"))
+ graincol << "1";
+ else
+ graincol << "0";
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Chromolitho\">\n"
+ "<feComposite in=\"SourceGraphic\" in2=\"SourceGraphic\" operator=\"arithmetic\" k1=\"%s\" k2=\"1\" result=\"composite1\" />\n"
+ "<feConvolveMatrix in=\"composite1\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0 \" order=\"3 3\" result=\"convolve1\" />\n"
+ "<feBlend in=\"%s\" in2=\"composite1\" mode=\"%s\" result=\"blend1\" />\n"
+ "<feGaussianBlur in=\"blend1\" stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feTurbulence baseFrequency=\"%s %s\" numOctaves=\"%s\" seed=\"%s\" type=\"fractalNoise\" result=\"turbulence1\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"colormatrix1\" />\n"
+ "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"colormatrix2\" />\n"
+ "<feBlend in=\"%s\" in2=\"blur1\" mode=\"%s\" result=\"blend2\" />\n"
+ "<feColorMatrix in=\"blend2\" type=\"saturate\" values=\"%s\" result=\"colormatrix3\" />\n"
+ "<feComponentTransfer in=\"colormatrix3\" result=\"component1\">\n"
+ "<feFuncR type=\"discrete\" tableValues=\"%s\" />\n"
+ "<feFuncG type=\"discrete\" tableValues=\"%s\" />\n"
+ "<feFuncB type=\"discrete\" tableValues=\"%s\" />\n"
+ "</feComponentTransfer>\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"colormatrix4\" />\n"
+ "<feColorMatrix in=\"%s\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 15 0 \" result=\"colormatrix5\" />\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n"
+ "</filter>\n", light.str().c_str(), noise.str().c_str(), b1in.str().c_str(), dblend.str().c_str(), smooth.str().c_str(), grainxf.str().c_str(), grainyf.str().c_str(), grainc.str().c_str(), grainv.str().c_str(), grainexp.str().c_str(), grainero.str().c_str(), graincol.str().c_str(), b2in.str().c_str(), gblend.str().c_str(), saturation.str().c_str(), transf.str().c_str(), transf.str().c_str(), transf.str().c_str(), col3in.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Chromolitho filter */
+
+/**
+ \brief Custom predefined Cross engraving filter.
+
+ Convert image to an engraving made of vertical and horizontal lines
+
+ Filter's parameters:
+ * Clean-up (1->500, default 30) -> convolve1 (kernelMatrix, central value -1001->-1500, default -1030)
+ * Dilatation (1.->50., default 1) -> color2 (n-1th value)
+ * Erosion (0.->50., default 0) -> color2 (nth value 0->-50)
+ * Strength (0.->10., default 0.5) -> composite2 (k2)
+ * Length (0.5->20, default 4) -> blur1 (stdDeviation x), blur2 (stdDeviation y)
+ * Transparent (boolean, default false) -> composite 4 (in, true->composite3, false->blend)
+*/
+class CrossEngraving : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ CrossEngraving ( ) : Filter() { };
+ ~CrossEngraving ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Cross Engraving") "</name>\n"
+ "<id>org.inkscape.effect.filter.CrossEngraving</id>\n"
+ "<param name=\"clean\" gui-text=\"" N_("Clean-up") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"500\">30</param>\n"
+ "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" appearance=\"full\" min=\"1\" max=\"50\">1</param>\n"
+ "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"50\">0</param>\n"
+ "<param name=\"strength\" gui-text=\"" N_("Strength") "\" type=\"float\" appearance=\"full\" min=\"0.1\" max=\"10\">0.5</param>\n"
+ "<param name=\"length\" gui-text=\"" N_("Length") "\" type=\"float\" appearance=\"full\" min=\"0.5\" max=\"20\">4</param>\n"
+ "<param name=\"trans\" gui-text=\"" N_("Transparent") "\" type=\"bool\" >false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Convert image to an engraving made of vertical and horizontal lines") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new CrossEngraving());
+ // clang-format on
+ };
+};
+
+gchar const *
+CrossEngraving::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream clean;
+ std::ostringstream dilat;
+ std::ostringstream erosion;
+ std::ostringstream strength;
+ std::ostringstream length;
+ std::ostringstream trans;
+
+ clean << (-1000 - ext->get_param_int("clean"));
+ dilat << ext->get_param_float("dilat");
+ erosion << (- ext->get_param_float("erosion"));
+ strength << ext->get_param_float("strength");
+ length << ext->get_param_float("length");
+ if (ext->get_param_bool("trans"))
+ trans << "composite3";
+ else
+ trans << "blend";
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Cross Engraving\">\n"
+ "<feConvolveMatrix in=\"SourceGraphic\" targetY=\"1\" targetX=\"1\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0 \" order=\"3 3\" result=\"convolve\" />\n"
+ "<feComposite in=\"convolve\" in2=\"convolve\" k1=\"1\" k2=\"1\" operator=\"arithmetic\" result=\"composite1\" />\n"
+ "<feColorMatrix in=\"composite1\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"color1\" />\n"
+ "<feColorMatrix in=\"color1\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color2\" />\n"
+ "<feComposite in=\"color2\" in2=\"color2\" operator=\"arithmetic\" k2=\"%s\" result=\"composite2\" />\n"
+ "<feGaussianBlur in=\"composite2\" stdDeviation=\"%s 0.01\" result=\"blur1\" />\n"
+ "<feGaussianBlur in=\"composite2\" stdDeviation=\"0.01 %s\" result=\"blur2\" />\n"
+ "<feComposite in=\"blur2\" in2=\"blur1\" k3=\"1\" k2=\"1\" operator=\"arithmetic\" result=\"composite3\" />\n"
+ "<feFlood flood-color=\"rgb(255,255,255)\" flood-opacity=\"1\" result=\"flood\" />\n"
+ "<feBlend in=\"flood\" in2=\"composite3\" mode=\"multiply\" result=\"blend\" />\n"
+ "<feComposite in=\"%s\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite4\" />\n"
+ "</filter>\n", clean.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), strength.str().c_str(), length.str().c_str(), length.str().c_str(), trans.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* CrossEngraving filter */
+
+/**
+ \brief Custom predefined Drawing filter.
+
+ Convert images to duochrome drawings.
+
+ Filter's parameters:
+ * Simplification strength (0.01->20, default 0.6) -> blur1 (stdDeviation)
+ * Clean-up (1->500, default 10) -> convolve1 (kernelMatrix, central value -1001->-1500, default -1010)
+ * Erase (0.->6., default 0) -> composite1 (k4)
+ * Smoothness strength (0.01->20, default 0.6) -> blur2 (stdDeviation)
+ * Dilatation (1.->50., default 6) -> color2 (n-1th value)
+ * Erosion (0.->50., default 2) -> color2 (nth value 0->-50)
+ * translucent (boolean, default false) -> composite 8 (in, true->merge1, false->color5)
+
+ * Blur strength (0.01->20., default 1.) -> blur3 (stdDeviation)
+ * Blur dilatation (1.->50., default 6) -> color4 (n-1th value)
+ * Blur erosion (0.->50., default 2) -> color4 (nth value 0->-50)
+
+ * Stroke color (guint, default 64,64,64,255) -> flood2 (flood-color), composite3 (k2)
+ * Image on stroke (boolean, default false) -> composite2 (in="flood2" true-> in="SourceGraphic")
+ * Offset (-100->100, default 0) -> offset (val)
+
+ * Fill color (guint, default 200,200,200,255) -> flood3 (flood-opacity), composite5 (k2)
+ * Image on fill (boolean, default false) -> composite4 (in="flood3" true-> in="SourceGraphic")
+
+*/
+
+class Drawing : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Drawing ( ) : Filter() { };
+ ~Drawing ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Drawing") "</name>\n"
+ "<id>org.inkscape.effect.filter.Drawing</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"Options\">\n"
+ "<label appearance=\"header\">" N_("Simplify") "</label>\n"
+ "<param name=\"simply\" gui-text=\"" N_("Strength") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">0.6</param>\n"
+ "<param name=\"clean\" gui-text=\"" N_("Clean-up") "\" type=\"int\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"500\">10</param>\n"
+ "<param name=\"erase\" gui-text=\"" N_("Erase") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"60\">0</param>\n"
+ "<param name=\"translucent\" gui-text=\"" N_("Translucent") "\" indent=\"1\" type=\"bool\" >false</param>\n"
+ "<label appearance=\"header\">" N_("Smoothness") "</label>\n"
+ "<param name=\"smooth\" gui-text=\"" N_("Strength") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">0.6</param>\n"
+ "<param name=\"dilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"50\">6</param>\n"
+ "<param name=\"erosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"50\">2</param>\n"
+ "<label appearance=\"header\">" N_("Melt") "</label>\n"
+ "<param name=\"blur\" gui-text=\"" N_("Level") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">1</param>\n"
+ "<param name=\"bdilat\" gui-text=\"" N_("Dilatation") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"1\" max=\"50\">6</param>\n"
+ "<param name=\"berosion\" gui-text=\"" N_("Erosion") "\" type=\"float\" indent=\"1\" appearance=\"full\" min=\"0\" max=\"50\">2</param>\n"
+ "</page>\n"
+ "<page name=\"co11tab\" gui-text=\"Fill color\">\n"
+ "<param name=\"fcolor\" gui-text=\"" N_("Fill color") "\" type=\"color\">-1515870721</param>\n"
+ "<param name=\"iof\" gui-text=\"" N_("Image on fill") "\" type=\"bool\" >false</param>\n"
+ "</page>\n"
+ "<page name=\"co12tab\" gui-text=\"Stroke color\">\n"
+ "<param name=\"scolor\" gui-text=\"" N_("Stroke color") "\" type=\"color\">589505535</param>\n"
+ "<param name=\"ios\" gui-text=\"" N_("Image on stroke") "\" type=\"bool\" >false</param>\n"
+ "<param name=\"offset\" gui-text=\"" N_("Offset") "\" type=\"int\" appearance=\"full\" min=\"-100\" max=\"100\">0</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Convert images to duochrome drawings") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Drawing());
+ // clang-format on
+ };
+};
+
+gchar const *
+Drawing::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream simply;
+ std::ostringstream clean;
+ std::ostringstream erase;
+ std::ostringstream smooth;
+ std::ostringstream dilat;
+ std::ostringstream erosion;
+ std::ostringstream translucent;
+ std::ostringstream offset;
+ std::ostringstream blur;
+ std::ostringstream bdilat;
+ std::ostringstream berosion;
+ std::ostringstream strokea;
+ std::ostringstream stroker;
+ std::ostringstream strokeg;
+ std::ostringstream strokeb;
+ std::ostringstream ios;
+ std::ostringstream filla;
+ std::ostringstream fillr;
+ std::ostringstream fillg;
+ std::ostringstream fillb;
+ std::ostringstream iof;
+
+ simply << ext->get_param_float("simply");
+ clean << (-1000 - ext->get_param_int("clean"));
+ erase << (ext->get_param_float("erase") / 10);
+ smooth << ext->get_param_float("smooth");
+ dilat << ext->get_param_float("dilat");
+ erosion << (- ext->get_param_float("erosion"));
+ if (ext->get_param_bool("translucent"))
+ translucent << "merge1";
+ else
+ translucent << "color5";
+ offset << ext->get_param_int("offset");
+
+ blur << ext->get_param_float("blur");
+ bdilat << ext->get_param_float("bdilat");
+ berosion << (- ext->get_param_float("berosion"));
+
+ guint32 fcolor = ext->get_param_color("fcolor");
+ fillr << ((fcolor >> 24) & 0xff);
+ fillg << ((fcolor >> 16) & 0xff);
+ fillb << ((fcolor >> 8) & 0xff);
+ filla << (fcolor & 0xff) / 255.0F;
+ if (ext->get_param_bool("iof"))
+ iof << "SourceGraphic";
+ else
+ iof << "flood3";
+
+ guint32 scolor = ext->get_param_color("scolor");
+ stroker << ((scolor >> 24) & 0xff);
+ strokeg << ((scolor >> 16) & 0xff);
+ strokeb << ((scolor >> 8) & 0xff);
+ strokea << (scolor & 0xff) / 255.0F;
+ if (ext->get_param_bool("ios"))
+ ios << "SourceGraphic";
+ else
+ ios << "flood2";
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Drawing\">\n"
+ "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feConvolveMatrix in=\"blur1\" targetX=\"1\" targetY=\"1\" order=\"3 3\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0 \" result=\"convolve1\" />\n"
+ "<feComposite in=\"convolve1\" in2=\"convolve1\" k1=\"1\" k2=\"1\" k4=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n"
+ "<feColorMatrix in=\"composite1\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"color1\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color2\" />\n"
+ "<feFlood flood-color=\"rgb(255,255,255)\" result=\"flood1\" />\n"
+ "<feBlend in2=\"color2\" mode=\"multiply\" result=\"blend1\" />\n"
+ "<feComponentTransfer in=\"blend1\" result=\"component1\">\n"
+ "<feFuncR type=\"discrete\" tableValues=\"0 1 1 1\" />\n"
+ "<feFuncG type=\"discrete\" tableValues=\"0 1 1 1\" />\n"
+ "<feFuncB type=\"discrete\" tableValues=\"0 1 1 1\" />\n"
+ "</feComponentTransfer>\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur3\" />\n"
+ "<feColorMatrix in=\"blur3\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -0.2125 -0.7154 -0.0721 1 0 \" result=\"color3\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s %s \" result=\"color4\" />\n"
+ "<feFlood flood-color=\"rgb(%s,%s,%s)\" result=\"flood2\" />\n"
+ "<feComposite in=\"%s\" in2=\"color4\" operator=\"in\" result=\"composite2\" />\n"
+ "<feComposite in=\"composite2\" in2=\"composite2\" operator=\"arithmetic\" k2=\"%s\" result=\"composite3\" />\n"
+ "<feOffset dx=\"%s\" dy=\"%s\" result=\"offset1\" />\n"
+ "<feFlood in=\"color4\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood3\" />\n"
+ "<feComposite in=\"%s\" in2=\"color4\" operator=\"out\" result=\"composite4\" />\n"
+ "<feComposite in=\"composite4\" in2=\"composite4\" operator=\"arithmetic\" k2=\"%s\" result=\"composite5\" />\n"
+ "<feMerge result=\"merge1\">\n"
+ "<feMergeNode in=\"composite5\" />\n"
+ "<feMergeNode in=\"offset1\" />\n"
+ "</feMerge>\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1.3 0 \" result=\"color5\" flood-opacity=\"0.56\" />\n"
+ "<feComposite in=\"%s\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite8\" />\n"
+ "</filter>\n", simply.str().c_str(), clean.str().c_str(), erase.str().c_str(), smooth.str().c_str(), dilat.str().c_str(), erosion.str().c_str(), blur.str().c_str(), bdilat.str().c_str(), berosion.str().c_str(), stroker.str().c_str(), strokeg.str().c_str(), strokeb.str().c_str(), ios.str().c_str(), strokea.str().c_str(), offset.str().c_str(), offset.str().c_str(), fillr.str().c_str(), fillg.str().c_str(), fillb.str().c_str(), iof.str().c_str(), filla.str().c_str(), translucent.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Drawing filter */
+
+
+/**
+ \brief Custom predefined Electrize filter.
+
+ Electro solarization effects.
+
+ Filter's parameters:
+ * Simplify (0.01->10., default 2.) -> blur (stdDeviation)
+ * Effect type (enum: table or discrete, default "table") -> component (type)
+ * Level (0->10, default 3) -> component (tableValues)
+ * Inverted (boolean, default false) -> component (tableValues)
+*/
+class Electrize : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Electrize ( ) : Filter() { };
+ ~Electrize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Electrize") "</name>\n"
+ "<id>org.inkscape.effect.filter.Electrize</id>\n"
+ "<param name=\"blur\" gui-text=\"" N_("Simplify") "\" type=\"float\" appearance=\"full\" min=\"0.01\" max=\"10.0\">2.0</param>\n"
+ "<param name=\"type\" gui-text=\"" N_("Effect type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"table\">" N_("Table") "</option>\n"
+ "<option value=\"discrete\">" N_("Discrete") "</option>\n"
+ "</param>\n"
+ "<param name=\"levels\" gui-text=\"" N_("Levels") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"10\">3</param>\n"
+ "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Electro solarization effects") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Electrize());
+ // clang-format on
+ };
+};
+
+gchar const *
+Electrize::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream blur;
+ std::ostringstream type;
+ std::ostringstream values;
+
+ blur << ext->get_param_float("blur");
+ type << ext->get_param_optiongroup("type");
+
+ // TransfertComponent table values are calculated based on the effect level and inverted parameters.
+ int val = 0;
+ int levels = ext->get_param_int("levels") + 1;
+ if (ext->get_param_bool("invert")) {
+ val = 1;
+ }
+ values << val;
+ for ( int step = 1 ; step <= levels ; step++ ) {
+ if (val == 1) {
+ val = 0;
+ }
+ else {
+ val = 1;
+ }
+ values << " " << val;
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Electrize\">\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feComponentTransfer in=\"blur\" result=\"component\" >\n"
+ "<feFuncR type=\"%s\" tableValues=\"%s\" />\n"
+ "<feFuncG type=\"%s\" tableValues=\"%s\" />\n"
+ "<feFuncB type=\"%s\" tableValues=\"%s\" />\n"
+ "</feComponentTransfer>\n"
+ "</filter>\n", blur.str().c_str(), type.str().c_str(), values.str().c_str(), type.str().c_str(), values.str().c_str(), type.str().c_str(), values.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Electrize filter */
+
+/**
+ \brief Custom predefined Neon draw filter.
+
+ Posterize and draw smooth lines around color shapes
+
+ Filter's parameters:
+ * Lines type (enum, default smooth) ->
+ smooth = component2 (type="table"), composite1 (in2="blur2")
+ hard = component2 (type="discrete"), composite1 (in2="component1")
+ * Simplify (0.01->20., default 3) -> blur1 (stdDeviation)
+ * Line width (0.01->20., default 3) -> blur2 (stdDeviation)
+ * Lightness (0.->10., default 1) -> composite1 (k2)
+ * Blend (enum [normal, multiply, screen], default normal) -> blend (mode)
+ * Dark mode (boolean, default false) -> composite2 (true: in2="component2")
+*/
+class NeonDraw : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ NeonDraw ( ) : Filter() { };
+ ~NeonDraw ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Neon Draw") "</name>\n"
+ "<id>org.inkscape.effect.filter.NeonDraw</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Line type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"table\">" N_("Smoothed") "</option>\n"
+ "<option value=\"discrete\">" N_("Contrasted") "</option>\n"
+ "</param>\n"
+ "<param name=\"simply\" gui-text=\"" N_("Simplify") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">3</param>\n"
+ "<param name=\"width\" gui-text=\"" N_("Line width") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">3</param>\n"
+ "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"10.00\">1</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">Normal</option>\n"
+ "<option value=\"multiply\">Multiply</option>\n"
+ "<option value=\"screen\">Screen</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Posterize and draw smooth lines around color shapes") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new NeonDraw());
+ // clang-format on
+ };
+};
+
+gchar const *
+NeonDraw::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream blend;
+ std::ostringstream simply;
+ std::ostringstream width;
+ std::ostringstream lightness;
+ std::ostringstream type;
+
+ type << ext->get_param_optiongroup("type");
+ blend << ext->get_param_optiongroup("blend");
+ simply << ext->get_param_float("simply");
+ width << ext->get_param_float("width");
+ lightness << ext->get_param_float("lightness");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Neon Draw\">\n"
+ "<feBlend mode=\"%s\" result=\"blend\" />\n"
+ "<feGaussianBlur in=\"blend\" stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 50 0\" result=\"color1\" />\n"
+ "<feComponentTransfer result=\"component1\">\n"
+ "<feFuncR type=\"discrete\" tableValues=\"0 0.3 0.3 0.3 0.3 0.6 0.6 0.6 0.6 1 1\" />\n"
+ "<feFuncG type=\"discrete\" tableValues=\"0 0.3 0.3 0.3 0.3 0.6 0.6 0.6 0.6 1 1\" />\n"
+ "<feFuncB type=\"discrete\" tableValues=\"0 0.3 0.3 0.3 0.3 0.6 0.6 0.6 0.6 1 1\" />\n"
+ "</feComponentTransfer>\n"
+ "<feGaussianBlur in=\"component1\" stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 50 0\" result=\"color2\" />\n"
+ "<feComponentTransfer in=\"color2\" result=\"component2\">\n"
+ "<feFuncR type=\"%s\" tableValues=\"0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1\" />\n"
+ "<feFuncG type=\"%s\" tableValues=\"0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1\" />\n"
+ "<feFuncB type=\"%s\" tableValues=\"0 1 1 1 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 1\" />\n"
+ "</feComponentTransfer>\n"
+ "<feComposite in=\"component2\" in2=\"blur2\" k3=\"%s\" operator=\"arithmetic\" k2=\"1\" result=\"composite1\" />\n"
+ "<feComposite in=\"composite1\" in2=\"SourceGraphic\" operator=\"in\" result=\"composite2\" />\n"
+ "</filter>\n", blend.str().c_str(), simply.str().c_str(), width.str().c_str(), type.str().c_str(), type.str().c_str(), type.str().c_str(), lightness.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* NeonDraw filter */
+
+/**
+ \brief Custom predefined Point engraving filter.
+
+ Convert image to a transparent point engraving
+
+ Filter's parameters:
+
+ * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type)
+ * Horizontal frequency (0.001->1., default 1) -> turbulence (baseFrequency [/100])
+ * Vertical frequency (0.001->1., default 1) -> turbulence (baseFrequency [/100])
+ * Complexity (1->5, default 3) -> turbulence (numOctaves)
+ * Variation (0->1000, default 0) -> turbulence (seed)
+ * Noise reduction (-1000->-1500, default -1045) -> convolve (kernelMatrix, central value)
+ * Noise blend (enum, all blend options, default normal) -> blend (mode)
+ * Lightness (0.->10., default 2.5) -> composite1 (k1)
+ * Grain lightness (0.->10., default 1.3) -> composite1 (k2)
+ * Erase (0.00->1., default 0) -> composite1 (k4)
+ * Blur (0.01->2., default 0.5) -> blur (stdDeviation)
+
+ * Drawing color (guint32, default rgb(255,255,255)) -> flood1 (flood-color, flood-opacity)
+
+ * Background color (guint32, default rgb(99,89,46)) -> flood2 (flood-color, flood-opacity)
+*/
+
+class PointEngraving : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ PointEngraving ( ) : Filter() { };
+ ~PointEngraving ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Point Engraving") "</name>\n"
+ "<id>org.inkscape.effect.filter.PointEngraving</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"" N_("Options") "\">\n"
+ "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"fractalNoise\">" N_("Fractal noise") "</option>\n"
+ "<option value=\"turbulence\">" N_("Turbulence") "</option>\n"
+ "</param>\n"
+ "<param name=\"hfreq\" gui-text=\"" N_("Horizontal frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"100.00\">100</param>\n"
+ "<param name=\"vfreq\" gui-text=\"" N_("Vertical frequency") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.1\" max=\"100.00\">100</param>\n"
+ "<param name=\"complexity\" gui-text=\"" N_("Complexity") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">1</param>\n"
+ "<param name=\"variation\" gui-text=\"" N_("Variation") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"100\">0</param>\n"
+ "<param name=\"reduction\" gui-text=\"" N_("Noise reduction") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"500\">45</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Noise blend:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "</param>\n"
+ "<param name=\"lightness\" gui-text=\"" N_("Lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10\">2.5</param>\n"
+ "<param name=\"grain\" gui-text=\"" N_("Grain lightness") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"10\">1.3</param>\n"
+ "<param name=\"erase\" gui-text=\"" N_("Erase") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0\" max=\"1\">0</param>\n"
+ "<param name=\"blur\" gui-text=\"" N_("Blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"2\">0.5</param>\n"
+ "</page>\n"
+ "<page name=\"fcolortab\" gui-text=\"" N_("Fill color") "\">\n"
+ "<param name=\"fcolor\" gui-text=\"" N_("Color") "\" type=\"color\">-1</param>\n"
+ "<param name=\"iof\" gui-text=\"" N_("Image on fill") "\" type=\"bool\" >false</param>\n"
+ "</page>\n"
+ "<page name=\"pcolortab\" gui-text=\"" N_("Points color") "\">\n"
+ "<param name=\"pcolor\" gui-text=\"" N_("Color") "\" type=\"color\">1666789119</param>\n"
+ "<param name=\"iop\" gui-text=\"" N_("Image on points") "\" type=\"bool\" >false</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Convert image to a transparent point engraving") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new PointEngraving());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+PointEngraving::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream type;
+ std::ostringstream hfreq;
+ std::ostringstream vfreq;
+ std::ostringstream complexity;
+ std::ostringstream variation;
+ std::ostringstream reduction;
+ std::ostringstream blend;
+ std::ostringstream lightness;
+ std::ostringstream grain;
+ std::ostringstream erase;
+ std::ostringstream blur;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream a;
+ std::ostringstream br;
+ std::ostringstream bg;
+ std::ostringstream bb;
+ std::ostringstream ba;
+ std::ostringstream iof;
+ std::ostringstream iop;
+
+ type << ext->get_param_optiongroup("type");
+ hfreq << ext->get_param_float("hfreq") / 100;
+ vfreq << ext->get_param_float("vfreq") / 100;
+ complexity << ext->get_param_int("complexity");
+ variation << ext->get_param_int("variation");
+ reduction << (-1000 - ext->get_param_int("reduction"));
+ blend << ext->get_param_optiongroup("blend");
+ lightness << ext->get_param_float("lightness");
+ grain << ext->get_param_float("grain");
+ erase << ext->get_param_float("erase");
+ blur << ext->get_param_float("blur");
+
+ guint32 fcolor = ext->get_param_color("fcolor");
+ r << ((fcolor >> 24) & 0xff);
+ g << ((fcolor >> 16) & 0xff);
+ b << ((fcolor >> 8) & 0xff);
+ a << (fcolor & 0xff) / 255.0F;
+
+ guint32 pcolor = ext->get_param_color("pcolor");
+ br << ((pcolor >> 24) & 0xff);
+ bg << ((pcolor >> 16) & 0xff);
+ bb << ((pcolor >> 8) & 0xff);
+ ba << (pcolor & 0xff) / 255.0F;
+
+ if (ext->get_param_bool("iof"))
+ iof << "SourceGraphic";
+ else
+ iof << "flood2";
+
+ if (ext->get_param_bool("iop"))
+ iop << "SourceGraphic";
+ else
+ iop << "flood1";
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Point Engraving\" style=\"color-interpolation-filters:sRGB;\">\n"
+ "<feConvolveMatrix in=\"SourceGraphic\" kernelMatrix=\"0 250 0 250 %s 250 0 250 0\" order=\"3 3\" result=\"convolve\" />\n"
+ "<feBlend in=\"convolve\" in2=\"SourceGraphic\" mode=\"%s\" result=\"blend\" />\n"
+ "<feTurbulence type=\"%s\" baseFrequency=\"%s %s\" numOctaves=\"%s\" seed=\"%s\" result=\"turbulence\" />\n"
+ "<feColorMatrix in=\"blend\" type=\"luminanceToAlpha\" result=\"colormatrix1\" />\n"
+ "<feComposite in=\"turbulence\" in2=\"colormatrix1\" k1=\"%s\" k2=\"%s\" k4=\"%s\" operator=\"arithmetic\" result=\"composite1\" />\n"
+ "<feColorMatrix in=\"composite1\" values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 10 -9 \" result=\"colormatrix2\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood1\" />\n"
+ "<feComposite in=\"%s\" in2=\"blur\" operator=\"out\" result=\"composite2\" />\n"
+ "<feFlood flood-color=\"rgb(%s,%s,%s)\" flood-opacity=\"%s\" result=\"flood2\" />\n"
+ "<feComposite in=\"%s\" in2=\"blur\" operator=\"in\" result=\"composite3\" />\n"
+ "<feComposite in=\"composite3\" in2=\"composite2\" k2=\"%s\" k3=\"%s\" operator=\"arithmetic\" result=\"composite4\" />\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite5\" />\n"
+ "</filter>\n", reduction.str().c_str(), blend.str().c_str(),
+ type.str().c_str(), hfreq.str().c_str(), vfreq.str().c_str(), complexity.str().c_str(), variation.str().c_str(),
+ lightness.str().c_str(), grain.str().c_str(), erase.str().c_str(), blur.str().c_str(),
+ br.str().c_str(), bg.str().c_str(), bb.str().c_str(), ba.str().c_str(), iop.str().c_str(),
+ r.str().c_str(), g.str().c_str(), b.str().c_str(), a.str().c_str(), iof.str().c_str(),
+ a.str().c_str(), ba.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Point engraving filter */
+
+/**
+ \brief Custom predefined Poster paint filter.
+
+ Poster and painting effects.
+
+ Filter's parameters:
+ * Effect type (enum, default "Normal") ->
+ Normal = feComponentTransfer
+ Dented = Normal + intermediate values
+ * Transfer type (enum, default "discrete") -> component (type)
+ * Levels (0->15, default 5) -> component (tableValues)
+ * Blend mode (enum, default "Lighten") -> blend (mode)
+ * Primary simplify (0.01->100., default 4.) -> blur1 (stdDeviation)
+ * Secondary simplify (0.01->100., default 0.5) -> blur2 (stdDeviation)
+ * Pre-saturation (0.->1., default 1.) -> color1 (values)
+ * Post-saturation (0.->1., default 1.) -> color2 (values)
+ * Simulate antialiasing (boolean, default false) -> blur3 (true->stdDeviation=0.5, false->stdDeviation=0.01)
+*/
+class Posterize : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Posterize ( ) : Filter() { };
+ ~Posterize ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Poster Paint") "</name>\n"
+ "<id>org.inkscape.effect.filter.Posterize</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Effect type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"normal\">Normal</option>\n"
+ "<option value=\"dented\">Dented</option>\n"
+ "</param>\n"
+ "<param name=\"table\" gui-text=\"" N_("Transfer type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"discrete\">" N_("Poster") "</option>\n"
+ "<option value=\"table\">" N_("Painting") "</option>\n"
+ "</param>\n"
+ "<param name=\"levels\" gui-text=\"" N_("Levels") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"15\">5</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Blend mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"lighten\">Lighten</option>\n"
+ "<option value=\"normal\">Normal</option>\n"
+ "<option value=\"darken\">Darken</option>\n"
+ "<option value=\"multiply\">Multiply</option>\n"
+ "<option value=\"screen\">Screen</option>\n"
+ "</param>\n"
+ "<param name=\"blur1\" gui-text=\"" N_("Simplify (primary)") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">4.0</param>\n"
+ "<param name=\"blur2\" gui-text=\"" N_("Simplify (secondary)") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">0.5</param>\n"
+ "<param name=\"presaturation\" gui-text=\"" N_("Pre-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"1.00\">1.00</param>\n"
+ "<param name=\"postsaturation\" gui-text=\"" N_("Post-saturation") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.00\" max=\"1.00\">1.00</param>\n"
+ "<param name=\"antialiasing\" gui-text=\"" N_("Simulate antialiasing") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Poster and painting effects") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Posterize());
+ // clang-format on
+ };
+};
+
+gchar const *
+Posterize::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream table;
+ std::ostringstream blendmode;
+ std::ostringstream blur1;
+ std::ostringstream blur2;
+ std::ostringstream presat;
+ std::ostringstream postsat;
+ std::ostringstream transf;
+ std::ostringstream antialias;
+
+ table << ext->get_param_optiongroup("table");
+ blendmode << ext->get_param_optiongroup("blend");
+ blur1 << ext->get_param_float("blur1");
+ blur2 << ext->get_param_float("blur2");
+ presat << ext->get_param_float("presaturation");
+ postsat << ext->get_param_float("postsaturation");
+
+ // TransfertComponent table values are calculated based on the poster type.
+ transf << "0";
+ int levels = ext->get_param_int("levels") + 1;
+ const gchar *effecttype = ext->get_param_optiongroup("type");
+ if (levels == 1) {
+ if ((g_ascii_strcasecmp("dented", effecttype) == 0)) {
+ transf << " 1 0 1";
+ } else {
+ transf << " 1";
+ }
+ } else {
+ for ( int step = 1 ; step <= levels ; step++ ) {
+ float val = (float) step / levels;
+ transf << " " << val;
+ if ((g_ascii_strcasecmp("dented", effecttype) == 0)) {
+ transf << " " << (val - ((float) 1 / (3 * levels))) << " " << (val + ((float) 1 / (2 * levels)));
+ }
+ }
+ }
+ transf << " 1";
+
+ if (ext->get_param_bool("antialiasing"))
+ antialias << "0.5";
+ else
+ antialias << "0.01";
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Poster Paint\">\n"
+ "<feComposite operator=\"arithmetic\" k2=\"1\" result=\"composite1\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "<feBlend in2=\"blur1\" mode=\"%s\" result=\"blend\"/>\n"
+ "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"color1\" />\n"
+ "<feComponentTransfer result=\"component\">\n"
+ "<feFuncR type=\"%s\" tableValues=\"%s\" />\n"
+ "<feFuncG type=\"%s\" tableValues=\"%s\" />\n"
+ "<feFuncB type=\"%s\" tableValues=\"%s\" />\n"
+ "</feComponentTransfer>\n"
+ "<feColorMatrix type=\"saturate\" values=\"%s\" result=\"color2\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur3\" />\n"
+ "<feComposite in2=\"SourceGraphic\" operator=\"in\" result=\"composite3\" />\n"
+ "</filter>\n", blur1.str().c_str(), blur2.str().c_str(), blendmode.str().c_str(), presat.str().c_str(), table.str().c_str(), transf.str().c_str(), table.str().c_str(), transf.str().c_str(), table.str().c_str(), transf.str().c_str(), postsat.str().c_str(), antialias.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Posterize filter */
+
+/**
+ \brief Custom predefined Posterize basic filter.
+
+ Simple posterizing effect
+
+ Filter's parameters:
+ * Levels (0->20, default 5) -> component1 (tableValues)
+ * Blur (0.01->20., default 4.) -> blur1 (stdDeviation)
+*/
+class PosterizeBasic : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ PosterizeBasic ( ) : Filter() { };
+ ~PosterizeBasic ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Posterize Basic") "</name>\n"
+ "<id>org.inkscape.effect.filter.PosterizeBasic</id>\n"
+ "<param name=\"levels\" gui-text=\"" N_("Levels") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"20\">5</param>\n"
+ "<param name=\"blur\" gui-text=\"" N_("Simplify") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"20.00\">4.0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Image Paint and Draw") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Simple posterizing effect") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new PosterizeBasic());
+ // clang-format on
+ };
+};
+
+gchar const *
+PosterizeBasic::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream blur;
+ std::ostringstream transf;
+
+ blur << ext->get_param_float("blur");
+
+ transf << "0";
+ int levels = ext->get_param_int("levels") + 1;
+ for ( int step = 1 ; step <= levels ; step++ ) {
+ const float val = (float) step / levels;
+ transf << " " << val;
+ }
+ transf << " 1";
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Posterize Basic\">\n"
+ "<feGaussianBlur stdDeviation=\"%s\" result=\"blur1\" />\n"
+ "<feComponentTransfer in=\"blur1\" result=\"component1\">\n"
+ "<feFuncR type=\"discrete\" tableValues=\"%s\" />\n"
+ "<feFuncG type=\"discrete\" tableValues=\"%s\" />\n"
+ "<feFuncB type=\"discrete\" tableValues=\"%s\" />\n"
+ "</feComponentTransfer>\n"
+ "<feComposite in=\"component1\" in2=\"SourceGraphic\" operator=\"in\" />\n"
+ "</filter>\n", blur.str().c_str(), transf.str().c_str(), transf.str().c_str(), transf.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* PosterizeBasic filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'PAINT' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PAINT_H__ */
diff --git a/src/extension/internal/filter/protrusions.h b/src/extension/internal/filter/protrusions.h
new file mode 100644
index 0000000..ccf54b4
--- /dev/null
+++ b/src/extension/internal/filter/protrusions.h
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PROTRUSIONS_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PROTRUSIONS_H__
+/* Change the 'PROTRUSIONS' above to be your file name */
+
+/*
+ * Copyright (C) 2008 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Protrusion filters
+ * Snow
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+
+/**
+ \brief Custom predefined Snow filter.
+
+ Snow has fallen on object.
+*/
+class Snow : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Snow ( ) : Filter() { };
+ ~Snow ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+public:
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Snow Crest") "</name>\n"
+ "<id>org.inkscape.effect.filter.snow</id>\n"
+ "<param name=\"drift\" gui-text=\"" N_("Drift Size") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"20.0\">3.5</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"Protrusions\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Snow has fallen on object") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Snow());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Snow::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream drift;
+ drift << ext->get_param_float("drift");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Snow\">\n"
+ "<feConvolveMatrix order=\"3 3\" kernelMatrix=\"1 1 1 0 0 0 -1 -1 -1\" preserveAlpha=\"false\" divisor=\"3\"/>\n"
+ "<feMorphology operator=\"dilate\" radius=\"1 %s\"/>\n"
+ "<feGaussianBlur stdDeviation=\"1.6270889487870621\" result=\"result0\"/>\n"
+ "<feColorMatrix values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 10 0\" result=\"result1\"/>\n"
+ "<feOffset dx=\"0\" dy=\"1\" result=\"result5\"/>\n"
+ "<feDiffuseLighting in=\"result0\" diffuseConstant=\"2.2613065326633168\" surfaceScale=\"1\">\n"
+ "<feDistantLight azimuth=\"225\" elevation=\"32\"/>\n"
+ "</feDiffuseLighting>\n"
+ "<feComposite in2=\"result1\" operator=\"in\" result=\"result2\"/>\n"
+ "<feColorMatrix values=\"0.4 0 0 0 0.6 0 0.4 0 0 0.6 0 0 0 0 1 0 0 0 1 0\" result=\"result4\"/>\n"
+ "<feComposite in2=\"result5\" in=\"result4\"/>\n"
+ "<feComposite in2=\"SourceGraphic\"/>\n"
+ "</filter>\n", drift.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Snow filter */
+
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'PROTRUSIONS' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_PROTRUSIONS_H__ */
diff --git a/src/extension/internal/filter/shadows.h b/src/extension/internal/filter/shadows.h
new file mode 100644
index 0000000..2616b25
--- /dev/null
+++ b/src/extension/internal/filter/shadows.h
@@ -0,0 +1,194 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_SHADOWS_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_SHADOWS_H__
+/* Change the 'SHADOWS' above to be your file name */
+
+/*
+ * Copyright (C) 2013 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Shadow filters
+ * Drop shadow
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Drop shadow filter.
+
+ Colorizable Drop shadow.
+
+ Filter's parameters:
+ * Blur radius (0.->200., default 3) -> blur (stdDeviation)
+ * Horizontal offset (-50.->50., default 6.0) -> offset (dx)
+ * Vertical offset (-50.->50., default 6.0) -> offset (dy)
+ * Blur type (enum, default outer) ->
+ outer = composite1 (operator="in"), composite2 (operator="over", in1="SourceGraphic", in2="offset")
+ inner = composite1 (operator="out"), composite2 (operator="atop", in1="offset", in2="SourceGraphic")
+ innercut = composite1 (operator="in"), composite2 (operator="out", in1="offset", in2="SourceGraphic")
+ outercut = composite1 (operator="out"), composite2 (operator="in", in1="SourceGraphic", in2="offset")
+ shadow = composite1 (operator="out"), composite2 (operator="atop", in1="offset", in2="offset")
+ * Color (guint, default 0,0,0,127) -> flood (flood-opacity, flood-color)
+ * Use object's color (boolean, default false) -> composite1 (in1, in2)
+*/
+class ColorizableDropShadow : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ColorizableDropShadow ( ) : Filter() { };
+ ~ColorizableDropShadow ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Drop Shadow") "</name>\n"
+ "<id>org.inkscape.effect.filter.ColorDropShadow</id>\n"
+ "<param name=\"tab\" type=\"notebook\">\n"
+ "<page name=\"optionstab\" gui-text=\"" N_("Options") "\">\n"
+ "<param name=\"blur\" gui-text=\"" N_("Blur radius (px)") "\" type=\"float\" appearance=\"full\" min=\"0.0\" max=\"200.0\">3.0</param>\n"
+ "<param name=\"xoffset\" gui-text=\"" N_("Horizontal offset (px)") "\" type=\"float\" appearance=\"full\" min=\"-50.0\" max=\"50.0\">6.0</param>\n"
+ "<param name=\"yoffset\" gui-text=\"" N_("Vertical offset (px)") "\" type=\"float\" appearance=\"full\" min=\"-50.0\" max=\"50.0\">6.0</param>\n"
+ "<param name=\"type\" gui-text=\"" N_("Shadow type:") "\" type=\"optiongroup\" appearance=\"combo\" >\n"
+ "<option value=\"outer\">" N_("Outer") "</option>\n"
+ "<option value=\"inner\">" N_("Inner") "</option>\n"
+ "<option value=\"outercut\">" N_("Outer cutout") "</option>\n"
+ "<option value=\"innercut\">" N_("Inner cutout") "</option>\n"
+ "<option value=\"shadow\">" N_("Shadow only") "</option>\n"
+ "</param>\n"
+ "</page>\n"
+ "<page name=\"coltab\" gui-text=\"" N_("Blur color") "\">\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">127</param>\n"
+ "<param name=\"objcolor\" gui-text=\"" N_("Use object's color") "\" type=\"bool\" >false</param>\n"
+ "</page>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Shadows and Glows") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Colorizable Drop shadow") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ColorizableDropShadow());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+ColorizableDropShadow::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream blur;
+ std::ostringstream a;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream x;
+ std::ostringstream y;
+ std::ostringstream comp1in1;
+ std::ostringstream comp1in2;
+ std::ostringstream comp1op;
+ std::ostringstream comp2in1;
+ std::ostringstream comp2in2;
+ std::ostringstream comp2op;
+
+ const gchar *type = ext->get_param_optiongroup("type");
+ guint32 color = ext->get_param_color("color");
+
+ blur << ext->get_param_float("blur");
+ x << ext->get_param_float("xoffset");
+ y << ext->get_param_float("yoffset");
+ a << (color & 0xff) / 255.0F;
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+
+ // Select object or user-defined color
+ if ((g_ascii_strcasecmp("innercut", type) == 0)) {
+ if (ext->get_param_bool("objcolor")) {
+ comp2in1 << "SourceGraphic";
+ comp2in2 << "offset";
+ } else {
+ comp2in1 << "offset";
+ comp2in2 << "SourceGraphic";
+ }
+ } else {
+ if (ext->get_param_bool("objcolor")) {
+ comp1in1 << "SourceGraphic";
+ comp1in2 << "flood";
+ } else {
+ comp1in1 << "flood";
+ comp1in2 << "SourceGraphic";
+ }
+ }
+
+ // Shadow mode
+ if ((g_ascii_strcasecmp("outer", type) == 0)) {
+ comp1op << "in";
+ comp2op << "over";
+ comp2in1 << "SourceGraphic";
+ comp2in2 << "offset";
+ } else if ((g_ascii_strcasecmp("inner", type) == 0)) {
+ comp1op << "out";
+ comp2op << "atop";
+ comp2in1 << "offset";
+ comp2in2 << "SourceGraphic";
+ } else if ((g_ascii_strcasecmp("outercut", type) == 0)) {
+ comp1op << "in";
+ comp2op << "out";
+ comp2in1 << "offset";
+ comp2in2 << "SourceGraphic";
+ } else if ((g_ascii_strcasecmp("innercut", type) == 0)){
+ comp1op << "out";
+ comp1in1 << "flood";
+ comp1in2 << "SourceGraphic";
+ comp2op << "in";
+ } else { //shadow
+ comp1op << "in";
+ comp2op << "atop";
+ comp2in1 << "offset";
+ comp2in2 << "offset";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Drop Shadow\">\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feComposite in=\"%s\" in2=\"%s\" operator=\"%s\" result=\"composite1\" />\n"
+ "<feGaussianBlur in=\"composite1\" stdDeviation=\"%s\" result=\"blur\" />\n"
+ "<feOffset dx=\"%s\" dy=\"%s\" result=\"offset\" />\n"
+ "<feComposite in=\"%s\" in2=\"%s\" operator=\"%s\" result=\"composite2\" />\n"
+ "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(),
+ comp1in1.str().c_str(), comp1in2.str().c_str(), comp1op.str().c_str(),
+ blur.str().c_str(), x.str().c_str(), y.str().c_str(),
+ comp2in1.str().c_str(), comp2in2.str().c_str(), comp2op.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Drop shadow filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'SHADOWS' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_SHADOWS_H__ */
diff --git a/src/extension/internal/filter/textures.h b/src/extension/internal/filter/textures.h
new file mode 100644
index 0000000..66066ae
--- /dev/null
+++ b/src/extension/internal/filter/textures.h
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TEXTURES_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TEXTURES_H__
+/* Change the 'TEXTURES' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Protrusion filters
+ * Ink blot
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+
+/**
+ \brief Custom predefined Ink Blot filter.
+
+ Inkblot on tissue or rough paper.
+
+ Filter's parameters:
+
+ * Turbulence type (enum, default fractalNoise else turbulence) -> turbulence (type)
+ * Frequency (0.001->1., default 0.04) -> turbulence (baseFrequency [/100])
+ * Complexity (1->5, default 3) -> turbulence (numOctaves)
+ * Variation (0->100, default 0) -> turbulence (seed)
+ * Horizontal inlay (0.01->30., default 10) -> blur1 (stdDeviation x)
+ * Vertical inlay (0.01->30., default 10) -> blur1 (stdDeviation y)
+ * Displacement (0.->100., default 50) -> map (scale)
+ * Blend (0.01->30., default 5) -> blur2 (stdDeviation)
+ * Stroke (enum, default over) -> composite (operator)
+ * Arithmetic stroke options
+ * k1 (-10.->10., default 1.5)
+ * k2 (-10.->10., default -0.25)
+ * k3 (-10.->10., default 0.5)
+*/
+class InkBlot : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ InkBlot ( ) : Filter() { };
+ ~InkBlot ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+public:
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Ink Blot") "</name>\n"
+ "<id>org.inkscape.effect.filter.InkBlot</id>\n"
+ "<param name=\"type\" gui-text=\"" N_("Turbulence type:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"fractalNoise\">Fractal noise</option>\n"
+ "<option value=\"turbulence\">Turbulence</option>\n"
+ "</param>\n"
+ "<param name=\"freq\" gui-text=\"" N_("Frequency:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"100.00\">4</param>\n"
+ "<param name=\"complexity\" gui-text=\"" N_("Complexity:") "\" type=\"int\" appearance=\"full\" min=\"1\" max=\"5\">3</param>\n"
+ "<param name=\"variation\" gui-text=\"" N_("Variation:") "\" type=\"int\" appearance=\"full\" min=\"0\" max=\"100\">0</param>\n"
+ "<param name=\"hblur\" gui-text=\"" N_("Horizontal inlay:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">10</param>\n"
+ "<param name=\"vblur\" gui-text=\"" N_("Vertical inlay:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">10</param>\n"
+ "<param name=\"displacement\" gui-text=\"" N_("Displacement:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"100.00\">50</param>\n"
+ "<param name=\"blend\" gui-text=\"" N_("Blend:") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"30.00\">5</param>\n"
+ "<param name=\"stroke\" gui-text=\"" N_("Stroke:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"over\">" N_("Wide") "</option>\n"
+ "<option value=\"atop\">" N_("Normal") "</option>\n"
+ "<option value=\"in\">" N_("Narrow") "</option>\n"
+ "<option value=\"xor\">" N_("Overlapping") "</option>\n"
+ "<option value=\"out\">" N_("External") "</option>\n"
+ "<option value=\"arithmetic\">" N_("Custom") "</option>\n"
+ "</param>\n"
+ "<label appearance=\"header\">" N_("Custom stroke options") "</label>\n"
+ "<param name=\"k1\" gui-text=\"" N_("k1:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1.5</param>\n"
+ "<param name=\"k2\" gui-text=\"" N_("k2:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">-0.25</param>\n"
+ "<param name=\"k3\" gui-text=\"" N_("k3:") "\" type=\"float\" indent=\"1\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"Textures\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Inkblot on tissue or rough paper") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new InkBlot());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+InkBlot::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream type;
+ std::ostringstream freq;
+ std::ostringstream complexity;
+ std::ostringstream variation;
+ std::ostringstream hblur;
+ std::ostringstream vblur;
+ std::ostringstream displacement;
+ std::ostringstream blend;
+ std::ostringstream stroke;
+ std::ostringstream custom;
+
+ type << ext->get_param_optiongroup("type");
+ freq << ext->get_param_float("freq") / 100;
+ complexity << ext->get_param_int("complexity");
+ variation << ext->get_param_int("variation");
+ hblur << ext->get_param_float("hblur");
+ vblur << ext->get_param_float("vblur");
+ displacement << ext->get_param_float("displacement");
+ blend << ext->get_param_float("blend");
+
+ const gchar *ope = ext->get_param_optiongroup("stroke");
+ if (g_ascii_strcasecmp("arithmetic", ope) == 0) {
+ custom << "k1=\"" << ext->get_param_float("k1") << "\" k2=\"" << ext->get_param_float("k2") << "\" k3=\"" << ext->get_param_float("k3") << "\"";
+ } else {
+ custom << "";
+ }
+
+ stroke << ext->get_param_optiongroup("stroke");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" x=\"-0.15\" width=\"1.3\" y=\"-0.15\" height=\"1.3\" inkscape:label=\"Ink Blot\" >\n"
+ "<feGaussianBlur in=\"SourceGraphic\" stdDeviation=\"%s %s\" result=\"blur1\" />\n"
+ "<feTurbulence type=\"%s\" baseFrequency=\"%s\" numOctaves=\"%s\" seed=\"%s\" result=\"turbulence\" />\n"
+ "<feDisplacementMap in=\"blur1\" in2=\"turbulence\" xChannelSelector=\"R\" yChannelSelector=\"G\" scale=\"%s\" result=\"map\" />\n"
+ "<feGaussianBlur in=\"map\" stdDeviation=\"%s\" result=\"blur2\" />\n"
+ "<feComposite in=\"blur2\" in2=\"map\" %s operator=\"%s\" result=\"composite\" />\n"
+ "</filter>\n", hblur.str().c_str(), vblur.str().c_str(), type.str().c_str(),
+ freq.str().c_str(), complexity.str().c_str(), variation.str().c_str(),
+ displacement.str().c_str(), blend.str().c_str(),
+ custom.str().c_str(), stroke.str().c_str() );
+ // clang-format on
+
+ return _filter;
+
+}; /* Ink Blot filter */
+
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'TEXTURES' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TEXTURES_H__ */
diff --git a/src/extension/internal/filter/transparency.h b/src/extension/internal/filter/transparency.h
new file mode 100644
index 0000000..39cd240
--- /dev/null
+++ b/src/extension/internal/filter/transparency.h
@@ -0,0 +1,420 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TRANSPARENCY_H__
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TRANSPARENCY_H__
+/* Change the 'TRANSPARENCY' above to be your file name */
+
+/*
+ * Copyright (C) 2011 Authors:
+ * Ivan Louette (filters)
+ * Nicolas Dufour (UI) <nicoduf@yahoo.fr>
+ *
+ * Fill and transparency filters
+ * Blend
+ * Channel transparency
+ * Light eraser
+ * Opacity
+ * Silhouette
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/* ^^^ Change the copyright to be you and your e-mail address ^^^ */
+
+#include "filter.h"
+
+#include "extension/internal/clear-n_.h"
+#include "extension/system.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+namespace Filter {
+
+/**
+ \brief Custom predefined Blend filter.
+
+ Blend objects with background images or with themselves
+
+ Filter's parameters:
+ * Source (enum [SourceGraphic,BackgroundImage], default BackgroundImage) -> blend (in2)
+ * Mode (enum, all blend modes, default Multiply) -> blend (mode)
+*/
+
+class Blend : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Blend ( ) : Filter() { };
+ ~Blend ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Blend") "</name>\n"
+ "<id>org.inkscape.effect.filter.Blend</id>\n"
+ "<param name=\"source\" gui-text=\"" N_("Source:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"BackgroundImage\">" N_("Background") "</option>\n"
+ "<option value=\"SourceGraphic\">" N_("Image") "</option>\n"
+ "</param>\n"
+ "<param name=\"mode\" gui-text=\"" N_("Mode:") "\" type=\"optiongroup\" appearance=\"combo\">\n"
+ "<option value=\"multiply\">" N_("Multiply") "</option>\n"
+ "<option value=\"normal\">" N_("Normal") "</option>\n"
+ "<option value=\"screen\">" N_("Screen") "</option>\n"
+ "<option value=\"darken\">" N_("Darken") "</option>\n"
+ "<option value=\"lighten\">" N_("Lighten") "</option>\n"
+ "</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Fill and Transparency") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Blend objects with background images or with themselves") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Blend());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Blend::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream source;
+ std::ostringstream mode;
+
+ source << ext->get_param_optiongroup("source");
+ mode << ext->get_param_optiongroup("mode");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Blend\">\n"
+ "<feBlend in2=\"%s\" mode=\"%s\" result=\"blend\" />\n"
+ "</filter>\n", source.str().c_str(), mode.str().c_str() );
+ // clang-format on
+
+ return _filter;
+}; /* Blend filter */
+
+/**
+ \brief Custom predefined Channel transparency filter.
+
+ Channel transparency filter.
+
+ Filter's parameters:
+ * Saturation (0.->1., default 1.) -> colormatrix1 (values)
+ * Red (-10.->10., default -1.) -> colormatrix2 (values)
+ * Green (-10.->10., default 0.5) -> colormatrix2 (values)
+ * Blue (-10.->10., default 0.5) -> colormatrix2 (values)
+ * Alpha (-10.->10., default 1.) -> colormatrix2 (values)
+ * Flood colors (guint, default 16777215) -> flood (flood-opacity, flood-color)
+ * Inverted (boolean, default false) -> composite1 (operator, true='in', false='out')
+
+ Matrix:
+ 1 0 0 0 0
+ 0 1 0 0 0
+ 0 0 1 0 0
+ R G B A 0
+*/
+class ChannelTransparency : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ ChannelTransparency ( ) : Filter() { };
+ ~ChannelTransparency ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Channel Transparency") "</name>\n"
+ "<id>org.inkscape.effect.filter.ChannelTransparency</id>\n"
+ "<param name=\"red\" gui-text=\"" N_("Red") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">-1</param>\n"
+ "<param name=\"green\" gui-text=\"" N_("Green") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n"
+ "<param name=\"blue\" gui-text=\"" N_("Blue") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">0.5</param>\n"
+ "<param name=\"alpha\" gui-text=\"" N_("Alpha") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"-10.\" max=\"10.\">1</param>\n"
+ "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Fill and Transparency") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Replace RGB with transparency") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new ChannelTransparency());
+ // clang-format on
+ };
+};
+
+gchar const *
+ChannelTransparency::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream red;
+ std::ostringstream green;
+ std::ostringstream blue;
+ std::ostringstream alpha;
+ std::ostringstream invert;
+
+ red << ext->get_param_float("red");
+ green << ext->get_param_float("green");
+ blue << ext->get_param_float("blue");
+ alpha << ext->get_param_float("alpha");
+
+ if (!ext->get_param_bool("invert")) {
+ invert << "in";
+ } else {
+ invert << "xor";
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Channel Transparency\" style=\"color-interpolation-filters:sRGB;\" >\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s %s %s 0 \" in=\"SourceGraphic\" result=\"colormatrix\" />\n"
+ "<feComposite in=\"colormatrix\" in2=\"SourceGraphic\" operator=\"%s\" result=\"composite1\" />\n"
+ "</filter>\n", red.str().c_str(), green.str().c_str(), blue.str().c_str(), alpha.str().c_str(),
+ invert.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Channel transparency filter */
+
+/**
+ \brief Custom predefined LightEraser filter.
+
+ Make the lightest parts of the object progressively transparent.
+
+ Filter's parameters:
+ * Expansion (0.->1000., default 50) -> colormatrix (4th value, multiplicator)
+ * Erosion (1.->1000., default 100) -> colormatrix (first 3 values, multiplicator)
+ * Global opacity (0.->1., default 1.) -> composite (k2)
+ * Inverted (boolean, default false) -> colormatrix (values, true: first 3 values positive, 4th negative)
+
+*/
+class LightEraser : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ LightEraser ( ) : Filter() { };
+ ~LightEraser ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Light Eraser") "</name>\n"
+ "<id>org.inkscape.effect.filter.LightEraser</id>\n"
+ "<param name=\"expand\" gui-text=\"" N_("Expansion") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"1000\">50</param>\n"
+ "<param name=\"erode\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" min=\"1\" max=\"1000\">100</param>\n"
+ "<param name=\"opacity\" gui-text=\"" N_("Global opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">1</param>\n"
+ "<param name=\"invert\" gui-text=\"" N_("Inverted") "\" type=\"bool\">false</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Fill and Transparency") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Make the lightest parts of the object progressively transparent") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new LightEraser());
+ // clang-format on
+ };
+};
+
+gchar const *
+LightEraser::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream expand;
+ std::ostringstream erode;
+ std::ostringstream opacity;
+
+ opacity << ext->get_param_float("opacity");
+
+ if (ext->get_param_bool("invert")) {
+ expand << (ext->get_param_float("erode") * 0.2125) << " "
+ << (ext->get_param_float("erode") * 0.7154) << " "
+ << (ext->get_param_float("erode") * 0.0721);
+ erode << (-ext->get_param_float("expand"));
+ } else {
+ expand << (-ext->get_param_float("erode") * 0.2125) << " "
+ << (-ext->get_param_float("erode") * 0.7154) << " "
+ << (-ext->get_param_float("erode") * 0.0721);
+ erode << ext->get_param_float("expand");
+ }
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Light Eraser\" style=\"color-interpolation-filters:sRGB;\" >\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 %s %s 0 \" result=\"colormatrix\" />\n"
+ "<feComposite in2=\"colormatrix\" operator=\"arithmetic\" k2=\"%s\" result=\"composite\" />\n"
+ "</filter>\n", expand.str().c_str(), erode.str().c_str(), opacity.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Light Eraser filter */
+
+
+/**
+ \brief Custom predefined Opacity filter.
+
+ Set opacity and strength of opacity boundaries.
+
+ Filter's parameters:
+ * Expansion (0.->1000., default 5) -> colormatrix (last-1th value)
+ * Erosion (0.->1000., default 1) -> colormatrix (last value)
+ * Global opacity (0.->1., default 1.) -> composite (k2)
+
+*/
+class Opacity : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Opacity ( ) : Filter() { };
+ ~Opacity ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Opacity") "</name>\n"
+ "<id>org.inkscape.effect.filter.Opacity</id>\n"
+ "<param name=\"expand\" gui-text=\"" N_("Expansion") "\" type=\"float\" appearance=\"full\" min=\"1\" max=\"1000\">5</param>\n"
+ "<param name=\"erode\" gui-text=\"" N_("Erosion") "\" type=\"float\" appearance=\"full\" min=\"0\" max=\"1000\">1</param>\n"
+ "<param name=\"opacity\" gui-text=\"" N_("Global opacity") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.\" max=\"1.\">1</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Fill and Transparency") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Set opacity and strength of opacity boundaries") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Opacity());
+ // clang-format on
+ };
+};
+
+gchar const *
+Opacity::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream matrix;
+ std::ostringstream opacity;
+
+ opacity << ext->get_param_float("opacity");
+
+ matrix << (ext->get_param_float("expand")) << " "
+ << (-ext->get_param_float("erode"));
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" inkscape:label=\"Opacity\" style=\"color-interpolation-filters:sRGB;\" >\n"
+ "<feColorMatrix values=\"1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 %s \" result=\"colormatrix\" />\n"
+ "<feComposite in2=\"colormatrix\" operator=\"arithmetic\" k2=\"%s\" result=\"composite\" />\n"
+ "</filter>\n", matrix.str().c_str(), opacity.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Opacity filter */
+
+/**
+ \brief Custom predefined Silhouette filter.
+
+ Repaint anything visible monochrome
+
+ Filter's parameters:
+ * Blur (0.01->50., default 0.01) -> blur (stdDeviation)
+ * Cutout (boolean, default False) -> composite (false=in, true=out)
+ * Color (guint, default 0,0,0,255) -> flood (flood-color, flood-opacity)
+*/
+
+class Silhouette : public Inkscape::Extension::Internal::Filter::Filter {
+protected:
+ gchar const * get_filter_text (Inkscape::Extension::Extension * ext) override;
+
+public:
+ Silhouette ( ) : Filter() { };
+ ~Silhouette ( ) override { if (_filter != nullptr) g_free((void *)_filter); return; }
+
+ static void init () {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Silhouette") "</name>\n"
+ "<id>org.inkscape.effect.filter.Silhouette</id>\n"
+ "<param name=\"blur\" gui-text=\"" N_("Blur") "\" type=\"float\" appearance=\"full\" precision=\"2\" min=\"0.01\" max=\"50.00\">0.01</param>\n"
+ "<param name=\"cutout\" gui-text=\"" N_("Cutout") "\" type=\"bool\">false</param>\n"
+ "<param name=\"color\" gui-text=\"" N_("Color") "\" type=\"color\">255</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Filters") "\">\n"
+ "<submenu name=\"" N_("Fill and Transparency") "\"/>\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Repaint anything visible monochrome") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Silhouette());
+ // clang-format on
+ };
+
+};
+
+gchar const *
+Silhouette::get_filter_text (Inkscape::Extension::Extension * ext)
+{
+ if (_filter != nullptr) g_free((void *)_filter);
+
+ std::ostringstream a;
+ std::ostringstream r;
+ std::ostringstream g;
+ std::ostringstream b;
+ std::ostringstream cutout;
+ std::ostringstream blur;
+
+ guint32 color = ext->get_param_color("color");
+ r << ((color >> 24) & 0xff);
+ g << ((color >> 16) & 0xff);
+ b << ((color >> 8) & 0xff);
+ a << (color & 0xff) / 255.0F;
+ if (ext->get_param_bool("cutout"))
+ cutout << "out";
+ else
+ cutout << "in";
+ blur << ext->get_param_float("blur");
+
+ // clang-format off
+ _filter = g_strdup_printf(
+ "<filter xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" style=\"color-interpolation-filters:sRGB;\" inkscape:label=\"Silhouette\">\n"
+ "<feFlood flood-opacity=\"%s\" flood-color=\"rgb(%s,%s,%s)\" result=\"flood\" />\n"
+ "<feComposite in=\"flood\" in2=\"SourceGraphic\" operator=\"%s\" result=\"composite\" />\n"
+ "<feGaussianBlur stdDeviation=\"%s\" />\n"
+ "</filter>\n", a.str().c_str(), r.str().c_str(), g.str().c_str(), b.str().c_str(), cutout.str().c_str(), blur.str().c_str());
+ // clang-format on
+
+ return _filter;
+}; /* Silhouette filter */
+
+}; /* namespace Filter */
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/* Change the 'TRANSPARENCY' below to be your file name */
+#endif /* SEEN_INKSCAPE_EXTENSION_INTERNAL_FILTER_TRANSPARENCY_H__ */
diff --git a/src/extension/internal/gdkpixbuf-input.cpp b/src/extension/internal/gdkpixbuf-input.cpp
new file mode 100644
index 0000000..7441879
--- /dev/null
+++ b/src/extension/internal/gdkpixbuf-input.cpp
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <set>
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <gdkmm/pixbuf.h>
+#include <gdkmm/pixbufformat.h>
+#include <glib/gprintf.h>
+#include <glibmm/i18n.h>
+
+#include "document.h"
+#include "document-undo.h"
+#include "gdkpixbuf-input.h"
+#include "image-resolution.h"
+#include "preferences.h"
+#include "selection-chemistry.h"
+
+#include "display/cairo-utils.h"
+
+#include "extension/input.h"
+#include "extension/system.h"
+
+#include "io/dir-util.h"
+
+#include "object/sp-image.h"
+#include "object/sp-root.h"
+
+#include "util/units.h"
+
+namespace Inkscape {
+
+namespace Extension {
+namespace Internal {
+
+SPDocument *
+GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri)
+{
+ // Determine whether the image should be embedded
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool ask = prefs->getBool( "/dialogs/import/ask");
+ bool forcexdpi = prefs->getBool( "/dialogs/import/forcexdpi");
+ Glib::ustring link = prefs->getString("/dialogs/import/link");
+ Glib::ustring scale = prefs->getString("/dialogs/import/scale");
+
+ // If we asked about import preferences, get values and update preferences.
+ if (ask) {
+ ask = !mod->get_param_bool("do_not_ask");
+ forcexdpi = (strcmp(mod->get_param_optiongroup("dpi"), "from_default") == 0);
+ link = mod->get_param_optiongroup("link");
+ scale = mod->get_param_optiongroup("scale");
+
+ prefs->setBool( "/dialogs/import/ask", ask );
+ prefs->setBool( "/dialogs/import/forcexdpi", forcexdpi);
+ prefs->setString("/dialogs/import/link", link );
+ prefs->setString("/dialogs/import/scale", scale );
+ }
+
+ bool embed = (link == "embed");
+
+ SPDocument *doc = nullptr;
+ std::unique_ptr<Inkscape::Pixbuf> pb(Inkscape::Pixbuf::create_from_file(uri));
+
+ // TODO: the pixbuf is created again from the base64-encoded attribute in SPImage.
+ // Find a way to create the pixbuf only once.
+
+ if (pb) {
+ doc = SPDocument::createNewDoc(nullptr, TRUE, TRUE);
+ bool saved = DocumentUndo::getUndoSensitive(doc);
+ DocumentUndo::setUndoSensitive(doc, false); // no need to undo in this temporary document
+
+ double width = pb->width();
+ double height = pb->height();
+ double defaultxdpi = prefs->getDouble("/dialogs/import/defaultxdpi/value", Inkscape::Util::Quantity::convert(1, "in", "px"));
+
+ ImageResolution *ir = nullptr;
+ double xscale = 1;
+ double yscale = 1;
+
+
+ if (!ir && !forcexdpi) {
+ ir = new ImageResolution(uri);
+ }
+ if (ir && ir->ok()) {
+ xscale = 960.0 / round(10.*ir->x()); // round-off to 0.1 dpi
+ yscale = 960.0 / round(10.*ir->y());
+ // prevent crash on image with too small dpi (bug 1479193)
+ if (ir->x() <= .05)
+ xscale = 960.0;
+ if (ir->y() <= .05)
+ yscale = 960.0;
+ } else {
+ xscale = 96.0 / defaultxdpi;
+ yscale = 96.0 / defaultxdpi;
+ }
+
+ width *= xscale;
+ height *= yscale;
+ doc->setWidthAndHeight(Util::Quantity(width, "px"), Util::Quantity(height, "px"));
+
+ 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");
+ image_node->setAttributeSvgDouble("width", width);
+ image_node->setAttributeSvgDouble("height", height);
+
+ // Set default value as we honor "preserveAspectRatio".
+ image_node->setAttribute("preserveAspectRatio", "none");
+
+ // This is actually 'image-rendering'.
+ if( scale.compare( "auto" ) != 0 ) {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, "image-rendering", scale.c_str());
+ sp_repr_css_set(image_node, css, "style");
+ sp_repr_css_attr_unref( css );
+ }
+
+ if (embed) {
+ sp_embed_image(image_node, pb.get());
+ } else {
+ // convert filename to uri
+ gchar* _uri = g_filename_to_uri(uri, nullptr, nullptr);
+ if(_uri) {
+ image_node->setAttribute("xlink:href", _uri);
+ g_free(_uri);
+ } else {
+ image_node->setAttribute("xlink:href", uri);
+ }
+ }
+
+ // Add it to the current layer
+ Inkscape::XML::Node *layer_node = xml_doc->createElement("svg:g");
+ layer_node->setAttribute("inkscape:groupmode", "layer");
+ layer_node->setAttribute("inkscape:label", "Image");
+ doc->getRoot()->appendChildRepr(layer_node);
+ layer_node->appendChild(image_node);
+ Inkscape::GC::release(image_node);
+ Inkscape::GC::release(layer_node);
+ fit_canvas_to_drawing(doc);
+
+ // Set viewBox if it doesn't exist
+ if (!doc->getRoot()->viewBox_set) {
+ // std::cerr << "Viewbox not set, setting" << std::endl;
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit())));
+ }
+
+ // restore undo, as now this document may be shown to the user if a bitmap was opened
+ DocumentUndo::setUndoSensitive(doc, saved);
+ } else {
+ printf("GdkPixbuf loader failed\n");
+ }
+
+ return doc;
+}
+
+#include "clear-n_.h"
+
+void
+GdkpixbufInput::init()
+{
+ static std::vector< Gdk::PixbufFormat > formatlist = Gdk::Pixbuf::get_formats();
+ for (auto i: formatlist) {
+ GdkPixbufFormat *pixformat = i.gobj();
+
+ gchar *name = gdk_pixbuf_format_get_name(pixformat);
+ gchar *description = gdk_pixbuf_format_get_description(pixformat);
+ gchar **extensions = gdk_pixbuf_format_get_extensions(pixformat);
+ gchar **mimetypes = gdk_pixbuf_format_get_mime_types(pixformat);
+
+ for (int i = 0; extensions[i] != nullptr; i++) {
+ for (int j = 0; mimetypes[j] != nullptr; j++) {
+
+ /* thanks but no thanks, we'll handle SVG extensions... */
+ if (strcmp(extensions[i], "svg") == 0) {
+ continue;
+ }
+ if (strcmp(extensions[i], "svgz") == 0) {
+ continue;
+ }
+ if (strcmp(extensions[i], "svg.gz") == 0) {
+ continue;
+ }
+ gchar *caption = g_strdup_printf(_("%s bitmap image import"), name);
+
+ // clang-format off
+ gchar *xmlString = g_strdup_printf(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>%s</name>\n"
+ "<id>org.inkscape.input.gdkpixbuf.%s</id>\n"
+
+ "<param name='link' type='optiongroup' gui-text='" N_("Image Import Type:") "' gui-description='" N_("Embed results in stand-alone, larger SVG files. Link references a file outside this SVG document and all files must be moved together.") "' >\n"
+ "<option value='embed' >" N_("Embed") "</option>\n"
+ "<option value='link' >" N_("Link") "</option>\n"
+ "</param>\n"
+
+ "<param name='dpi' type='optiongroup' gui-text='" N_("Image DPI:") "' gui-description='" N_("Take information from file or use default bitmap import resolution as defined in the preferences.") "' >\n"
+ "<option value='from_file' >" N_("From file") "</option>\n"
+ "<option value='from_default' >" N_("Default import resolution") "</option>\n"
+ "</param>\n"
+
+ "<param name='scale' type='optiongroup' gui-text='" N_("Image Rendering Mode:") "' gui-description='" N_("When an image is upscaled, apply smoothing or keep blocky (pixelated). (Will not work in all browsers.)") "' >\n"
+ "<option value='auto' >" N_("None (auto)") "</option>\n"
+ "<option value='optimizeQuality' >" N_("Smooth (optimizeQuality)") "</option>\n"
+ "<option value='optimizeSpeed' >" N_("Blocky (optimizeSpeed)") "</option>\n"
+ "</param>\n"
+
+ "<param name=\"do_not_ask\" gui-description='" N_("Hide the dialog next time and always apply the same actions.") "' gui-text=\"" N_("Don't ask again") "\" type=\"bool\" >false</param>\n"
+ "<input>\n"
+ "<extension>.%s</extension>\n"
+ "<mimetype>%s</mimetype>\n"
+ "<filetypename>%s (*.%s)</filetypename>\n"
+ "<filetypetooltip>%s</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>",
+ caption,
+ extensions[i],
+ extensions[i],
+ mimetypes[j],
+ name,
+ extensions[i],
+ description
+ );
+ // clang-format off
+
+ Inkscape::Extension::build_from_mem(xmlString, new GdkpixbufInput());
+ g_free(xmlString);
+ g_free(caption);
+ }}
+
+ g_free(name);
+ g_free(description);
+ g_strfreev(mimetypes);
+ g_strfreev(extensions);
+ }
+}
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/gdkpixbuf-input.h b/src/extension/internal/gdkpixbuf-input.h
new file mode 100644
index 0000000..1980700
--- /dev/null
+++ b/src/extension/internal/gdkpixbuf-input.h
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO: insert short description here
+ *//*
+ * Authors: see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef INKSCAPE_EXTENSION_INTERNAL_GDKPIXBUF_INPUT_H
+#define INKSCAPE_EXTENSION_INTERNAL_GDKPIXBUF_INPUT_H
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class GdkpixbufInput : Inkscape::Extension::Implementation::Implementation {
+public:
+ SPDocument *open(Inkscape::Extension::Input *mod,
+ char const *uri) override;
+ static void init();
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+
+#endif /* INKSCAPE_EXTENSION_INTERNAL_GDKPIXBUF_INPUT_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/gimpgrad.cpp b/src/extension/internal/gimpgrad.cpp
new file mode 100644
index 0000000..c22d498
--- /dev/null
+++ b/src/extension/internal/gimpgrad.cpp
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Inkscape::Extension::Internal::GimpGrad implementation
+ */
+
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <color-rgba.h>
+#include "io/sys.h"
+#include "extension/system.h"
+#include "svg/css-ostringstream.h"
+#include "svg/svg-color.h"
+
+#include "gimpgrad.h"
+#include "streq.h"
+#include "strneq.h"
+#include "document.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ \brief A function to allocate anything -- just an example here
+ \param module Unused
+ \return Whether the load was successful
+*/
+bool GimpGrad::load (Inkscape::Extension::Extension */*module*/)
+{
+ // std::cout << "Hey, I'm loading!\n" << std::endl;
+ return TRUE;
+}
+
+/**
+ \brief A function to remove what was allocated
+ \param module Unused
+ \return None
+*/
+void GimpGrad::unload (Inkscape::Extension::Extension */*module*/)
+{
+ // std::cout << "Nooo! I'm being unloaded!" << std::endl;
+ return;
+}
+
+static void append_css_num(Glib::ustring &str, double const num)
+{
+ CSSOStringStream stream;
+ stream << num;
+ str += stream.str();
+}
+
+/**
+ \brief A function to turn a color into a gradient stop
+ \param in_color The color for the stop
+ \param location Where the stop is placed in the gradient
+ \return The text that is the stop. Full SVG containing the element.
+
+ This function encapsulates all of the translation of the ColorRGBA
+ and the location into the gradient. It is really pretty simple except
+ that the ColorRGBA is in floats that are 0 to 1 and the SVG wants
+ hex values from 0 to 255 for color. Otherwise mostly this is just
+ turning the values into strings and returning it.
+*/
+static Glib::ustring stop_svg(ColorRGBA const in_color, double const location)
+{
+ Glib::ustring ret("<stop stop-color=\"");
+
+ char stop_color_css[16];
+ sp_svg_write_color(stop_color_css, sizeof(stop_color_css), in_color.getIntValue());
+ ret += stop_color_css;
+ ret += '"';
+
+ if (in_color[3] != 1) {
+ ret += " stop-opacity=\"";
+ append_css_num(ret, in_color[3]);
+ ret += '"';
+ }
+ ret += " offset=\"";
+ append_css_num(ret, location);
+ ret += "\"/>\n";
+ return ret;
+}
+
+/**
+ \brief Actually open the gradient and turn it into an SPDocument
+ \param module The input module being used
+ \param filename The filename of the gradient to be opened
+ \return A Document with the gradient in it.
+
+ GIMP gradients are pretty simple (atleast the newer format, this
+ function does not handle the old one yet). They start out with
+ the like "GIMP Gradient", then name it, and tell how many entries
+ there are. This function currently ignores the name and the number
+ of entries just reading until it fails.
+
+ The other small piece of trickery here is that GIMP gradients define
+ a left position, right position and middle position. SVG gradients
+ have no middle position in them. In order to handle this case the
+ left and right colors are averaged in a linear manner and the middle
+ position is used for that color.
+
+ That is another point, the GIMP gradients support many different types
+ of gradients -- linear being the most simple. This plugin assumes
+ that they are all linear. Most GIMP gradients are done this way,
+ but it is possible to encounter more complex ones -- which won't be
+ handled correctly.
+
+ The one optimization that this plugin makes that if the right side
+ of the previous segment is the same color as the left side of the
+ current segment, then the second one is dropped. This is often
+ done in GIMP gradients and they are not necissary in SVG.
+
+ What this function does is build up an SVG document with a single
+ linear gradient in it with all the stops of the colors in the GIMP
+ gradient that is passed in. This document is then turned into a
+ document using the \c sp_document_from_mem. That is then returned
+ to Inkscape.
+*/
+SPDocument *
+GimpGrad::open (Inkscape::Extension::Input */*module*/, gchar const *filename)
+{
+ Inkscape::IO::dump_fopen_call(filename, "I");
+ FILE *gradient = Inkscape::IO::fopen_utf8name(filename, "r");
+ if (gradient == nullptr) {
+ return nullptr;
+ }
+
+ {
+ char tempstr[1024];
+ if (fgets(tempstr, 1024, gradient) == nullptr) {
+ // std::cout << "Seems that the read failed" << std::endl;
+ goto error;
+ }
+ if (!streq(tempstr, "GIMP Gradient\n")) {
+ // std::cout << "This doesn't appear to be a GIMP gradient" << std::endl;
+ goto error;
+ }
+
+ /* Name field. */
+ if (fgets(tempstr, 1024, gradient) == nullptr) {
+ // std::cout << "Seems that the second read failed" << std::endl;
+ goto error;
+ }
+ if (!strneq(tempstr, "Name: ", 6)) {
+ goto error;
+ }
+ /* Handle very long names. (And also handle nul bytes gracefully: don't use strlen.) */
+ while (memchr(tempstr, '\n', sizeof(tempstr) - 1) == nullptr) {
+ if (fgets(tempstr, sizeof(tempstr), gradient) == nullptr) {
+ goto error;
+ }
+ }
+
+ /* n. segments */
+ if (fgets(tempstr, 1024, gradient) == nullptr) {
+ // std::cout << "Seems that the third read failed" << std::endl;
+ goto error;
+ }
+ char *endptr = nullptr;
+ long const n_segs = strtol(tempstr, &endptr, 10);
+ if ((*endptr != '\n')
+ || n_segs < 1) {
+ /* SVG gradients are allowed to have zero stops (treated as `none'), but gimp 2.2
+ * requires at least one segment (i.e. at least two stops) (see gimp_gradient_load in
+ * gimpgradient-load.c). We try to use the same error handling as gimp, so that
+ * .ggr files that work in one program work in both programs. */
+ goto error;
+ }
+
+ ColorRGBA prev_color(-1.0, -1.0, -1.0, -1.0);
+ Glib::ustring outsvg("<svg><defs><linearGradient>\n");
+ long n_segs_found = 0;
+ double prev_right = 0.0;
+ while (fgets(tempstr, 1024, gradient) != nullptr) {
+ double dbls[3 + 4 + 4];
+ gchar *p = tempstr;
+ for (double & dbl : dbls) {
+ gchar *end = nullptr;
+ double const xi = g_ascii_strtod(p, &end);
+ if (!end || end == p || !g_ascii_isspace(*end)) {
+ goto error;
+ }
+ if (xi < 0 || 1 < xi) {
+ goto error;
+ }
+ dbl = xi;
+ p = end + 1;
+ }
+
+ double const left = dbls[0];
+ if (left != prev_right) {
+ goto error;
+ }
+ double const middle = dbls[1];
+ if (!(left <= middle)) {
+ goto error;
+ }
+ double const right = dbls[2];
+ if (!(middle <= right)) {
+ goto error;
+ }
+
+ ColorRGBA const leftcolor(dbls[3], dbls[4], dbls[5], dbls[6]);
+ ColorRGBA const rightcolor(dbls[7], dbls[8], dbls[9], dbls[10]);
+ g_assert(11 == G_N_ELEMENTS(dbls));
+
+ /* Interpolation enums: curve shape and colour space. */
+ {
+ /* TODO: Currently we silently ignore type & color, assuming linear interpolation in
+ * sRGB space (or whatever the default in SVG is). See gimp/app/core/gimpgradient.c
+ * for how gimp uses these. We could use gimp functions to sample at a few points, and
+ * add some intermediate stops to convert to the linear/sRGB interpolation */
+ int type; /* enum: linear, curved, sine, sphere increasing, sphere decreasing. */
+ int color_interpolation; /* enum: rgb, hsv anticlockwise, hsv clockwise. */
+ if (sscanf(p, "%8d %8d", &type, &color_interpolation) != 2) {
+ continue;
+ }
+ }
+
+ if (prev_color != leftcolor) {
+ outsvg += stop_svg(leftcolor, left);
+ }
+ if (fabs(middle - .5 * (left + right)) > 1e-4) {
+ outsvg += stop_svg(leftcolor.average(rightcolor), middle);
+ }
+ outsvg += stop_svg(rightcolor, right);
+
+ prev_color = rightcolor;
+ prev_right = right;
+ ++n_segs_found;
+ }
+ if (prev_right != 1.0) {
+ goto error;
+ }
+
+ if (n_segs_found != n_segs) {
+ goto error;
+ }
+
+ outsvg += "</linearGradient></defs></svg>";
+
+ // std::cout << "SVG Output: " << outsvg << std::endl;
+
+ fclose(gradient);
+
+ return SPDocument::createNewDocFromMem(outsvg.c_str(), outsvg.length(), TRUE);
+ }
+
+error:
+ fclose(gradient);
+ return nullptr;
+}
+
+#include "clear-n_.h"
+
+void GimpGrad::init ()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("GIMP Gradients") "</name>\n"
+ "<id>org.inkscape.input.gimpgrad</id>\n"
+ "<input>\n"
+ "<extension>.ggr</extension>\n"
+ "<mimetype>application/x-gimp-gradient</mimetype>\n"
+ "<filetypename>" N_("GIMP Gradient (*.ggr)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Gradients used in GIMP") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>\n", new GimpGrad());
+ // clang-format on
+ return;
+}
+
+} } } /* namespace Internal; Extension; Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/gimpgrad.h b/src/extension/internal/gimpgrad.h
new file mode 100644
index 0000000..8daadef
--- /dev/null
+++ b/src/extension/internal/gimpgrad.h
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+// TODO add include guard
+#include <glibmm/ustring.h>
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+
+class Extension;
+
+namespace Internal {
+
+/**
+ * Implementation class of the GIMP gradient plugin.
+ * This mostly just creates a namespace for the GIMP gradient plugin today.
+ */
+class GimpGrad : public Inkscape::Extension::Implementation::Implementation
+{
+public:
+ bool load(Inkscape::Extension::Extension *module) override;
+ void unload(Inkscape::Extension::Extension *module) override;
+ SPDocument *open(Inkscape::Extension::Input *module, gchar const *filename) override;
+
+ static void init();
+};
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/grid.cpp b/src/extension/internal/grid.cpp
new file mode 100644
index 0000000..8c1ae0d
--- /dev/null
+++ b/src/extension/internal/grid.cpp
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ \file grid.cpp
+
+ A plug-in to add a grid creation effect into Inkscape.
+*/
+/*
+ * Copyright (C) 2004-2005 Ted Gould <ted@gould.cx>
+ * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
+ * Abhishek Sharma
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/spinbutton.h>
+
+#include "desktop.h"
+
+#include "document.h"
+#include "layer-manager.h"
+#include "selection.h"
+#include "2geom/geom.h"
+
+#include "svg/path-string.h"
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "util/units.h"
+
+#include "grid.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ \brief A function to allocated anything -- just an example here
+ \param module Unused
+ \return Whether the load was successful
+*/
+bool
+Grid::load (Inkscape::Extension::Extension */*module*/)
+{
+ // std::cout << "Hey, I'm Grid, I'm loading!" << std::endl;
+ return TRUE;
+}
+
+namespace {
+
+Glib::ustring build_lines(Geom::Rect bounding_area,
+ Geom::Point const &offset, Geom::Point const &spacing)
+{
+ Geom::Point point_offset(0.0, 0.0);
+
+ SVG::PathString path_data;
+
+ for ( int axis = Geom::X ; axis <= Geom::Y ; ++axis ) {
+ point_offset[axis] = offset[axis];
+
+ for (Geom::Point start_point = bounding_area.min();
+ start_point[axis] + offset[axis] <= (bounding_area.max())[axis];
+ start_point[axis] += spacing[axis]) {
+ Geom::Point end_point = start_point;
+ end_point[1-axis] = (bounding_area.max())[1-axis];
+
+ path_data.moveTo(start_point + point_offset)
+ .lineTo(end_point + point_offset);
+ }
+ }
+ // std::cout << "Path data:" << path_data.c_str() << std::endl;
+ return path_data;
+}
+
+} // namespace
+
+/**
+ \brief This actually draws the grid.
+ \param module The effect that was called (unused)
+ \param document What should be edited.
+*/
+void
+Grid::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *view, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ auto desktop = dynamic_cast<SPDesktop *>(view);
+ Inkscape::Selection *selection = desktop->selection;
+ SPDocument *doc = desktop->doc();
+
+ Geom::Rect bounding_area = Geom::Rect(Geom::Point(0,0), Geom::Point(100,100));
+ if (selection->isEmpty()) {
+ /* get page size */
+ if (auto bounds = doc->preferredBounds()) {
+ bounding_area = *bounds;
+ }
+ } else {
+ if (auto bounds = selection->visualBounds()) {
+ bounding_area = *bounds;
+ }
+ Geom::Rect temprec = bounding_area * desktop->doc2dt();
+ bounding_area = temprec;
+ }
+
+ double scale = doc->getDocumentScale().inverse()[Geom::X];
+
+ bounding_area *= Geom::Scale(scale);
+ Geom::Point spacings( scale * module->get_param_float("xspacing"),
+ scale * module->get_param_float("yspacing") );
+ gdouble line_width = scale * module->get_param_float("lineWidth");
+ Geom::Point offsets( scale * module->get_param_float("xoffset"),
+ scale * module->get_param_float("yoffset") );
+
+ Glib::ustring path_data("");
+
+ path_data = build_lines(bounding_area, offsets, spacings);
+ Inkscape::XML::Document * xml_doc = doc->getReprDoc();
+
+ //XML Tree being used directly here while it shouldn't be.
+ Inkscape::XML::Node * current_layer = desktop->layerManager().currentLayer()->getRepr();
+ Inkscape::XML::Node * path = xml_doc->createElement("svg:path");
+
+ path->setAttribute("d", path_data);
+
+ std::ostringstream stringstream;
+ stringstream << "fill:none;stroke:#000000;stroke-width:" << line_width << "px";
+ path->setAttribute("style", stringstream.str());
+
+ current_layer->appendChild(path);
+ Inkscape::GC::release(path);
+}
+
+/** \brief A class to make an adjustment that uses Extension params */
+class PrefAdjustment : public Gtk::Adjustment {
+ /** Extension that this relates to */
+ Inkscape::Extension::Extension * _ext;
+ /** The string which represents the parameter */
+ char * _pref;
+public:
+ /** \brief Make the adjustment using an extension and the string
+ describing the parameter. */
+ PrefAdjustment(Inkscape::Extension::Extension * ext, char * pref) :
+ Gtk::Adjustment(0.0, 0.0, 10.0, 0.1), _ext(ext), _pref(pref) {
+ this->set_value(_ext->get_param_float(_pref));
+ this->signal_value_changed().connect(sigc::mem_fun(this, &PrefAdjustment::val_changed));
+ return;
+ };
+
+ void val_changed ();
+}; /* class PrefAdjustment */
+
+/** \brief A function to respond to the value_changed signal from the
+ adjustment.
+
+ This function just grabs the value from the adjustment and writes
+ it to the parameter. Very simple, but yet beautiful.
+*/
+void
+PrefAdjustment::val_changed ()
+{
+ // std::cout << "Value Changed to: " << this->get_value() << std::endl;
+ _ext->set_param_float(_pref, this->get_value());
+ return;
+}
+
+/** \brief A function to get the preferences for the grid
+ \param module Module which holds the params
+ \param view Unused today - may get style information in the future.
+
+ Uses AutoGUI for creating the GUI.
+*/
+Gtk::Widget *
+Grid::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ SPDocument * current_document = view->doc();
+
+ auto selected = ((SPDesktop *) view)->getSelection()->items();
+ Inkscape::XML::Node * first_select = nullptr;
+ if (!selected.empty()) {
+ first_select = selected.front()->getRepr();
+ }
+
+ return module->autogui(current_document, first_select, changeSignal);
+}
+
+#include "clear-n_.h"
+
+void
+Grid::init ()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Grid") "</name>\n"
+ "<id>org.inkscape.effect.grid</id>\n"
+ "<param name=\"lineWidth\" gui-text=\"" N_("Line Width:") "\" type=\"float\">1.0</param>\n"
+ "<param name=\"xspacing\" gui-text=\"" N_("Horizontal Spacing:") "\" type=\"float\" min=\"0.1\" max=\"1000\">10.0</param>\n"
+ "<param name=\"yspacing\" gui-text=\"" N_("Vertical Spacing:") "\" type=\"float\" min=\"0.1\" max=\"1000\">10.0</param>\n"
+ "<param name=\"xoffset\" gui-text=\"" N_("Horizontal Offset:") "\" type=\"float\" min=\"0.0\" max=\"1000\">0.0</param>\n"
+ "<param name=\"yoffset\" gui-text=\"" N_("Vertical Offset:") "\" type=\"float\" min=\"0.0\" max=\"1000\">0.0</param>\n"
+ "<effect>\n"
+ "<object-type>all</object-type>\n"
+ "<effects-menu>\n"
+ "<submenu name=\"" N_("Render") "\">\n"
+ "<submenu name=\"" N_("Grids") "\" />\n"
+ "</submenu>\n"
+ "</effects-menu>\n"
+ "<menu-tip>" N_("Draw a path which is a grid") "</menu-tip>\n"
+ "</effect>\n"
+ "</inkscape-extension>\n", new Grid());
+ // clang-format on
+ return;
+}
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/grid.h b/src/extension/internal/grid.h
new file mode 100644
index 0000000..16f4cd7
--- /dev/null
+++ b/src/extension/internal/grid.h
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+
+class Effect;
+class Extension;
+
+namespace Internal {
+
+/** \brief Implementation class of the GIMP gradient plugin. This mostly
+ just creates a namespace for the GIMP gradient plugin today.
+*/
+class Grid : public Inkscape::Extension::Implementation::Implementation {
+
+public:
+ bool load(Inkscape::Extension::Extension *module) override;
+ void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+ Gtk::Widget * prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+
+ static void init ();
+};
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/image-resolution.cpp b/src/extension/internal/image-resolution.cpp
new file mode 100644
index 0000000..3ca596c
--- /dev/null
+++ b/src/extension/internal/image-resolution.cpp
@@ -0,0 +1,447 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Daniel Wagenaar <daw@caltech.edu>
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include "util/units.h"
+#include "image-resolution.h"
+
+#define IR_TRY_PNG 1
+#include <png.h>
+
+#ifdef HAVE_EXIF
+#include <math.h>
+#include <libexif/exif-data.h>
+#endif
+
+#define IR_TRY_EXIV 0
+
+#ifdef HAVE_JPEG
+#define IR_TRY_JFIF 1
+#include <jpeglib.h>
+#include <csetjmp>
+#endif
+
+#ifdef WITH_MAGICK
+#include <Magick++.h>
+#endif
+
+#define noIMAGE_RESOLUTION_DEBUG
+
+#ifdef IMAGE_RESOLUTION_DEBUG
+# define debug(f, a...) { g_print("%s(%d) %s:", \
+ __FILE__,__LINE__,__FUNCTION__); \
+ g_print(f, ## a); \
+ g_print("\n"); \
+ }
+#else
+# define debug(f, a...) /* */
+#endif
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+ImageResolution::ImageResolution(char const *fn) {
+ ok_ = false;
+
+ readpng(fn);
+ if (!ok_) {
+ readexiv(fn);
+ }
+ if (!ok_) {
+ readjfif(fn);
+ }
+ if (!ok_) {
+ readexif(fn);
+ }
+ if (!ok_) {
+ readmagick(fn);
+ }
+}
+
+bool ImageResolution::ok() const {
+ return ok_;
+}
+
+double ImageResolution::x() const {
+ return x_;
+}
+
+double ImageResolution::y() const {
+ return y_;
+}
+
+
+
+#if IR_TRY_PNG
+
+static bool haspngheader(FILE *fp) {
+ unsigned char header[8];
+ if ( fread(header, 1, 8, fp) != 8 ) {
+ return false;
+ }
+
+ fseek(fp, 0, SEEK_SET);
+
+ if (png_sig_cmp(header, 0, 8)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Implementation using libpng
+void ImageResolution::readpng(char const *fn) {
+ FILE *fp = fopen(fn, "rb");
+ if (!fp)
+ return;
+
+ if (!haspngheader(fp)) {
+ fclose(fp);
+ return;
+ }
+
+ png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (!png_ptr)
+ return;
+
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if (!info_ptr) {
+ png_destroy_read_struct(&png_ptr, nullptr, nullptr);
+ return;
+ }
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+ fclose(fp);
+ return;
+ }
+
+ png_init_io(png_ptr, fp);
+ png_read_info(png_ptr, info_ptr);
+
+ png_uint_32 res_x, res_y;
+#ifdef PNG_INCH_CONVERSIONS_SUPPORTED
+ debug("PNG_INCH_CONVERSIONS_SUPPORTED");
+ res_x = png_get_x_pixels_per_inch(png_ptr, info_ptr);
+ res_y = png_get_y_pixels_per_inch(png_ptr, info_ptr);
+ if (res_x != 0 && res_y != 0) {
+ ok_ = true;
+ x_ = res_x * 1.0; // FIXME: implicit conversion of png_uint_32 to double ok?
+ y_ = res_y * 1.0; // FIXME: implicit conversion of png_uint_32 to double ok?
+ }
+#else
+ debug("PNG_RESOLUTION_METER");
+ int unit_type;
+ // FIXME: png_get_pHYs() fails to return expected values
+ // with clang (based on LLVM 3.2svn) from Xcode 4.6.3 (OS X 10.7.5)
+ png_get_pHYs(png_ptr, info_ptr, &res_x, &res_y, &unit_type);
+
+ if (unit_type == PNG_RESOLUTION_METER) {
+ ok_ = true;
+ x_ = res_x * 2.54 / 100;
+ y_ = res_y * 2.54 / 100;
+ }
+#endif
+
+ png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
+ fclose(fp);
+
+ if (ok_) {
+ debug("xdpi: %f", x_);
+ debug("ydpi: %f", y_);
+ } else {
+ debug("FAILED");
+ }
+}
+#else
+
+// Dummy implementation
+void ImageResolution::readpng(char const *) {
+}
+
+#endif
+
+#if IR_TRY_EXIF
+
+static double exifDouble(ExifEntry *entry, ExifByteOrder byte_order) {
+ switch (entry->format) {
+ case EXIF_FORMAT_BYTE: {
+ return double(entry->data[0]);
+ }
+ case EXIF_FORMAT_SHORT: {
+ return double(exif_get_short(entry->data, byte_order));
+ }
+ case EXIF_FORMAT_LONG: {
+ return double(exif_get_long(entry->data, byte_order));
+ }
+ case EXIF_FORMAT_RATIONAL: {
+ ExifRational r = exif_get_rational(entry->data, byte_order);
+ return double(r.numerator) / double(r.denominator);
+ }
+ case EXIF_FORMAT_SBYTE: {
+ return double(*(signed char *)entry->data);
+ }
+ case EXIF_FORMAT_SSHORT: {
+ return double(exif_get_sshort(entry->data, byte_order));
+ }
+ case EXIF_FORMAT_SLONG: {
+ return double(exif_get_slong(entry->data, byte_order));
+ }
+ case EXIF_FORMAT_SRATIONAL: {
+ ExifSRational r = exif_get_srational(entry->data, byte_order);
+ return double(r.numerator) / double(r.denominator);
+ }
+ case EXIF_FORMAT_FLOAT: {
+ return double((reinterpret_cast<float *>(entry->data))[0]);
+ }
+ case EXIF_FORMAT_DOUBLE: {
+ return (reinterpret_cast<double *>(entry->data))[0];
+ }
+ default: {
+ return nan(0);
+ }
+ }
+}
+
+// Implementation using libexif
+void ImageResolution::readexif(char const *fn) {
+ ExifData *ed;
+ ed = exif_data_new_from_file(fn);
+ if (!ed)
+ return;
+
+ ExifByteOrder byte_order = exif_data_get_byte_order(ed);
+
+ ExifEntry *xres = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_X_RESOLUTION);
+ ExifEntry *yres = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_Y_RESOLUTION);
+ ExifEntry *unit = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_RESOLUTION_UNIT);
+
+ if ( xres && yres ) {
+ x_ = exifDouble(xres, byte_order);
+ y_ = exifDouble(yres, byte_order);
+ if (unit) {
+ double u = exifDouble(unit, byte_order);
+ if ( u == 3 ) {
+ x_ *= 2.54;
+ y_ *= 2.54;
+ }
+ }
+ ok_ = true;
+ }
+ exif_data_free(ed);
+
+ if (ok_) {
+ debug("xdpi: %f", x_);
+ debug("ydpi: %f", y_);
+ } else {
+ debug("FAILED");
+ }
+}
+
+#else
+
+// Dummy implementation
+void ImageResolution::readexif(char const *) {
+}
+
+#endif
+
+#if IR_TRY_EXIV
+
+void ImageResolution::readexiv(char const *fn) {
+ Exiv2::Image::AutoPtr image = Exiv2::ImageFactory::open(fn);
+ if (!image.get())
+ return;
+
+ image->readMetadata();
+ Exiv2::ExifData &exifData = image->exifData();
+ if (exifData.empty())
+ return;
+
+ Exiv2::ExifData::const_iterator end = exifData.end();
+ bool havex = false;
+ bool havey = false;
+ bool haveunit = false;
+ int unit;
+ for (Exiv2::ExifData::const_iterator i = exifData.begin(); i != end; ++i) {
+ if (ok_)
+ break;
+ if ( i->tag() == 0x011a ) {
+ // X Resolution
+ x_ = i->toFloat();
+ havex = true;
+ } else if ( i->tag() == 0x011b ) {
+ // Y Resolution
+ y_ = i->toFloat();
+ havey = true;
+ } else if ( i->tag() == 0x0128 ) {
+ unit = i->toLong();
+ }
+ ok_ = havex && havey && haveunit;
+ }
+ if (haveunit) {
+ if ( unit == 3 ) {
+ x_ *= 2.54;
+ y_ *= 2.54;
+ }
+ }
+ ok_ = havex && havey;
+
+ if (ok_) {
+ debug("xdpi: %f", x_);
+ debug("ydpi: %f", y_);
+ } else {
+ debug("FAILED");
+ }
+}
+
+#else
+
+// Dummy implementation
+void ImageResolution::readexiv(char const *) {
+}
+
+#endif
+
+#if IR_TRY_JFIF
+
+static void irjfif_error_exit(j_common_ptr cinfo) {
+ longjmp(*(jmp_buf*)cinfo->client_data, 1);
+}
+
+static void irjfif_emit_message(j_common_ptr, int) {
+}
+
+static void irjfif_output_message(j_common_ptr) {
+}
+
+static void irjfif_format_message(j_common_ptr, char *) {
+}
+
+static void irjfif_reset(j_common_ptr) {
+}
+
+void ImageResolution::readjfif(char const *fn) {
+ FILE *ifd = fopen(fn, "rb");
+ if (!ifd) {
+ return;
+ }
+
+ struct jpeg_decompress_struct cinfo;
+ jmp_buf jbuf;
+ struct jpeg_error_mgr jerr;
+
+ if (setjmp(jbuf)) {
+ fclose(ifd);
+ jpeg_destroy_decompress(&cinfo);
+ return;
+ }
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jerr.error_exit = &irjfif_error_exit;
+ jerr.emit_message = &irjfif_emit_message;
+ jerr.output_message = &irjfif_output_message;
+ jerr.format_message = &irjfif_format_message;
+ jerr.reset_error_mgr = &irjfif_reset;
+ cinfo.client_data = (void*)&jbuf;
+
+ jpeg_stdio_src(&cinfo, ifd);
+ jpeg_read_header(&cinfo, TRUE);
+
+ debug("cinfo.[XY]_density");
+ if (cinfo.saw_JFIF_marker) { // JFIF APP0 marker was seen
+ if ( cinfo.density_unit == 1 ) { // dots/inch
+ x_ = cinfo.X_density;
+ y_ = cinfo.Y_density;
+ ok_ = true;
+ } else if ( cinfo.density_unit == 2 ) { // dots/cm
+ x_ = cinfo.X_density * 2.54;
+ y_ = cinfo.Y_density * 2.54;
+ ok_ = true;
+ }
+ /* According to http://www.jpeg.org/public/jfif.pdf (page 7):
+ * "Xdensity and Ydensity should always be non-zero".
+ * but in some cases, they are (see LP bug #1275443) */
+ if (x_ == 0 or y_ == 0) {
+ ok_ = false;
+ }
+ }
+ jpeg_destroy_decompress(&cinfo);
+ fclose(ifd);
+
+ if (ok_) {
+ debug("xdpi: %f", x_);
+ debug("ydpi: %f", y_);
+ } else {
+ debug("FAILED");
+ }
+}
+
+#else
+
+// Dummy implementation
+void ImageResolution::readjfif(char const *) {
+}
+
+#endif
+
+#ifdef WITH_MAGICK
+void ImageResolution::readmagick(char const *fn) {
+ Magick::Image image;
+ debug("Trying image.read");
+ try {
+ image.read(fn);
+ } catch (Magick::Error & err) {
+ debug("ImageMagick error: %s", err.what());
+ return;
+ } catch (std::exception & err) {
+ debug("ImageResolution::readmagick: %s", err.what());
+ return;
+ }
+ debug("image.[xy]Resolution");
+ std::string const type = image.magick();
+ x_ = image.xResolution();
+ y_ = image.yResolution();
+
+// TODO: find out why the hell the following conversion is necessary
+ if (type == "BMP") {
+ x_ = Inkscape::Util::Quantity::convert(x_, "in", "cm");
+ y_ = Inkscape::Util::Quantity::convert(y_, "in", "cm");
+ }
+
+ if (x_ != 0 && y_ != 0) {
+ ok_ = true;
+ }
+
+ if (ok_) {
+ debug("xdpi: %f", x_);
+ debug("ydpi: %f", y_);
+ } else {
+ debug("FAILED");
+ debug("Using default Inkscape import resolution");
+ }
+}
+
+#else
+
+// Dummy implementation
+void ImageResolution::readmagick(char const *) {
+}
+
+#endif /* WITH_MAGICK */
+
+}
+}
+}
diff --git a/src/extension/internal/image-resolution.h b/src/extension/internal/image-resolution.h
new file mode 100644
index 0000000..ad69df0
--- /dev/null
+++ b/src/extension/internal/image-resolution.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Daniel Wagenaar <daw@caltech.edu>
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#ifndef IMAGE_RESOLUTION_H
+
+#define IMAGE_RESOLUTION_H
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class ImageResolution {
+public:
+ ImageResolution(char const *fn);
+ bool ok() const;
+ double x() const;
+ double y() const;
+private:
+ bool ok_;
+ double x_;
+ double y_;
+private:
+ void readpng(char const *fn);
+ void readexif(char const *fn);
+ void readexiv(char const *fn);
+ void readjfif(char const *fn);
+ void readmagick(char const *fn);
+};
+
+}
+}
+}
+
+#endif
diff --git a/src/extension/internal/latex-pstricks-out.cpp b/src/extension/internal/latex-pstricks-out.cpp
new file mode 100644
index 0000000..824f382
--- /dev/null
+++ b/src/extension/internal/latex-pstricks-out.cpp
@@ -0,0 +1,118 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Michael Forbes <miforbes@mbhs.edu>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "latex-pstricks-out.h"
+#include <print.h>
+#include "extension/system.h"
+#include "extension/print.h"
+#include "extension/db.h"
+#include "display/drawing.h"
+#include "object/sp-root.h"
+
+
+#include "document.h"
+
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+LatexOutput::LatexOutput () // The null constructor
+{
+ return;
+}
+
+LatexOutput::~LatexOutput () //The destructor
+{
+ return;
+}
+
+bool LatexOutput::check(Inkscape::Extension::Extension * /*module*/)
+{
+ bool result = Inkscape::Extension::db.get("org.inkscape.print.latex") != nullptr;
+ return result;
+}
+
+
+void LatexOutput::save(Inkscape::Extension::Output * /*mod2*/, SPDocument *doc, gchar const *filename)
+{
+ SPPrintContext context;
+ doc->ensureUpToDate();
+
+ Inkscape::Extension::Print *mod = Inkscape::Extension::get_print(SP_MODULE_KEY_PRINT_LATEX);
+ const gchar * oldconst = mod->get_param_string("destination");
+ gchar * oldoutput = g_strdup(oldconst);
+ mod->set_param_string("destination", filename);
+
+ // Start
+ context.module = mod;
+ // fixme: This has to go into module constructor somehow
+ mod->base = doc->getRoot();
+ Inkscape::Drawing drawing;
+ mod->dkey = SPItem::display_key_new(1);
+ mod->root = (mod->base)->invoke_show(drawing, mod->dkey, SP_ITEM_SHOW_DISPLAY);
+ drawing.setRoot(mod->root);
+ // Print document
+ mod->begin(doc);
+ (mod->base)->invoke_print(&context);
+ mod->finish();
+ // Release things
+ (mod->base)->invoke_hide(mod->dkey);
+ mod->base = nullptr;
+ mod->root = nullptr; // should have been deleted by invoke_hide
+ // end
+
+ mod->set_param_string("destination", oldoutput);
+ g_free(oldoutput);
+}
+
+#include "clear-n_.h"
+
+/**
+ \brief A function allocate a copy of this function.
+
+ This is the definition of postscript out. This function just
+ calls the extension system with the memory allocated XML that
+ describes the data.
+*/
+void
+LatexOutput::init ()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("LaTeX Output") "</name>\n"
+ "<id>org.inkscape.output.latex</id>\n"
+ "<output>\n"
+ "<extension>.tex</extension>\n"
+ "<mimetype>text/x-tex</mimetype>\n"
+ "<filetypename>" N_("LaTeX With PSTricks macros (*.tex)") "</filetypename>\n"
+ "<filetypetooltip>" N_("LaTeX PSTricks File") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>", new LatexOutput());
+ // clang-format on
+
+ return;
+}
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+/*
+ Local Variables:
+ mode:cpp
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/latex-pstricks-out.h b/src/extension/internal/latex-pstricks-out.h
new file mode 100644
index 0000000..670904b
--- /dev/null
+++ b/src/extension/internal/latex-pstricks-out.h
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * Authors:
+ * Michael Forbes <miforbes-inkscape@mbhs.edu>
+ *
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef EXTENSION_INTERNAL_LATEX_OUT_H
+#define EXTENSION_INTERNAL_LATEX_OUT_H
+
+#include "extension/implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class LatexOutput : Inkscape::Extension::Implementation::Implementation { //This is a derived class
+
+public:
+ LatexOutput(); // Empty constructor
+
+ ~LatexOutput() override;//Destructor
+
+ bool check(Inkscape::Extension::Extension *module) override; //Can this module load (always yes for now)
+
+ void save(Inkscape::Extension::Output *mod, // Save the given document to the given filename
+ SPDocument *doc,
+ gchar const *filename) override;
+
+ static void init();//Initialize the class
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* EXTENSION_INTERNAL_LATEX_OUT_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/latex-pstricks.cpp b/src/extension/internal/latex-pstricks.cpp
new file mode 100644
index 0000000..542a4bf
--- /dev/null
+++ b/src/extension/internal/latex-pstricks.cpp
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LaTeX Printing
+ *
+ * Author:
+ * Michael Forbes <miforbes@mbhs.edu>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/pathvector.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/curves.h>
+#include <cerrno>
+#include <csignal>
+#include "util/units.h"
+#include "helper/geom-curves.h"
+
+#include "extension/print.h"
+#include "extension/system.h"
+#include "inkscape-version.h"
+#include "io/sys.h"
+#include "latex-pstricks.h"
+#include "style.h"
+#include "document.h"
+#include <cstring>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+PrintLatex::PrintLatex ():
+ _width(0),
+ _height(0),
+ _stream(nullptr)
+{
+}
+
+PrintLatex::~PrintLatex ()
+{
+ if (_stream) fclose(_stream);
+
+ /* restore default signal handling for SIGPIPE */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_DFL);
+#endif
+ return;
+}
+
+unsigned int PrintLatex::setup(Inkscape::Extension::Print * /*mod*/)
+{
+ return TRUE;
+}
+
+unsigned int PrintLatex::begin (Inkscape::Extension::Print *mod, SPDocument *doc)
+{
+ Inkscape::SVGOStringStream os;
+ int res;
+ FILE *osf = nullptr;
+ const gchar * fn = nullptr;
+ gsize bytesRead = 0;
+ gsize bytesWritten = 0;
+ GError* error = nullptr;
+
+ os.setf(std::ios::fixed);
+ fn = mod->get_param_string("destination");
+ gchar* local_fn = g_filename_from_utf8( fn,
+ -1, &bytesRead, &bytesWritten, &error);
+ fn = local_fn;
+
+ /* TODO: Replace the below fprintf's with something that does the right thing whether in
+ * gui or batch mode (e.g. --print=blah). Consider throwing an exception: currently one of
+ * the callers (sp_print_document_to_file, "ret = mod->begin(doc)") wrongly ignores the
+ * return code.
+ */
+ if (fn != nullptr) {
+ while (isspace(*fn)) fn += 1;
+ Inkscape::IO::dump_fopen_call(fn, "K");
+ osf = Inkscape::IO::fopen_utf8name(fn, "w+");
+ if (!osf) {
+ fprintf(stderr, "inkscape: fopen(%s): %s\n", fn, strerror(errno));
+ g_free(local_fn);
+ return 0;
+ }
+ _stream = osf;
+ }
+
+ g_free(local_fn);
+
+ /* fixme: this is kinda icky */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_IGN);
+#endif
+
+ res = fprintf(_stream, "%%LaTeX with PSTricks extensions\n");
+ /* flush this to test output stream as early as possible */
+ if (fflush(_stream)) {
+ /*g_print("caught error in sp_module_print_plain_begin\n");*/
+ if (ferror(_stream)) {
+ g_print("Error %d on output stream: %s\n", errno,
+ g_strerror(errno));
+ }
+ g_print("Printing failed\n");
+ /* fixme: should use pclose() for pipes */
+ fclose(_stream);
+ _stream = nullptr;
+ fflush(stdout);
+ return 0;
+ }
+
+ // width and height in pt
+ _width = doc->getWidth().value("pt");
+ _height = doc->getHeight().value("pt");
+
+ if (res >= 0) {
+
+ os << "%%Creator: Inkscape " << Inkscape::version_string << "\n";
+ os << "%%Please note this file requires PSTricks extensions\n";
+
+ os << "\\psset{xunit=.5pt,yunit=.5pt,runit=.5pt}\n";
+ // from now on we can output px, but they will be treated as pt
+
+ os << "\\begin{pspicture}(" << doc->getWidth().value("px") << "," << doc->getHeight().value("px") << ")\n";
+ }
+
+ m_tr_stack.push( Geom::Scale(1, -1) * Geom::Translate(0, doc->getHeight().value("px"))); /// @fixme hardcoded doc2dt transform
+
+ return fprintf(_stream, "%s", os.str().c_str());
+}
+
+unsigned int PrintLatex::finish(Inkscape::Extension::Print * /*mod*/)
+{
+ if (_stream) {
+ fprintf(_stream, "\\end{pspicture}\n");
+
+ // Flush stream to be sure.
+ fflush(_stream);
+
+ fclose(_stream);
+ _stream = nullptr;
+ }
+ return 0;
+}
+
+unsigned int PrintLatex::bind(Inkscape::Extension::Print * /*mod*/, Geom::Affine const &transform, float /*opacity*/)
+{
+ if (!m_tr_stack.empty()) {
+ Geom::Affine tr_top = m_tr_stack.top();
+ m_tr_stack.push(transform * tr_top);
+ } else {
+ m_tr_stack.push(transform);
+ }
+
+ return 1;
+}
+
+unsigned int PrintLatex::release(Inkscape::Extension::Print * /*mod*/)
+{
+ m_tr_stack.pop();
+ return 1;
+}
+
+unsigned int PrintLatex::fill(Inkscape::Extension::Print * /*mod*/,
+ Geom::PathVector const &pathv, Geom::Affine const &transform, SPStyle const *style,
+ Geom::OptRect const & /*pbox*/, Geom::OptRect const & /*dbox*/, Geom::OptRect const & /*bbox*/)
+{
+ if (!_stream) {
+ return 0; // XXX: fixme, returning -1 as unsigned.
+ }
+
+ if (style->fill.isColor()) {
+ Inkscape::SVGOStringStream os;
+ float rgb[3];
+ float fill_opacity;
+
+ os.setf(std::ios::fixed);
+
+ fill_opacity=SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+ style->fill.value.color.get_rgb_floatv(rgb);
+ os << "{\n\\newrgbcolor{curcolor}{" << rgb[0] << " " << rgb[1] << " " << rgb[2] << "}\n";
+ os << "\\pscustom[linestyle=none,fillstyle=solid,fillcolor=curcolor";
+ if (fill_opacity!=1.0) {
+ os << ",opacity="<<fill_opacity;
+ }
+
+ os << "]\n{\n";
+
+ print_pathvector(os, pathv, transform);
+
+ os << "}\n}\n";
+
+ fprintf(_stream, "%s", os.str().c_str());
+ }
+
+ return 0;
+}
+
+unsigned int PrintLatex::stroke(Inkscape::Extension::Print * /*mod*/,
+ Geom::PathVector const &pathv, Geom::Affine const &transform, SPStyle const *style,
+ Geom::OptRect const & /*pbox*/, Geom::OptRect const & /*dbox*/, Geom::OptRect const & /*bbox*/)
+{
+ if (!_stream) {
+ return 0; // XXX: fixme, returning -1 as unsigned.
+ }
+
+ if (style->stroke.isColor()) {
+ Inkscape::SVGOStringStream os;
+ float rgb[3];
+ float stroke_opacity;
+ Geom::Affine tr_stack = m_tr_stack.top();
+ double const scale = tr_stack.descrim();
+ os.setf(std::ios::fixed);
+
+ stroke_opacity=SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
+ style->stroke.value.color.get_rgb_floatv(rgb);
+ os << "{\n\\newrgbcolor{curcolor}{" << rgb[0] << " " << rgb[1] << " " << rgb[2] << "}\n";
+
+ os << "\\pscustom[linewidth=" << style->stroke_width.computed*scale<< ",linecolor=curcolor";
+
+ if (stroke_opacity!=1.0) {
+ os<<",strokeopacity="<<stroke_opacity;
+ }
+
+ if (style->stroke_dasharray.set && !style->stroke_dasharray.values.empty()) {
+ os << ",linestyle=dashed,dash=";
+ for (unsigned i = 0; i < style->stroke_dasharray.values.size(); i++) {
+ if ((i)) {
+ os << " ";
+ }
+ os << style->stroke_dasharray.values[i].value;
+ }
+ }
+
+ os <<"]\n{\n";
+
+ print_pathvector(os, pathv, transform);
+
+ os << "}\n}\n";
+
+ fprintf(_stream, "%s", os.str().c_str());
+ }
+
+ return 0;
+}
+
+// FIXME: why is 'transform' argument not used?
+void
+PrintLatex::print_pathvector(SVGOStringStream &os, Geom::PathVector const &pathv_in, const Geom::Affine & /*transform*/)
+{
+ if (pathv_in.empty())
+ return;
+
+// Geom::Affine tf=transform; // why was this here?
+ Geom::Affine tf_stack=m_tr_stack.top(); // and why is transform argument not used?
+ Geom::PathVector pathv = pathv_in * tf_stack; // generates new path, which is a bit slow, but this doesn't have to be performance optimized
+
+ os << "\\newpath\n";
+
+ for(const auto & it : pathv) {
+
+ os << "\\moveto(" << it.initialPoint()[Geom::X] << "," << it.initialPoint()[Geom::Y] << ")\n";
+
+ for(Geom::Path::const_iterator cit = it.begin(); cit != it.end_open(); ++cit) {
+ print_2geomcurve(os, *cit);
+ }
+
+ if (it.closed()) {
+ os << "\\closepath\n";
+ }
+
+ }
+}
+
+void
+PrintLatex::print_2geomcurve(SVGOStringStream &os, Geom::Curve const &c)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ if( is_straight_curve(c) )
+ {
+ os << "\\lineto(" << c.finalPoint()[X] << "," << c.finalPoint()[Y] << ")\n";
+ }
+ else if(Geom::CubicBezier const *cubic_bezier = dynamic_cast<Geom::CubicBezier const*>(&c)) {
+ std::vector<Geom::Point> points = cubic_bezier->controlPoints();
+ os << "\\curveto(" << points[1][X] << "," << points[1][Y] << ")("
+ << points[2][X] << "," << points[2][Y] << ")("
+ << points[3][X] << "," << points[3][Y] << ")\n";
+ }
+ else {
+ //this case handles sbasis as well as all other curve types
+ Geom::Path sbasis_path = Geom::cubicbezierpath_from_sbasis(c.toSBasis(), 0.1);
+
+ for(const auto & iter : sbasis_path) {
+ print_2geomcurve(os, iter);
+ }
+ }
+}
+
+bool
+PrintLatex::textToPath(Inkscape::Extension::Print * ext)
+{
+ return ext->get_param_bool("textToPath");
+}
+
+#include "clear-n_.h"
+
+void PrintLatex::init()
+{
+ /* SVG in */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("LaTeX Print") "</name>\n"
+ "<id>" SP_MODULE_KEY_PRINT_LATEX "</id>\n"
+ "<param gui-hidden=\"true\" name=\"destination\" type=\"string\"></param>\n"
+ "<param gui-hidden=\"true\" name=\"textToPath\" type=\"bool\">true</param>\n"
+ "<print/>\n"
+ "</inkscape-extension>", new PrintLatex());
+ // clang-format on
+}
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
+
diff --git a/src/extension/internal/latex-pstricks.h b/src/extension/internal/latex-pstricks.h
new file mode 100644
index 0000000..edb1906
--- /dev/null
+++ b/src/extension/internal/latex-pstricks.h
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef __INKSCAPE_EXTENSION_INTERNAL_PRINT_LATEX_H__
+#define __INKSCAPE_EXTENSION_INTERNAL_PRINT_LATEX_H__
+
+/*
+ * LaTeX Printing
+ *
+ * Author:
+ * Michael Forbes <miforbes@mbhs.edu>
+ *
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <stack>
+
+#include "extension/implementation/implementation.h"
+#include "extension/extension.h"
+
+#include "svg/stringstream.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class PrintLatex : public Inkscape::Extension::Implementation::Implementation {
+
+ float _width;
+ float _height;
+ FILE * _stream;
+
+ std::stack<Geom::Affine> m_tr_stack;
+
+ void print_pathvector(SVGOStringStream &os, Geom::PathVector const &pathv_in, const Geom::Affine & /*transform*/);
+ void print_2geomcurve(SVGOStringStream &os, Geom::Curve const & c );
+
+public:
+ PrintLatex ();
+ ~PrintLatex () override;
+
+ /* Print functions */
+ unsigned int setup (Inkscape::Extension::Print * module) override;
+
+ unsigned int begin (Inkscape::Extension::Print * module, SPDocument *doc) override;
+ unsigned int finish (Inkscape::Extension::Print * module) override;
+
+ /* Rendering methods */
+ unsigned int bind(Inkscape::Extension::Print *module, Geom::Affine const &transform, float opacity) override;
+ unsigned int release(Inkscape::Extension::Print *module) override;
+
+ unsigned int fill (Inkscape::Extension::Print *module, Geom::PathVector const &pathv,
+ Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox) override;
+ unsigned int stroke (Inkscape::Extension::Print *module, Geom::PathVector const &pathv,
+ Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox) override;
+ bool textToPath (Inkscape::Extension::Print * ext) override;
+
+ static void init ();
+};
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_LATEX */
+
+/*
+ Local Variables:
+ mode:cpp
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/latex-text-renderer.cpp b/src/extension/internal/latex-text-renderer.cpp
new file mode 100644
index 0000000..12fa66f
--- /dev/null
+++ b/src/extension/internal/latex-text-renderer.cpp
@@ -0,0 +1,755 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Rendering LaTeX file (pdf/eps/ps+latex output)
+ *
+ * The idea stems from GNUPlot's epslatex terminal output :-)
+ */
+/*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ * Miklos Erdelyi <erdelyim@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006-2011 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "latex-text-renderer.h"
+
+#include <csignal>
+#include <cerrno>
+
+#include <glibmm/i18n.h>
+#include <glibmm/regex.h>
+
+#include "libnrtype/Layout-TNG.h"
+#include <2geom/transforms.h>
+#include <2geom/rect.h>
+
+#include "object/sp-item.h"
+#include "object/sp-item-group.h"
+#include "object/sp-root.h"
+#include "object/sp-use.h"
+#include "object/sp-text.h"
+#include "object/sp-flowtext.h"
+#include "object/sp-rect.h"
+#include "style.h"
+
+#include "text-editing.h"
+
+#include "util/units.h"
+
+#include "extension/output.h"
+#include "extension/system.h"
+
+#include "inkscape-version.h"
+#include "io/sys.h"
+#include "document.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ * This method is called by the PDF, EPS and PS output extensions.
+ * @param filename This should be the filename without '_tex' extension to which the tex code should be written. Output goes to <filename>_tex, note the underscore instead of period.
+ */
+bool
+latex_render_document_text_to_file( SPDocument *doc, gchar const *filename,
+ const gchar * const exportId, bool exportDrawing, bool exportCanvas, double bleedmargin_px,
+ bool pdflatex)
+{
+ doc->ensureUpToDate();
+
+ SPRoot *root = doc->getRoot();
+ SPItem *base = nullptr;
+
+ bool pageBoundingBox = true;
+ if (exportId && strcmp(exportId, "")) {
+ // we want to export the given item only
+ base = dynamic_cast<SPItem *>(doc->getObjectById(exportId));
+ if (!base) {
+ throw Inkscape::Extension::Output::export_id_not_found(exportId);
+ }
+ root->cropToObject(base); // TODO: This is inconsistent in CLI (should only happen for --export-id-only)
+ pageBoundingBox = exportCanvas;
+ }
+ else {
+ // we want to export the entire document from root
+ base = root;
+ pageBoundingBox = !exportDrawing;
+ }
+
+ if (!base)
+ return false;
+
+ /* Create renderer */
+ LaTeXTextRenderer *renderer = new LaTeXTextRenderer(pdflatex);
+
+ bool ret = renderer->setTargetFile(filename);
+ if (ret) {
+ /* Render document */
+ bool ret = renderer->setupDocument(doc, pageBoundingBox, bleedmargin_px, base);
+ if (ret) {
+ renderer->renderItem(root);
+ }
+ }
+
+ delete renderer;
+
+ return ret;
+}
+
+LaTeXTextRenderer::LaTeXTextRenderer(bool pdflatex)
+ : _stream(nullptr),
+ _filename(nullptr),
+ _pdflatex(pdflatex),
+ _omittext_state(EMPTY),
+ _omittext_page(1)
+{
+ push_transform(Geom::identity());
+}
+
+LaTeXTextRenderer::~LaTeXTextRenderer()
+{
+ if (_stream) {
+ writePostamble();
+
+ fclose(_stream);
+ }
+
+ /* restore default signal handling for SIGPIPE */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_DFL);
+#endif
+
+ if (_filename) {
+ g_free(_filename);
+ }
+
+ return;
+}
+
+/** This should create the output LaTeX file, and assign it to _stream.
+ * @return Returns true when successful
+ */
+bool
+LaTeXTextRenderer::setTargetFile(gchar const *filename) {
+ if (filename != nullptr) {
+ while (isspace(*filename)) filename += 1;
+
+ _filename = g_path_get_basename(filename);
+
+ gchar *filename_ext = g_strdup_printf("%s_tex", filename);
+ Inkscape::IO::dump_fopen_call(filename_ext, "K");
+ FILE *osf = Inkscape::IO::fopen_utf8name(filename_ext, "w+");
+ if (!osf) {
+ fprintf(stderr, "inkscape: fopen(%s): %s\n", filename_ext, strerror(errno));
+ g_free(filename_ext);
+ return false;
+ }
+ _stream = osf;
+ g_free(filename_ext);
+ }
+
+ /* fixme: this is kinda icky */
+#if !defined(_WIN32) && !defined(__WIN32__)
+ (void) signal(SIGPIPE, SIG_IGN);
+#endif
+
+ fprintf(_stream, "%%%% Creator: Inkscape %s, www.inkscape.org\n", Inkscape::version_string);
+ fprintf(_stream, "%%%% PDF/EPS/PS + LaTeX output extension by Johan Engelen, 2010\n");
+ fprintf(_stream, "%%%% Accompanies image file '%s' (pdf, eps, ps)\n", _filename);
+ fprintf(_stream, "%%%%\n");
+ /* flush this to test output stream as early as possible */
+ if (fflush(_stream)) {
+ if (ferror(_stream)) {
+ g_print("Error %d on LaTeX file output stream: %s\n", errno,
+ g_strerror(errno));
+ }
+ g_print("Output to LaTeX file failed\n");
+ /* fixme: should use pclose() for pipes */
+ fclose(_stream);
+ _stream = nullptr;
+ fflush(stdout);
+ return false;
+ }
+
+ writePreamble();
+
+ return true;
+}
+
+static char const preamble[] =
+"%% To include the image in your LaTeX document, write\n"
+"%% \\input{<filename>.pdf_tex}\n"
+"%% instead of\n"
+"%% \\includegraphics{<filename>.pdf}\n"
+"%% To scale the image, write\n"
+"%% \\def\\svgwidth{<desired width>}\n"
+"%% \\input{<filename>.pdf_tex}\n"
+"%% instead of\n"
+"%% \\includegraphics[width=<desired width>]{<filename>.pdf}\n"
+"%%\n"
+"%% Images with a different path to the parent latex file can\n"
+"%% be accessed with the `import' package (which may need to be\n"
+"%% installed) using\n"
+"%% \\usepackage{import}\n"
+"%% in the preamble, and then including the image with\n"
+"%% \\import{<path to file>}{<filename>.pdf_tex}\n"
+"%% Alternatively, one can specify\n"
+"%% \\graphicspath{{<path to file>/}}\n"
+"%% \n"
+"%% For more information, please see info/svg-inkscape on CTAN:\n"
+"%% http://tug.ctan.org/tex-archive/info/svg-inkscape\n"
+"%%\n"
+"\\begingroup%\n"
+" \\makeatletter%\n"
+" \\providecommand\\color[2][]{%\n"
+" \\errmessage{(Inkscape) Color is used for the text in Inkscape, but the package \'color.sty\' is not loaded}%\n"
+" \\renewcommand\\color[2][]{}%\n"
+" }%\n"
+" \\providecommand\\transparent[1]{%\n"
+" \\errmessage{(Inkscape) Transparency is used (non-zero) for the text in Inkscape, but the package \'transparent.sty\' is not loaded}%\n"
+" \\renewcommand\\transparent[1]{}%\n"
+" }%\n"
+" \\providecommand\\rotatebox[2]{#2}%\n"
+" \\newcommand*\\fsize{\\dimexpr\\f@size pt\\relax}%\n"
+" \\newcommand*\\lineheight[1]{\\fontsize{\\fsize}{#1\\fsize}\\selectfont}%\n";
+
+static char const postamble[] =
+" \\end{picture}%\n"
+"\\endgroup%\n";
+
+void
+LaTeXTextRenderer::writePreamble()
+{
+ fprintf(_stream, "%s", preamble);
+}
+void
+LaTeXTextRenderer::writePostamble()
+{
+ fprintf(_stream, "%s", postamble);
+}
+
+void LaTeXTextRenderer::sp_group_render(SPGroup *group)
+{
+ std::vector<SPObject*> l = (group->childList(false));
+ for(auto x : l){
+ SPItem *item = dynamic_cast<SPItem*>(x);
+ if (item) {
+ renderItem(item);
+ }
+ }
+}
+
+void LaTeXTextRenderer::sp_use_render(SPUse *use)
+{
+ bool translated = false;
+
+ if ((use->x._set && use->x.computed != 0) || (use->y._set && use->y.computed != 0)) {
+ Geom::Affine tp(Geom::Translate(use->x.computed, use->y.computed));
+ push_transform(tp);
+ translated = true;
+ }
+
+ SPItem *childItem = dynamic_cast<SPItem *>(use->child);
+ if (childItem) {
+ renderItem(childItem);
+ }
+
+ if (translated) {
+ pop_transform();
+ }
+}
+
+void LaTeXTextRenderer::sp_text_render(SPText *textobj)
+{
+ // Nothing to do here... (so don't emit an empty box)
+ // Also avoids falling out of sync with the CairoRenderer (which won't render anything in this case either)
+ if (textobj->layout.getActualLength() == 0)
+ return;
+
+ // Only PDFLaTeX supports importing a single page of a graphics file,
+ // so only PDF backend gets interleaved text/graphics
+ if (_pdflatex && _omittext_state == GRAPHIC_ON_TOP)
+ _omittext_state = NEW_PAGE_ON_GRAPHIC;
+
+ SPStyle *style = textobj->style;
+
+ // get position and alignment
+ // Align vertically on the baseline of the font (retrieved from the anchor point)
+ // Align horizontally on anchorpoint
+ gchar const *alignment = nullptr;
+ gchar const *aligntabular = nullptr;
+ switch (style->text_anchor.computed) {
+ case SP_CSS_TEXT_ANCHOR_START:
+ alignment = "[lt]";
+ aligntabular = "{l}";
+ break;
+ case SP_CSS_TEXT_ANCHOR_END:
+ alignment = "[rt]";
+ aligntabular = "{r}";
+ break;
+ case SP_CSS_TEXT_ANCHOR_MIDDLE:
+ default:
+ alignment = "[t]";
+ aligntabular = "{c}";
+ break;
+ }
+
+ Geom::Point anchor;
+ const auto baseline_anchor_point = textobj->layout.baselineAnchorPoint();
+ if (baseline_anchor_point) {
+ anchor = (*baseline_anchor_point) * transform();
+ } else {
+ g_warning("LaTeXTextRenderer::sp_text_render: baselineAnchorPoint unset, text position will be wrong. Please report the issue.");
+ }
+
+ // determine color and transparency (for now, use rgb color model as it is most native to Inkscape)
+ bool has_color = false; // if the item has no color set, don't force black color
+ bool has_transparency = false;
+ // TODO: how to handle ICC colors?
+ // give priority to fill color
+ guint32 rgba = 0;
+ float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
+ if (style->fill.set && style->fill.isColor()) {
+ has_color = true;
+ rgba = style->fill.value.color.toRGBA32(1.);
+ opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+ } else if (style->stroke.set && style->stroke.isColor()) {
+ has_color = true;
+ rgba = style->stroke.value.color.toRGBA32(1.);
+ opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
+ }
+ if (opacity < 1.0) {
+ has_transparency = true;
+ }
+
+ // get rotation
+ Geom::Affine i2doc = textobj->i2doc_affine();
+ Geom::Affine wotransl = i2doc.withoutTranslation();
+ double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis());
+ bool has_rotation = !Geom::are_near(degrees,0.);
+
+ // get line-height
+ float line_height;
+ if (style->line_height.unit == SP_CSS_UNIT_NONE) {
+ // unitless 'line-height' (use as-is, computed value is relative value)
+ line_height = style->line_height.computed;
+ } else {
+ // 'line-height' with unit (make relative, computed value is absolute value)
+ line_height = style->line_height.computed / style->font_size.computed;
+ }
+
+ // write to LaTeX
+ Inkscape::SVGOStringStream os;
+ os.setf(std::ios::fixed); // don't use scientific notation
+
+ os << " \\put(" << anchor[Geom::X] << "," << anchor[Geom::Y] << "){";
+ if (has_color) {
+ os << "\\color[rgb]{" << SP_RGBA32_R_F(rgba) << "," << SP_RGBA32_G_F(rgba) << "," << SP_RGBA32_B_F(rgba) << "}";
+ }
+ if (_pdflatex && has_transparency) {
+ os << "\\transparent{" << opacity << "}";
+ }
+ if (has_rotation) {
+ os << "\\rotatebox{" << degrees << "}{";
+ }
+ os << "\\makebox(0,0)" << alignment << "{";
+ if (line_height != 1) {
+ os << "\\lineheight{" << line_height << "}";
+ }
+ os << "\\smash{";
+ os << "\\begin{tabular}[t]" << aligntabular;
+
+ // Walk through all spans in the text object.
+ // Write span strings to LaTeX, associated with font weight and style.
+ Inkscape::Text::Layout const &layout = *(te_get_layout (textobj));
+ for (Inkscape::Text::Layout::iterator li = layout.begin(), le = layout.end();
+ li != le; li.nextStartOfSpan())
+ {
+ Inkscape::Text::Layout::iterator ln = li;
+ ln.nextStartOfSpan();
+ Glib::ustring uspanstr = sp_te_get_string_multiline (textobj, li, ln);
+
+ // escape ampersands
+ uspanstr = Glib::Regex::create("&")->replace_literal(uspanstr, 0, "\\&", (Glib::RegexMatchFlags)0);
+ // escape percent
+ uspanstr = Glib::Regex::create("%")->replace_literal(uspanstr, 0, "\\%", (Glib::RegexMatchFlags)0);
+
+ const gchar *spanstr = uspanstr.c_str();
+ if (!spanstr) {
+ continue;
+ }
+
+ bool is_bold = false, is_italic = false, is_oblique = false;
+
+ // newline character only -> don't attempt to add style (will break compilation in LaTeX)
+ if (g_strcmp0(spanstr, "\n")) {
+ SPStyle const &spanstyle = *(sp_te_style_at_position (textobj, li));
+ if (spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_500 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_600 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_700 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_800 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_900 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLD ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLDER)
+ {
+ is_bold = true;
+ os << "\\textbf{";
+ }
+ if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_ITALIC)
+ {
+ is_italic = true;
+ os << "\\textit{";
+ }
+ if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_OBLIQUE)
+ {
+ is_oblique = true;
+ os << "\\textsl{"; // this is an accurate choice if the LaTeX chosen font matches the font in Inkscape. Gives bad results when it is not so...
+ }
+ }
+
+ // replace carriage return with double slash
+ gchar ** splitstr = g_strsplit(spanstr, "\n", 2);
+ os << splitstr[0];
+ if (g_strv_length(splitstr) > 1)
+ {
+ os << "\\\\";
+ }
+ g_strfreev(splitstr);
+
+ if (is_oblique) { os << "}"; } // oblique end
+ if (is_italic) { os << "}"; } // italic end
+ if (is_bold) { os << "}"; } // bold end
+ }
+
+ os << "\\end{tabular}"; // tabular end
+ os << "}"; // smash end
+ if (has_rotation) { os << "}"; } // rotatebox end
+ os << "}"; //makebox end
+ os << "}%\n"; // put end
+
+ fprintf(_stream, "%s", os.str().c_str());
+}
+
+void LaTeXTextRenderer::sp_flowtext_render(SPFlowtext *flowtext)
+{
+/*
+Flowtext is possible by using a minipage! :)
+Flowing in rectangle is possible, not in arb shape.
+*/
+
+ // Only PDFLaTeX supports importing a single page of a graphics file,
+ // so only PDF backend gets interleaved text/graphics
+ if (_pdflatex && _omittext_state == GRAPHIC_ON_TOP)
+ _omittext_state = NEW_PAGE_ON_GRAPHIC;
+
+ SPStyle *style = flowtext->style;
+
+ SPItem *frame_item = flowtext->get_frame(nullptr);
+ SPRect *frame = dynamic_cast<SPRect *>(frame_item);
+ if (!frame_item || !frame) {
+ g_warning("LaTeX export: non-rectangular flowed text shapes are not supported, skipping text.");
+ return; // don't know how to handle non-rect frames yet. is quite uncommon for latex users i think
+ }
+
+ // We will transform the coordinates
+ Geom::Rect framebox = frame->getRect();
+
+ // get position and alignment
+ // Align on topleft corner.
+ gchar const *alignment = "[lt]";
+ gchar const *justification = "";
+ switch (flowtext->layout.paragraphAlignment(flowtext->layout.begin())) {
+ case Inkscape::Text::Layout::LEFT:
+ justification = "\\raggedright ";
+ break;
+ case Inkscape::Text::Layout::RIGHT:
+ justification = "\\raggedleft ";
+ break;
+ case Inkscape::Text::Layout::CENTER:
+ justification = "\\centering ";
+ case Inkscape::Text::Layout::FULL:
+ default:
+ // no need to add LaTeX code for standard justified output :)
+ break;
+ }
+
+ // The topleft Corner was calculated after rotating the text which results in a wrong Coordinate.
+ // Now, the topleft Corner is rotated after calculating it
+ Geom::Point pos(framebox.corner(0) * transform()); //topleft corner
+
+ // determine color and transparency (for now, use rgb color model as it is most native to Inkscape)
+ bool has_color = false; // if the item has no color set, don't force black color
+ bool has_transparency = false;
+ // TODO: how to handle ICC colors?
+ // give priority to fill color
+ guint32 rgba = 0;
+ float opacity = SP_SCALE24_TO_FLOAT(style->opacity.value);
+ if (style->fill.set && style->fill.isColor()) {
+ has_color = true;
+ rgba = style->fill.value.color.toRGBA32(1.);
+ opacity *= SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+ } else if (style->stroke.set && style->stroke.isColor()) {
+ has_color = true;
+ rgba = style->stroke.value.color.toRGBA32(1.);
+ opacity *= SP_SCALE24_TO_FLOAT(style->stroke_opacity.value);
+ }
+ if (opacity < 1.0) {
+ has_transparency = true;
+ }
+
+ // get rotation
+ Geom::Affine i2doc = flowtext->i2doc_affine();
+ Geom::Affine wotransl = i2doc.withoutTranslation();
+ double degrees = -180/M_PI * Geom::atan2(wotransl.xAxis());
+ bool has_rotation = !Geom::are_near(degrees,0.);
+
+ // write to LaTeX
+ Inkscape::SVGOStringStream os;
+ os.setf(std::ios::fixed); // don't use scientific notation
+
+ os << " \\put(" << pos[Geom::X] << "," << pos[Geom::Y] << "){";
+ if (has_color) {
+ os << "\\color[rgb]{" << SP_RGBA32_R_F(rgba) << "," << SP_RGBA32_G_F(rgba) << "," << SP_RGBA32_B_F(rgba) << "}";
+ }
+ if (_pdflatex && has_transparency) {
+ os << "\\transparent{" << opacity << "}";
+ }
+ if (has_rotation) {
+ os << "\\rotatebox{" << degrees << "}{";
+ }
+ os << "\\makebox(0,0)" << alignment << "{";
+
+ // Scale the x width correctly
+ os << "\\begin{minipage}{" << framebox.width() * transform().expansionX() << "\\unitlength}";
+ os << justification;
+
+ // Walk through all spans in the text object.
+ // Write span strings to LaTeX, associated with font weight and style.
+ Inkscape::Text::Layout const &layout = *(te_get_layout(flowtext));
+ for (Inkscape::Text::Layout::iterator li = layout.begin(), le = layout.end();
+ li != le; li.nextStartOfSpan())
+ {
+ SPStyle const &spanstyle = *(sp_te_style_at_position(flowtext, li));
+ bool is_bold = false, is_italic = false, is_oblique = false;
+
+ if (spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_500 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_600 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_700 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_800 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_900 ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLD ||
+ spanstyle.font_weight.computed == SP_CSS_FONT_WEIGHT_BOLDER)
+ {
+ is_bold = true;
+ os << "\\textbf{";
+ }
+ if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_ITALIC)
+ {
+ is_italic = true;
+ os << "\\textit{";
+ }
+ if (spanstyle.font_style.computed == SP_CSS_FONT_STYLE_OBLIQUE)
+ {
+ is_oblique = true;
+ os << "\\textsl{"; // this is an accurate choice if the LaTeX chosen font matches the font in Inkscape. Gives bad results when it is not so...
+ }
+
+ Inkscape::Text::Layout::iterator ln = li;
+ ln.nextStartOfSpan();
+ Glib::ustring uspanstr = sp_te_get_string_multiline(flowtext, li, ln);
+ const gchar *spanstr = uspanstr.c_str();
+ if (!spanstr) {
+ continue;
+ }
+ // replace carriage return with double slash
+ gchar ** splitstr = g_strsplit(spanstr, "\n", -1);
+ gchar *spanstr_new = g_strjoinv("\\\\ ", splitstr);
+ os << spanstr_new;
+ g_strfreev(splitstr);
+ g_free(spanstr_new);
+
+ if (is_oblique) { os << "}"; } // oblique end
+ if (is_italic) { os << "}"; } // italic end
+ if (is_bold) { os << "}"; } // bold end
+ }
+
+ os << "\\end{minipage}";
+ if (has_rotation) {
+ os << "}"; // rotatebox end
+ }
+ os << "}"; //makebox end
+ os << "}%\n"; // put end
+
+ fprintf(_stream, "%s", os.str().c_str());
+}
+
+void LaTeXTextRenderer::sp_root_render(SPRoot *root)
+{
+ push_transform(root->c2p);
+ sp_group_render(root);
+ pop_transform();
+}
+
+void
+LaTeXTextRenderer::sp_item_invoke_render(SPItem *item)
+{
+ // Check item's visibility
+ if (item->isHidden()) {
+ return;
+ }
+
+ SPRoot *root = dynamic_cast<SPRoot *>(item);
+ if (root) {
+ return sp_root_render(root);
+ }
+ SPGroup *group = dynamic_cast<SPGroup *>(item);
+ if (group) {
+ return sp_group_render(group);
+ }
+ SPUse *use = dynamic_cast<SPUse *>(item);
+ if (use) {
+ return sp_use_render(use);
+ }
+ SPText *text = dynamic_cast<SPText *>(item);
+ if (text) {
+ return sp_text_render(text);
+ }
+ SPFlowtext *flowtext = dynamic_cast<SPFlowtext *>(item);
+ if (flowtext) {
+ return sp_flowtext_render(flowtext);
+ }
+ // Only PDFLaTeX supports importing a single page of a graphics file,
+ // so only PDF backend gets interleaved text/graphics
+ if (_pdflatex && (_omittext_state == EMPTY || _omittext_state == NEW_PAGE_ON_GRAPHIC)) {
+ writeGraphicPage();
+ }
+ _omittext_state = GRAPHIC_ON_TOP;
+}
+
+void
+LaTeXTextRenderer::renderItem(SPItem *item)
+{
+ push_transform(item->transform);
+ sp_item_invoke_render(item);
+ pop_transform();
+}
+
+void
+LaTeXTextRenderer::writeGraphicPage() {
+ Inkscape::SVGOStringStream os;
+ os.setf(std::ios::fixed); // no scientific notation
+
+ // strip pathname, as it is probably desired. Having a specific path in the TeX file is not convenient.
+ if (_pdflatex)
+ os << " \\put(0,0){\\includegraphics[width=\\unitlength,page=" << _omittext_page++ << "]{" << _filename << "}}%\n";
+ else
+ os << " \\put(0,0){\\includegraphics[width=\\unitlength]{" << _filename << "}}%\n";
+
+ fprintf(_stream, "%s", os.str().c_str());
+}
+
+bool
+LaTeXTextRenderer::setupDocument(SPDocument *doc, bool pageBoundingBox, double bleedmargin_px, SPItem *base)
+{
+// The boundingbox calculation here should be exactly the same as the one by CairoRenderer::setupDocument !
+
+ if (!base) {
+ base = doc->getRoot();
+ }
+
+ Geom::Rect d;
+ if (pageBoundingBox) {
+ d = Geom::Rect::from_xywh(Geom::Point(0,0), doc->getDimensions());
+ } else {
+ Geom::OptRect bbox = base->documentVisualBounds();
+ if (!bbox) {
+ g_message("CairoRenderer: empty bounding box.");
+ return false;
+ }
+ d = *bbox;
+ }
+ d.expandBy(bleedmargin_px);
+
+ // scale all coordinates, such that the width of the image is 1, this is convenient for scaling the image in LaTeX
+ double scale = 1/(d.width());
+ double _width = d.width() * scale;
+ double _height = d.height() * scale;
+ push_transform(Geom::Translate(-d.corner(3)) * Geom::Scale(scale, -scale));
+
+ // write the info to LaTeX
+ Inkscape::SVGOStringStream os;
+ os.setf(std::ios::fixed); // no scientific notation
+
+ // scaling of the image when including it in LaTeX
+ os << " \\ifx\\svgwidth\\undefined%\n";
+ os << " \\setlength{\\unitlength}{" << Inkscape::Util::Quantity::convert(d.width(), "px", "pt") << "bp}%\n"; // note: 'bp' is the Postscript pt unit in LaTeX, see LP bug #792384
+ os << " \\ifx\\svgscale\\undefined%\n";
+ os << " \\relax%\n";
+ os << " \\else%\n";
+ os << " \\setlength{\\unitlength}{\\unitlength * \\real{\\svgscale}}%\n";
+ os << " \\fi%\n";
+ os << " \\else%\n";
+ os << " \\setlength{\\unitlength}{\\svgwidth}%\n";
+ os << " \\fi%\n";
+ os << " \\global\\let\\svgwidth\\undefined%\n";
+ os << " \\global\\let\\svgscale\\undefined%\n";
+ os << " \\makeatother%\n";
+
+ os << " \\begin{picture}(" << _width << "," << _height << ")%\n";
+
+ // set \baselineskip equal to fontsize (the closest we can seem to get to CSS "line-height: 1;")
+ // and remove column spacing from tabular
+ os << " \\lineheight{1}%\n";
+ os << " \\setlength\\tabcolsep{0pt}%\n";
+
+ fprintf(_stream, "%s", os.str().c_str());
+
+ if (!_pdflatex)
+ writeGraphicPage();
+
+ return true;
+}
+
+Geom::Affine const &
+LaTeXTextRenderer::transform()
+{
+ return _transform_stack.top();
+}
+
+void
+LaTeXTextRenderer::push_transform(Geom::Affine const &tr)
+{
+ if(!_transform_stack.empty()){
+ Geom::Affine tr_top = _transform_stack.top();
+ _transform_stack.push(tr * tr_top);
+ } else {
+ _transform_stack.push(tr);
+ }
+}
+
+void
+LaTeXTextRenderer::pop_transform()
+{
+ _transform_stack.pop();
+}
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/latex-text-renderer.h b/src/extension/internal/latex-text-renderer.h
new file mode 100644
index 0000000..0870f0c
--- /dev/null
+++ b/src/extension/internal/latex-text-renderer.h
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef EXTENSION_INTERNAL_LATEX_TEXT_RENDERER_H_SEEN
+#define EXTENSION_INTERNAL_LATEX_TEXT_RENDERER_H_SEEN
+
+/** \file
+ * Declaration of LaTeXTextRenderer, used for rendering the accompanying LaTeX file when exporting to PDF/EPS/PS + LaTeX
+ */
+/*
+ * Authors:
+ * Johan Engelen <goejendaagh@zonnet.nl>
+ *
+ * Copyright (C) 2010 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "extension/extension.h"
+#include <2geom/affine.h>
+#include <stack>
+
+class SPItem;
+class SPRoot;
+class SPGroup;
+class SPUse;
+class SPText;
+class SPFlowtext;
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+bool latex_render_document_text_to_file(SPDocument *doc, gchar const *filename,
+ const gchar * const exportId, bool exportDrawing, bool exportCanvas, double bleedmargin_px,
+ bool pdflatex);
+
+class LaTeXTextRenderer {
+public:
+ LaTeXTextRenderer(bool pdflatex);
+ virtual ~LaTeXTextRenderer();
+
+ bool setTargetFile(gchar const *filename);
+
+ /** Initializes the LaTeXTextRenderer according to the specified
+ SPDocument. Important to set the boundingbox to the pdf boundingbox */
+ bool setupDocument(SPDocument *doc, bool pageBoundingBox, double bleedmargin_px, SPItem *base);
+
+ /** Traverses the object tree and invokes the render methods. */
+ void renderItem(SPItem *item);
+
+protected:
+ enum LaTeXOmitTextPageState {
+ EMPTY,
+ GRAPHIC_ON_TOP,
+ NEW_PAGE_ON_GRAPHIC
+ };
+
+ FILE * _stream;
+ gchar * _filename;
+
+ bool _pdflatex; /** true if outputting for pdfLaTeX*/
+
+ LaTeXOmitTextPageState _omittext_state;
+ gulong _omittext_page;
+
+ void push_transform(Geom::Affine const &transform);
+ Geom::Affine const & transform();
+ void pop_transform();
+ std::stack<Geom::Affine> _transform_stack;
+
+ void writePreamble();
+ void writePostamble();
+
+ void writeGraphicPage();
+
+ void sp_item_invoke_render(SPItem *item);
+ void sp_root_render(SPRoot *item);
+ void sp_group_render(SPGroup *group);
+ void sp_use_render(SPUse *use);
+ void sp_text_render(SPText *text);
+ void sp_flowtext_render(SPFlowtext *flowtext);
+};
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* !EXTENSION_INTERNAL_LATEX_TEXT_RENDERER_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/metafile-inout.cpp b/src/extension/internal/metafile-inout.cpp
new file mode 100644
index 0000000..3c8d5dc
--- /dev/null
+++ b/src/extension/internal/metafile-inout.cpp
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Metafile input - common routines
+ *//*
+ * Authors:
+ * David Mathog
+ *
+ * Copyright (C) 2013 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <fstream>
+#include <glib.h>
+#include <glibmm/miscutils.h>
+
+#include "display/curve.h"
+#include "extension/internal/metafile-inout.h" // picks up PNG
+#include "extension/print.h"
+#include "path-prefix.h"
+#include "document.h"
+#include "util/units.h"
+#include "ui/shape-editor.h"
+#include "document-undo.h"
+#include "inkscape.h"
+#include "preferences.h"
+
+#include "object/sp-root.h"
+#include "object/sp-namedview.h"
+#include "svg/stringstream.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+Metafile::~Metafile()
+{
+ return;
+}
+
+/** Construct a PNG in memory from an RGB from the EMF file
+
+from:
+http://www.lemoda.net/c/write-png/
+
+which was based on:
+http://stackoverflow.com/questions/1821806/how-to-encode-png-to-buffer-using-libpng
+
+gcc -Wall -o testpng testpng.c -lpng
+
+Originally here, but moved up
+
+#include <png.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+*/
+
+
+/* Given "bitmap", this returns the pixel of bitmap at the point
+ ("x", "y"). */
+
+pixel_t * Metafile::pixel_at (bitmap_t * bitmap, int x, int y)
+{
+ return bitmap->pixels + bitmap->width * y + x;
+}
+
+
+/* Write "bitmap" to a PNG file specified by "path"; returns 0 on
+ success, non-zero on error. */
+
+void
+Metafile::my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ PMEMPNG p=(PMEMPNG)png_get_io_ptr(png_ptr);
+
+ size_t nsize = p->size + length;
+
+ /* allocate or grow buffer */
+ if(p->buffer){ p->buffer = (char *) realloc(p->buffer, nsize); }
+ else{ p->buffer = (char *) malloc(nsize); }
+
+ if(!p->buffer){ png_error(png_ptr, "Write Error"); }
+
+ /* copy new bytes to end of buffer */
+ memcpy(p->buffer + p->size, data, length);
+ p->size += length;
+}
+
+void Metafile::toPNG(PMEMPNG accum, int width, int height, const char *px){
+ bitmap_t bmStore;
+ bitmap_t *bitmap = &bmStore;
+ accum->buffer=nullptr; // PNG constructed in memory will end up here, caller must free().
+ accum->size=0;
+ bitmap->pixels=(pixel_t *)px;
+ bitmap->width = width;
+ bitmap->height = height;
+
+ png_structp png_ptr = nullptr;
+ png_infop info_ptr = nullptr;
+ size_t x, y;
+ png_byte ** row_pointers = nullptr;
+ /* The following number is set by trial and error only. I cannot
+ see where it it is documented in the libpng manual.
+ */
+ int pixel_size = 3;
+ int depth = 8;
+
+ png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if (png_ptr == nullptr){
+ accum->buffer=nullptr;
+ return;
+ }
+
+ info_ptr = png_create_info_struct (png_ptr);
+ if (info_ptr == nullptr){
+ png_destroy_write_struct (&png_ptr, &info_ptr);
+ accum->buffer=nullptr;
+ return;
+ }
+
+ /* Set up error handling. */
+
+ if (setjmp (png_jmpbuf (png_ptr))) {
+ png_destroy_write_struct (&png_ptr, &info_ptr);
+ accum->buffer=nullptr;
+ return;
+ }
+
+ /* Set image attributes. */
+
+ png_set_IHDR (
+ png_ptr,
+ info_ptr,
+ bitmap->width,
+ bitmap->height,
+ depth,
+ PNG_COLOR_TYPE_RGB,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT
+ );
+
+ /* Initialize rows of PNG. */
+
+ row_pointers = (png_byte **) png_malloc (png_ptr, bitmap->height * sizeof (png_byte *));
+ for (y = 0; y < bitmap->height; ++y) {
+ png_byte *row =
+ (png_byte *) png_malloc (png_ptr, sizeof (uint8_t) * bitmap->width * pixel_size);
+ row_pointers[bitmap->height - y - 1] = row; // Row order in EMF is reversed.
+ for (x = 0; x < bitmap->width; ++x) {
+ pixel_t * pixel = pixel_at (bitmap, x, y);
+ *row++ = pixel->red; // R & B channels were set correctly by DIB_to_RGB
+ *row++ = pixel->green;
+ *row++ = pixel->blue;
+ }
+ }
+
+ /* Write the image data to memory */
+
+ png_set_rows (png_ptr, info_ptr, row_pointers);
+
+ png_set_write_fn(png_ptr, accum, my_png_write_data, nullptr);
+
+ png_write_png (png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, nullptr);
+
+ for (y = 0; y < bitmap->height; y++) {
+ png_free (png_ptr, row_pointers[y]);
+ }
+ png_free (png_ptr, row_pointers);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+}
+
+/* If the viewBox is missing, set one
+*/
+void Metafile::setViewBoxIfMissing(SPDocument *doc) {
+
+ if (doc && !doc->getRoot()->viewBox_set) {
+ bool saved = Inkscape::DocumentUndo::getUndoSensitive(doc);
+ Inkscape::DocumentUndo::setUndoSensitive(doc, false);
+
+ doc->ensureUpToDate();
+
+ // Set document unit
+ Inkscape::XML::Node *repr = doc->getNamedView()->getRepr();
+ Inkscape::SVGOStringStream os;
+ Inkscape::Util::Unit const* doc_unit = doc->getWidth().unit;
+ os << doc_unit->abbr;
+ repr->setAttribute("inkscape:document-units", os.str());
+
+ // Set viewBox
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc_unit), doc->getHeight().value(doc_unit)));
+ doc->ensureUpToDate();
+
+ // Scale and translate objects
+ double scale = Inkscape::Util::Quantity::convert(1, "px", doc_unit);
+ Inkscape::UI::ShapeEditor::blockSetItem(true);
+ double dh;
+ if(SP_ACTIVE_DOCUMENT){ // for file menu open or import, or paste from clipboard
+ dh = SP_ACTIVE_DOCUMENT->getHeight().value("px");
+ }
+ else { // for open via --file on command line
+ dh = doc->getHeight().value("px");
+ }
+
+ // These should not affect input, but they do, so set them to a neutral state
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool transform_stroke = prefs->getBool("/options/transform/stroke", true);
+ bool transform_rectcorners = prefs->getBool("/options/transform/rectcorners", true);
+ bool transform_pattern = prefs->getBool("/options/transform/pattern", true);
+ bool transform_gradient = prefs->getBool("/options/transform/gradient", true);
+ prefs->setBool("/options/transform/stroke", true);
+ prefs->setBool("/options/transform/rectcorners", true);
+ prefs->setBool("/options/transform/pattern", true);
+ prefs->setBool("/options/transform/gradient", true);
+
+ doc->getRoot()->scaleChildItemsRec(Geom::Scale(scale), Geom::Point(0, dh), true);
+ Inkscape::UI::ShapeEditor::blockSetItem(false);
+
+ // restore options
+ prefs->setBool("/options/transform/stroke", transform_stroke);
+ prefs->setBool("/options/transform/rectcorners", transform_rectcorners);
+ prefs->setBool("/options/transform/pattern", transform_pattern);
+ prefs->setBool("/options/transform/gradient", transform_gradient);
+
+ Inkscape::DocumentUndo::setUndoSensitive(doc, saved);
+ }
+}
+
+/**
+ \fn Convert EMF/WMF region combining ops to livarot region combining ops
+ \return combination operators in livarot enumeration, or -1 on no match
+ \param ops (int) combination operator (Inkscape)
+*/
+int Metafile::combine_ops_to_livarot(const int op)
+{
+ int ret = -1;
+ switch(op) {
+ case U_RGN_AND:
+ ret = bool_op_inters;
+ break;
+ case U_RGN_OR:
+ ret = bool_op_union;
+ break;
+ case U_RGN_XOR:
+ ret = bool_op_symdiff;
+ break;
+ case U_RGN_DIFF:
+ ret = bool_op_diff;
+ break;
+ }
+ return(ret);
+}
+
+
+
+/* convert an EMF RGB(A) color to 0RGB
+inverse of gethexcolor() in emf-print.cpp
+*/
+uint32_t Metafile::sethexcolor(U_COLORREF color){
+
+ uint32_t out;
+ out = (U_RGBAGetR(color) << 16) +
+ (U_RGBAGetG(color) << 8 ) +
+ (U_RGBAGetB(color) );
+ return(out);
+}
+
+/* Return the base64 encoded png which is shown for all bad images.
+Currently a random 3x4 blotch.
+Caller must free.
+*/
+gchar *Metafile::bad_image_png(){
+ gchar *gstring = g_strdup("iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAA3NCSVQICAjb4U/gAAAALElEQVQImQXBQQ2AMAAAsUJQMSWI2H8qME1yMshojwrvGB8XcHKvR1XtOTc/8HENumHCsOMAAAAASUVORK5CYII=");
+ return(gstring);
+}
+
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/metafile-inout.h b/src/extension/internal/metafile-inout.h
new file mode 100644
index 0000000..c742a64
--- /dev/null
+++ b/src/extension/internal/metafile-inout.h
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Metafile input - common functions
+ *//*
+ * Authors:
+ * David Mathog
+ *
+ * Copyright (C) 2013 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_H
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_H
+
+#define PNG_SKIP_SETJMP_CHECK // else any further png.h include blows up in the compiler
+#include <png.h>
+#include <cstdio>
+#include <cstdlib>
+#include <cstdint>
+#include <map>
+#include <stack>
+#include <glibmm/ustring.h>
+#include <3rdparty/libuemf/uemf.h>
+#include <2geom/affine.h>
+#include <2geom/pathvector.h>
+
+#include "extension/implementation/implementation.h"
+
+class SPObject;
+
+namespace Inkscape {
+class Pixbuf;
+
+namespace Extension {
+namespace Internal {
+
+/* A coloured pixel. */
+struct pixel_t {
+ uint8_t red;
+ uint8_t green;
+ uint8_t blue;
+ uint8_t opacity;
+};
+
+/* A picture. */
+struct bitmap_t {
+ pixel_t *pixels;
+ size_t width;
+ size_t height;
+};
+
+/* structure to store PNG image bytes */
+struct MEMPNG {
+ char *buffer;
+ size_t size;
+};
+using PMEMPNG = MEMPNG *;
+
+class Metafile
+ : public Inkscape::Extension::Implementation::Implementation
+{
+public:
+ Metafile() = default;
+ ~Metafile() override;
+
+protected:
+ static uint32_t sethexcolor(U_COLORREF color);
+ static pixel_t *pixel_at (bitmap_t * bitmap, int x, int y);
+ static void my_png_write_data(png_structp png_ptr, png_bytep data, png_size_t length);
+ static void toPNG(PMEMPNG accum, int width, int height, const char *px);
+ static gchar *bad_image_png();
+ static void setViewBoxIfMissing(SPDocument *doc);
+ static int combine_ops_to_livarot(const int op);
+
+
+private:
+};
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_INOUT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
diff --git a/src/extension/internal/metafile-print.cpp b/src/extension/internal/metafile-print.cpp
new file mode 100644
index 0000000..7cb5800
--- /dev/null
+++ b/src/extension/internal/metafile-print.cpp
@@ -0,0 +1,478 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Metafile printing - common routines
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2013 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <fstream>
+#include <glib.h>
+#include <glibmm/miscutils.h>
+#include <2geom/rect.h>
+#include <2geom/curves.h>
+#include <2geom/svg-path-parser.h>
+
+#include "extension/internal/metafile-print.h"
+#include "extension/print.h"
+#include "path-prefix.h"
+#include "object/sp-gradient.h"
+#include "object/sp-image.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-pattern.h"
+#include "object/sp-radial-gradient.h"
+#include "style.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+PrintMetafile::~PrintMetafile()
+{
+#ifndef G_OS_WIN32
+ // restore default signal handling for SIGPIPE
+ (void) signal(SIGPIPE, SIG_DFL);
+#endif
+ return;
+}
+
+static std::map<Glib::ustring, FontfixParams> const &get_ppt_fixable_fonts()
+{
+ static std::map<Glib::ustring, FontfixParams> _ppt_fixable_fonts;
+
+ if (_ppt_fixable_fonts.empty()) {
+ _ppt_fixable_fonts = {
+ // clang-format off
+ {{"Arial"}, { 0.05, -0.055, -0.065}},
+ {{"Times New Roman"}, { 0.05, -0.055, -0.065}},
+ {{"Lucida Sans"}, {-0.025, -0.055, -0.065}},
+ {{"Sans"}, { 0.05, -0.055, -0.065}},
+ {{"Microsoft Sans Serif"}, {-0.05, -0.055, -0.065}},
+ {{"Serif"}, { 0.05, -0.055, -0.065}},
+ {{"Garamond"}, { 0.05, -0.055, -0.065}},
+ {{"Century Schoolbook"}, { 0.25, 0.025, 0.025}},
+ {{"Verdana"}, { 0.025, 0.0, 0.0}},
+ {{"Tahoma"}, { 0.045, 0.025, 0.025}},
+ {{"Symbol"}, { 0.025, 0.0, 0.0}},
+ {{"Wingdings"}, { 0.05, 0.0, 0.0}},
+ {{"Zapf Dingbats"}, { 0.025, 0.0, 0.0}},
+ {{"Convert To Symbol"}, { 0.025, 0.0, 0.0}},
+ {{"Convert To Wingdings"}, { 0.05, 0.0, 0.0}},
+ {{"Convert To Zapf Dingbats"}, { 0.025, 0.0, 0.0}},
+ {{"Sylfaen"}, { 0.1, 0.0, 0.0}},
+ {{"Palatino Linotype"}, { 0.175, 0.125, 0.125}},
+ {{"Segoe UI"}, { 0.1, 0.0, 0.0}},
+ // clang-format on
+ };
+ }
+ return _ppt_fixable_fonts;
+}
+
+
+bool PrintMetafile::textToPath(Inkscape::Extension::Print *ext)
+{
+ return ext->get_param_bool("textToPath");
+}
+
+unsigned int PrintMetafile::bind(Inkscape::Extension::Print * /*mod*/, Geom::Affine const &transform, float /*opacity*/)
+{
+ if (!m_tr_stack.empty()) {
+ Geom::Affine tr_top = m_tr_stack.top();
+ m_tr_stack.push(transform * tr_top);
+ } else {
+ m_tr_stack.push(transform);
+ }
+
+ return 1;
+}
+
+unsigned int PrintMetafile::release(Inkscape::Extension::Print * /*mod*/)
+{
+ m_tr_stack.pop();
+ return 1;
+}
+
+// Finds font fix parameters for the given fontname.
+void PrintMetafile::_lookup_ppt_fontfix(Glib::ustring const &fontname, FontfixParams &params)
+{
+ auto const &fixable_fonts = get_ppt_fixable_fonts();
+ auto it = fixable_fonts.find(fontname);
+ if (it != fixable_fonts.end()) {
+ params = it->second;
+ }
+}
+
+U_COLORREF PrintMetafile::_gethexcolor(uint32_t color)
+{
+ U_COLORREF out;
+ out = U_RGB(
+ (color >> 16) & 0xFF,
+ (color >> 8) & 0xFF,
+ (color >> 0) & 0xFF
+ );
+ return out;
+}
+
+// Translate Inkscape weights to EMF weights.
+uint32_t PrintMetafile::_translate_weight(unsigned inkweight)
+{
+ switch (inkweight) {
+ // 400 is tested first, as it is the most common case
+ case SP_CSS_FONT_WEIGHT_400: return U_FW_NORMAL;
+ case SP_CSS_FONT_WEIGHT_100: return U_FW_THIN;
+ case SP_CSS_FONT_WEIGHT_200: return U_FW_EXTRALIGHT;
+ case SP_CSS_FONT_WEIGHT_300: return U_FW_LIGHT;
+ case SP_CSS_FONT_WEIGHT_500: return U_FW_MEDIUM;
+ case SP_CSS_FONT_WEIGHT_600: return U_FW_SEMIBOLD;
+ case SP_CSS_FONT_WEIGHT_700: return U_FW_BOLD;
+ case SP_CSS_FONT_WEIGHT_800: return U_FW_EXTRABOLD;
+ case SP_CSS_FONT_WEIGHT_900: return U_FW_HEAVY;
+ default: return U_FW_NORMAL;
+ }
+}
+
+/* opacity weighting of two colors as float. v1 is the color, op is its opacity, v2 is the background color */
+inline float opweight(float v1, float v2, float op)
+{
+ return v1 * op + v2 * (1.0 - op);
+}
+
+U_COLORREF PrintMetafile::avg_stop_color(SPGradient *gr)
+{
+ U_COLORREF cr;
+ int last = gr->vector.stops.size() - 1;
+ if (last >= 1) {
+ float rgbs[3];
+ float rgbe[3];
+ float ops, ope;
+
+ ops = gr->vector.stops[0 ].opacity;
+ ope = gr->vector.stops[last].opacity;
+ gr->vector.stops[0 ].color.get_rgb_floatv(rgbs);
+ gr->vector.stops[last].color.get_rgb_floatv(rgbe);
+
+ /* Replace opacity at start & stop with that fraction background color, then average those two for final color. */
+ cr = U_RGB(
+ 255 * ((opweight(rgbs[0], gv.rgb[0], ops) + opweight(rgbe[0], gv.rgb[0], ope)) / 2.0),
+ 255 * ((opweight(rgbs[1], gv.rgb[1], ops) + opweight(rgbe[1], gv.rgb[1], ope)) / 2.0),
+ 255 * ((opweight(rgbs[2], gv.rgb[2], ops) + opweight(rgbe[2], gv.rgb[2], ope)) / 2.0)
+ );
+ } else {
+ cr = U_RGB(0, 0, 0); // The default fill
+ }
+ return cr;
+}
+
+U_COLORREF PrintMetafile::weight_opacity(U_COLORREF c1)
+{
+ float opa = c1.Reserved / 255.0;
+ U_COLORREF result = U_RGB(
+ 255 * opweight((float)c1.Red / 255.0, gv.rgb[0], opa),
+ 255 * opweight((float)c1.Green / 255.0, gv.rgb[1], opa),
+ 255 * opweight((float)c1.Blue / 255.0, gv.rgb[2], opa)
+ );
+ return result;
+}
+
+/* t between 0 and 1, values outside that range use the nearest limit */
+U_COLORREF PrintMetafile::weight_colors(U_COLORREF c1, U_COLORREF c2, double t)
+{
+#define clrweight(a,b,t) ((1-t)*((double) a) + (t)*((double) b))
+ U_COLORREF result;
+ t = ( t > 1.0 ? 1.0 : ( t < 0.0 ? 0.0 : t));
+ // clang-format off
+ 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);
+ // clang-format on
+
+ // now handle the opacity, mix the RGB with background at the weighted opacity
+
+ if (result.Reserved != 255) {
+ result = weight_opacity(result);
+ }
+
+ return result;
+}
+
+// Extract hatchType, hatchColor from a name like
+// EMFhatch<hatchType>_<hatchColor>
+// Where the first one is a number and the second a color in hex.
+// hatchType and hatchColor have been set with defaults before this is called.
+//
+void PrintMetafile::hatch_classify(char *name, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor)
+{
+ int val;
+ uint32_t hcolor = 0;
+ uint32_t bcolor = 0;
+
+ // name should be EMFhatch or WMFhatch but *MFhatch will be accepted
+ if (0 != strncmp(&name[1], "MFhatch", 7)) {
+ return; // not anything we can parse
+ }
+ name += 8; // EMFhatch already detected
+ val = 0;
+ while (*name && isdigit(*name)) {
+ val = 10 * val + *name - '0';
+ name++;
+ }
+ *hatchType = val;
+ if (*name != '_' || val > U_HS_DITHEREDBKCLR) { // wrong syntax, cannot classify
+ *hatchType = -1;
+ } else {
+ name++;
+ if (2 != sscanf(name, "%X_%X", &hcolor, &bcolor)) { // not a pattern with background
+ if (1 != sscanf(name, "%X", &hcolor)) {
+ *hatchType = -1; // not a pattern, cannot classify
+ }
+ *hatchColor = _gethexcolor(hcolor);
+ } else {
+ *hatchColor = _gethexcolor(hcolor);
+ *bkColor = _gethexcolor(bcolor);
+ usebk = true;
+ }
+ }
+ /* Everything > U_HS_SOLIDCLR is solid, just specify the color in the brush rather than messing around with background or textcolor */
+ if (*hatchType > U_HS_SOLIDCLR) {
+ *hatchType = U_HS_SOLIDCLR;
+ }
+}
+
+//
+// Recurse down from a brush pattern, try to figure out what it is.
+// If an image is found set a pointer to the epixbuf, else set that to NULL
+// If a pattern is found with a name like [EW]MFhatch3_3F7FFF return hatchType=3, hatchColor=3F7FFF (as a uint32_t),
+// otherwise hatchType is set to -1 and hatchColor is not defined.
+//
+
+void PrintMetafile::brush_classify(SPObject *parent, int depth, Inkscape::Pixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor)
+{
+ if (depth == 0) {
+ *epixbuf = nullptr;
+ *hatchType = -1;
+ *hatchColor = U_RGB(0, 0, 0);
+ *bkColor = U_RGB(255, 255, 255);
+ }
+ depth++;
+ // first look along the pattern chain, if there is one
+ if (SP_IS_PATTERN(parent)) {
+ for (SPPattern *pat_i = SP_PATTERN(parent); pat_i != nullptr; pat_i = pat_i->ref ? pat_i->ref->getObject() : nullptr) {
+ if (SP_IS_IMAGE(pat_i)) {
+ *epixbuf = ((SPImage *)pat_i)->pixbuf;
+ return;
+ }
+ char temp[32]; // large enough
+ strncpy(temp, pat_i->getAttribute("id"), sizeof(temp)-1); // Some names may be longer than [EW]MFhatch#_######
+ temp[sizeof(temp)-1] = '\0';
+ hatch_classify(temp, hatchType, hatchColor, bkColor);
+ if (*hatchType != -1) {
+ return;
+ }
+
+ // still looking? Look at this pattern's children, if there are any
+ for (auto& child: pat_i->children) {
+ if (*epixbuf || *hatchType != -1) {
+ break;
+ }
+ brush_classify(&child, depth, epixbuf, hatchType, hatchColor, bkColor);
+ }
+ }
+ } else if (SP_IS_IMAGE(parent)) {
+ *epixbuf = ((SPImage *)parent)->pixbuf;
+ return;
+ } else { // some inkscape rearrangements pass through nodes between pattern and image which are not classified as either.
+ for (auto& child: parent->children) {
+ if (*epixbuf || *hatchType != -1) {
+ break;
+ }
+ brush_classify(&child, depth, epixbuf, hatchType, hatchColor, bkColor);
+ }
+ }
+}
+
+//swap R/B in 4 byte pixel
+void PrintMetafile::swapRBinRGBA(char *px, int pixels)
+{
+ char tmp;
+ for (int i = 0; i < pixels * 4; px += 4, i += 4) {
+ tmp = px[2];
+ px[2] = px[0];
+ px[0] = tmp;
+ }
+}
+
+int PrintMetafile::hold_gradient(void *gr, int mode)
+{
+ gv.mode = mode;
+ gv.grad = gr;
+ if (mode == DRAW_RADIAL_GRADIENT) {
+ SPRadialGradient *rg = (SPRadialGradient *) gr;
+ gv.r = rg->r.computed; // radius, but of what???
+ gv.p1 = Geom::Point(rg->cx.computed, rg->cy.computed); // center
+ gv.p2 = Geom::Point(gv.r, 0) + gv.p1; // xhandle
+ gv.p3 = Geom::Point(0, -gv.r) + gv.p1; // yhandle
+ if (rg->gradientTransform_set) {
+ gv.p1 = gv.p1 * rg->gradientTransform;
+ gv.p2 = gv.p2 * rg->gradientTransform;
+ gv.p3 = gv.p3 * rg->gradientTransform;
+ }
+ } else if (mode == DRAW_LINEAR_GRADIENT) {
+ SPLinearGradient *lg = (SPLinearGradient *) gr;
+ gv.r = 0; // unused
+ gv.p1 = Geom::Point(lg->x1.computed, lg->y1.computed); // start
+ gv.p2 = Geom::Point(lg->x2.computed, lg->y2.computed); // end
+ gv.p3 = Geom::Point(0, 0); // unused
+ if (lg->gradientTransform_set) {
+ gv.p1 = gv.p1 * lg->gradientTransform;
+ gv.p2 = gv.p2 * lg->gradientTransform;
+ }
+ } else {
+ g_error("Fatal programming error, hold_gradient() in metafile-print.cpp called with invalid draw mode");
+ }
+ return 1;
+}
+
+/* convert from center ellipse to SVGEllipticalArc ellipse
+
+ From:
+ http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+ A point (x,y) on the arc can be found by:
+
+ {x,y} = {cx,cy} + {cosF,-sinF,sinF,cosF} x {rxcosT,rysinT}
+
+ where
+ {cx,cy} is the center of the ellipse
+ F is the rotation angle of the X axis of the ellipse from the true X axis
+ T is the rotation angle around the ellipse
+ {,,,} is the rotation matrix
+ rx,ry are the radii of the ellipse's axes
+
+ For SVG parameterization need two points.
+ Arbitrarily we can use T=0 and T=pi
+ Since the sweep is 180 the flags are always 0:
+
+ F is in RADIANS, but the SVGEllipticalArc needs degrees!
+
+*/
+Geom::PathVector PrintMetafile::center_ellipse_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F)
+{
+ using Geom::X;
+ using Geom::Y;
+ double x1, y1, x2, y2;
+ Geom::Path SVGep;
+
+ x1 = ctr[X] + cos(F) * rx * cos(0) + sin(-F) * ry * sin(0);
+ y1 = ctr[Y] + sin(F) * rx * cos(0) + cos(F) * ry * sin(0);
+ x2 = ctr[X] + cos(F) * rx * cos(M_PI) + sin(-F) * ry * sin(M_PI);
+ y2 = ctr[Y] + sin(F) * rx * cos(M_PI) + cos(F) * ry * sin(M_PI);
+
+ char text[256];
+ snprintf(text, 256, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z",
+ x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1);
+ Geom::PathVector outres = Geom::parse_svg_path(text);
+ return outres;
+}
+
+
+/* rx2,ry2 must be larger than rx1,ry1!
+ angle is in RADIANS
+*/
+Geom::PathVector PrintMetafile::center_elliptical_ring_as_SVG_PathV(Geom::Point ctr, double rx1, double ry1, double rx2, double ry2, double F)
+{
+ using Geom::X;
+ using Geom::Y;
+ double x11, y11, x12, y12;
+ double x21, y21, x22, y22;
+ double degrot = F * 360. / (2.*M_PI);
+
+ x11 = ctr[X] + cos(F) * rx1 * cos(0) + sin(-F) * ry1 * sin(0);
+ y11 = ctr[Y] + sin(F) * rx1 * cos(0) + cos(F) * ry1 * sin(0);
+ x12 = ctr[X] + cos(F) * rx1 * cos(M_PI) + sin(-F) * ry1 * sin(M_PI);
+ y12 = ctr[Y] + sin(F) * rx1 * cos(M_PI) + cos(F) * ry1 * sin(M_PI);
+
+ x21 = ctr[X] + cos(F) * rx2 * cos(0) + sin(-F) * ry2 * sin(0);
+ y21 = ctr[Y] + sin(F) * rx2 * cos(0) + cos(F) * ry2 * sin(0);
+ x22 = ctr[X] + cos(F) * rx2 * cos(M_PI) + sin(-F) * ry2 * sin(M_PI);
+ y22 = ctr[Y] + sin(F) * rx2 * cos(M_PI) + cos(F) * ry2 * sin(M_PI);
+
+ char text[512];
+ snprintf(text, 512, " M %f,%f A %f %f %f 0 1 %f %f A %f %f %f 0 1 %f %f z M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z",
+ x11, y11, rx1, ry1, degrot, x12, y12, rx1, ry1, degrot, x11, y11,
+ x21, y21, rx2, ry2, degrot, x22, y22, rx2, ry2, degrot, x21, y21);
+ Geom::PathVector outres = Geom::parse_svg_path(text);
+
+ return outres;
+}
+
+/* Elliptical hole in a large square extending from -50k to +50k */
+Geom::PathVector PrintMetafile::center_elliptical_hole_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F)
+{
+ using Geom::X;
+ using Geom::Y;
+ double x1, y1, x2, y2;
+ Geom::Path SVGep;
+
+ x1 = ctr[X] + cos(F) * rx * cos(0) + sin(-F) * ry * sin(0);
+ y1 = ctr[Y] + sin(F) * rx * cos(0) + cos(F) * ry * sin(0);
+ x2 = ctr[X] + cos(F) * rx * cos(M_PI) + sin(-F) * ry * sin(M_PI);
+ y2 = ctr[Y] + sin(F) * rx * cos(M_PI) + cos(F) * ry * sin(M_PI);
+
+ char text[256];
+ snprintf(text, 256, " M %f,%f A %f %f %f 0 0 %f %f A %f %f %f 0 0 %f %f z M 50000,50000 50000,-50000 -50000,-50000 -50000,50000 z",
+ x1, y1, rx, ry, F * 360. / (2.*M_PI), x2, y2, rx, ry, F * 360. / (2.*M_PI), x1, y1);
+ Geom::PathVector outres = Geom::parse_svg_path(text);
+ return outres;
+}
+
+/* rectangular cutter.
+ctr "center" of rectangle (might not actually be in the center with respect to leading/trailing edges
+pos vector from center to leading edge
+neg vector from center to trailing edge
+width vector to side edge
+*/
+Geom::PathVector PrintMetafile::rect_cutter(Geom::Point ctr, Geom::Point pos, Geom::Point neg, Geom::Point width)
+{
+ Geom::PathVector outres;
+ Geom::Path cutter;
+ cutter.start(ctr + pos - width);
+ cutter.appendNew<Geom::LineSegment>(ctr + pos + width);
+ cutter.appendNew<Geom::LineSegment>(ctr + neg + width);
+ cutter.appendNew<Geom::LineSegment>(ctr + neg - width);
+ cutter.close();
+ outres.push_back(cutter);
+ return outres;
+}
+
+/* Convert from SPWindRule to livarot's FillRule
+ This is similar to what sp_selected_path_boolop() does
+*/
+FillRule PrintMetafile::SPWR_to_LVFR(SPWindRule wr)
+{
+ FillRule fr;
+ if (wr == SP_WIND_RULE_EVENODD) {
+ fr = fill_oddEven;
+ } else {
+ fr = fill_nonZero;
+ }
+ return fr;
+}
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/metafile-print.h b/src/extension/internal/metafile-print.h
new file mode 100644
index 0000000..d828f9f
--- /dev/null
+++ b/src/extension/internal/metafile-print.h
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Metafile printing - common functions
+ *//*
+ * Authors:
+ * Krzysztof Kosiński <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2013 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_PRINT_H
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_METAFILE_PRINT_H
+
+#include <map>
+#include <stack>
+
+#include <glibmm/ustring.h>
+#include <3rdparty/libuemf/uemf.h>
+#include <2geom/affine.h>
+#include <2geom/pathvector.h>
+
+#include "extension/implementation/implementation.h"
+
+#include "style-enums.h" // Fill rule
+#include "livarot/LivarotDefs.h" // FillRule
+
+class SPGradient;
+class SPObject;
+
+namespace Inkscape {
+class Pixbuf;
+
+namespace Extension {
+namespace Internal {
+
+enum MFDrawMode {DRAW_PAINT, DRAW_PATTERN, DRAW_IMAGE, DRAW_LINEAR_GRADIENT, DRAW_RADIAL_GRADIENT};
+
+struct FontfixParams {
+ double f1; //Vertical (rotating) offset factor (* font height)
+ double f2; //Vertical (nonrotating) offset factor (* font height)
+ double f3; //Horizontal (nonrotating) offset factor (* font height)
+};
+
+class PrintMetafile
+ : public Inkscape::Extension::Implementation::Implementation
+{
+public:
+ PrintMetafile() = default;
+ ~PrintMetafile() override;
+
+ bool textToPath (Inkscape::Extension::Print * ext) override;
+ unsigned int bind(Inkscape::Extension::Print *module, Geom::Affine const &transform, float opacity) override;
+ unsigned int release(Inkscape::Extension::Print *module) override;
+
+protected:
+ struct GRADVALUES {
+ Geom::Point p1; // center or start
+ Geom::Point p2; // xhandle or end
+ Geom::Point p3; // yhandle or unused
+ double r; // radius or unused
+ void *grad; // to access the stops information
+ int mode; // DRAW_LINEAR_GRADIENT or DRAW_RADIAL_GRADIENT, if GRADVALUES is valid, else any value
+ U_COLORREF bgc; // document background color, this is as good a place as any to keep it
+ float rgb[3]; // also background color, but as 0-1 float.
+ };
+
+ double _width;
+ double _height;
+ double _doc_unit_scale; // to pixels, regardless of the document units
+
+ U_RECTL rc;
+
+ uint32_t htextalignment;
+ uint32_t hpolyfillmode; // used to minimize redundant records that set this
+ float htextcolor_rgb[3]; // used to minimize redundant records that set this
+
+ std::stack<Geom::Affine> m_tr_stack;
+ Geom::PathVector fill_pathv;
+ Geom::Affine fill_transform;
+ bool use_stroke;
+ bool use_fill;
+ bool simple_shape;
+ bool usebk;
+
+ GRADVALUES gv;
+
+ static void _lookup_ppt_fontfix(Glib::ustring const &fontname, FontfixParams &);
+ static U_COLORREF _gethexcolor(uint32_t color);
+ static uint32_t _translate_weight(unsigned inkweight);
+
+ U_COLORREF avg_stop_color(SPGradient *gr);
+ U_COLORREF weight_opacity(U_COLORREF c1);
+ U_COLORREF weight_colors(U_COLORREF c1, U_COLORREF c2, double t);
+
+ void hatch_classify(char *name, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor);
+ void brush_classify(SPObject *parent, int depth, Inkscape::Pixbuf **epixbuf, int *hatchType, U_COLORREF *hatchColor, U_COLORREF *bkColor);
+ static void swapRBinRGBA(char *px, int pixels);
+
+ int hold_gradient(void *gr, int mode);
+ static int snprintf_dots(char * s, size_t n, const char * format, ...);
+ static Geom::PathVector center_ellipse_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F);
+ static Geom::PathVector center_elliptical_ring_as_SVG_PathV(Geom::Point ctr, double rx1, double ry1, double rx2, double ry2, double F);
+ static Geom::PathVector center_elliptical_hole_as_SVG_PathV(Geom::Point ctr, double rx, double ry, double F);
+ static Geom::PathVector rect_cutter(Geom::Point ctr, Geom::Point pos, Geom::Point neg, Geom::Point width);
+ static FillRule SPWR_to_LVFR(SPWindRule wr);
+
+ virtual int create_brush(SPStyle const *style, PU_COLORREF fcolor) = 0;
+ virtual void destroy_brush() = 0;
+ virtual int create_pen(SPStyle const *style, const Geom::Affine &transform) = 0;
+ virtual void destroy_pen() = 0;
+};
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/odf.cpp b/src/extension/internal/odf.cpp
new file mode 100644
index 0000000..4b10711
--- /dev/null
+++ b/src/extension/internal/odf.cpp
@@ -0,0 +1,2126 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/** @file
+ * OpenDocument (drawing) input and output
+ *//*
+ * Authors:
+ * Bob Jamison
+ * Abhishek Sharma
+ * Kris De Gussem
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+/*
+ * This is an an entry in the extensions mechanism to begin to enable
+ * the inputting and outputting of OpenDocument Format (ODF) files from
+ * within Inkscape. Although the initial implementations will be very lossy
+ * due to the differences in the models of SVG and ODF, they will hopefully
+ * improve greatly with time. People should consider this to be a framework
+ * that can be continuously upgraded for ever improving fidelity. Potential
+ * developers should especially look in preprocess() and writeTree() to see how
+ * the SVG tree is scanned, read, translated, and then written to ODF.
+ *
+ * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
+ *
+ */
+
+#include "odf.h"
+
+//# System includes
+#include <cstdio>
+#include <ctime>
+#include <vector>
+#include <cmath>
+
+//# Inkscape includes
+#include "clear-n_.h"
+#include "inkscape.h"
+#include "display/curve.h"
+#include <2geom/pathvector.h>
+#include <2geom/curves.h>
+#include <2geom/transforms.h>
+#include <helper/geom.h>
+#include "helper/geom-curves.h"
+#include "extension/system.h"
+
+#include "xml/repr.h"
+#include "xml/attribute-record.h"
+#include "object/sp-image.h"
+#include "object/sp-gradient.h"
+#include "object/sp-stop.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-root.h"
+#include "object/sp-path.h"
+#include "object/sp-text.h"
+#include "object/sp-flowtext.h"
+#include "object/uri.h"
+#include "style.h"
+
+#include "svg/svg.h"
+#include "text-editing.h"
+#include "util/units.h"
+
+
+#include "inkscape-version.h"
+#include "document.h"
+#include "extension/extension.h"
+
+#include "io/stream/bufferstream.h"
+#include "io/stream/stringstream.h"
+#include "io/sys.h"
+#include <util/ziptool.h>
+#include <iomanip>
+namespace Inkscape
+{
+namespace Extension
+{
+namespace Internal
+{
+//# Shorthand notation
+typedef Inkscape::IO::BufferOutputStream BufferOutputStream;
+typedef Inkscape::IO::OutputStreamWriter OutputStreamWriter;
+typedef Inkscape::IO::StringOutputStream StringOutputStream;
+
+
+//########################################################################
+//# C L A S S SingularValueDecomposition
+//########################################################################
+#include <cmath>
+
+class SVDMatrix
+{
+public:
+
+ SVDMatrix()
+ {
+ init();
+ }
+
+ SVDMatrix(unsigned int rowSize, unsigned int colSize)
+ {
+ init();
+ rows = rowSize;
+ cols = colSize;
+ size = rows * cols;
+ d = new double[size];
+ for (unsigned int i=0 ; i<size ; i++)
+ d[i] = 0.0;
+ }
+
+ SVDMatrix(double *vals, unsigned int rowSize, unsigned int colSize)
+ {
+ init();
+ rows = rowSize;
+ cols = colSize;
+ size = rows * cols;
+ d = new double[size];
+ for (unsigned int i=0 ; i<size ; i++)
+ d[i] = vals[i];
+ }
+
+
+ SVDMatrix(const SVDMatrix &other)
+ {
+ init();
+ assign(other);
+ }
+
+ SVDMatrix &operator=(const SVDMatrix &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ virtual ~SVDMatrix()
+ {
+ delete[] d;
+ }
+
+ double& operator() (unsigned int row, unsigned int col)
+ {
+ if (row >= rows || col >= cols)
+ return badval;
+ return d[cols*row + col];
+ }
+
+ double operator() (unsigned int row, unsigned int col) const
+ {
+ if (row >= rows || col >= cols)
+ return badval;
+ return d[cols*row + col];
+ }
+
+ unsigned int getRows()
+ {
+ return rows;
+ }
+
+ unsigned int getCols()
+ {
+ return cols;
+ }
+
+ SVDMatrix multiply(const SVDMatrix &other)
+ {
+ if (cols != other.rows)
+ {
+ SVDMatrix dummy;
+ return dummy;
+ }
+ SVDMatrix result(rows, other.cols);
+ for (unsigned int i=0 ; i<rows ; i++)
+ {
+ for (unsigned int j=0 ; j<other.cols ; j++)
+ {
+ double sum = 0.0;
+ for (unsigned int k=0 ; k<cols ; k++)
+ {
+ //sum += a[i][k] * b[k][j];
+ sum += d[i*cols +k] * other(k, j);
+ }
+ result(i, j) = sum;
+ }
+
+ }
+ return result;
+ }
+
+ SVDMatrix transpose()
+ {
+ SVDMatrix result(cols, rows);
+ for (unsigned int i=0 ; i<rows ; i++){
+ for (unsigned int j=0 ; j<cols ; j++){
+ result(j, i) = d[i*cols + j];
+ }
+ }
+ return result;
+ }
+
+private:
+
+
+ virtual void init()
+ {
+ badval = 0.0;
+ d = nullptr;
+ rows = 0;
+ cols = 0;
+ size = 0;
+ }
+
+ void assign(const SVDMatrix &other)
+ {
+ if (d)
+ {
+ delete[] d;
+ d = nullptr;
+ }
+ rows = other.rows;
+ cols = other.cols;
+ size = other.size;
+ badval = other.badval;
+ d = new double[size];
+ for (unsigned int i=0 ; i<size ; i++){
+ d[i] = other.d[i];
+ }
+ }
+
+ double badval;
+
+ double *d;
+ unsigned int rows;
+ unsigned int cols;
+ unsigned int size;
+};
+
+
+
+/**
+ *
+ * ====================================================
+ *
+ * NOTE:
+ * This class is ported almost verbatim from the public domain
+ * JAMA Matrix package. It is modified to handle only 3x3 matrices
+ * and our Geom::Affine affine transform class. We give full
+ * attribution to them, along with many thanks. JAMA can be found at:
+ * http://math.nist.gov/javanumerics/jama
+ *
+ * ====================================================
+ *
+ * Singular Value Decomposition.
+ * <P>
+ * For an m-by-n matrix A with m >= n, the singular value decomposition is
+ * an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
+ * an n-by-n orthogonal matrix V so that A = U*S*V'.
+ * <P>
+ * The singular values, sigma[k] = S[k][k], are ordered so that
+ * sigma[0] >= sigma[1] >= ... >= sigma[n-1].
+ * <P>
+ * The singular value decomposition always exists, so the constructor will
+ * never fail. The matrix condition number and the effective numerical
+ * rank can be computed from this decomposition.
+ */
+class SingularValueDecomposition
+{
+public:
+
+ /** Construct the singular value decomposition
+ @param A Rectangular matrix
+ @return Structure to access U, S and V.
+ */
+
+ SingularValueDecomposition (const SVDMatrix &mat) :
+ A (mat),
+ U (),
+ s (nullptr),
+ s_size (0),
+ V ()
+ {
+ calculate();
+ }
+
+ virtual ~SingularValueDecomposition()
+ {
+ delete[] s;
+ }
+
+ /**
+ * Return the left singular vectors
+ * @return U
+ */
+ SVDMatrix &getU();
+
+ /**
+ * Return the right singular vectors
+ * @return V
+ */
+ SVDMatrix &getV();
+
+ /**
+ * Return the s[index] value
+ */ double getS(unsigned int index);
+
+ /**
+ * Two norm
+ * @return max(S)
+ */
+ double norm2();
+
+ /**
+ * Two norm condition number
+ * @return max(S)/min(S)
+ */
+ double cond();
+
+ /**
+ * Effective numerical matrix rank
+ * @return Number of nonnegligible singular values.
+ */
+ int rank();
+
+private:
+
+ void calculate();
+
+ SVDMatrix A;
+ SVDMatrix U;
+ double *s;
+ unsigned int s_size;
+ SVDMatrix V;
+
+};
+
+
+static double svd_hypot(double a, double b)
+{
+ double r;
+
+ if (fabs(a) > fabs(b))
+ {
+ r = b/a;
+ r = fabs(a) * sqrt(1+r*r);
+ }
+ else if (b != 0)
+ {
+ r = a/b;
+ r = fabs(b) * sqrt(1+r*r);
+ }
+ else
+ {
+ r = 0.0;
+ }
+ return r;
+}
+
+
+
+void SingularValueDecomposition::calculate()
+{
+ // Initialize.
+ int m = A.getRows();
+ int n = A.getCols();
+
+ int nu = (m > n) ? m : n;
+ s_size = (m+1 < n) ? m+1 : n;
+ s = new double[s_size];
+ U = SVDMatrix(m, nu);
+ V = SVDMatrix(n, n);
+ double *e = new double[n];
+ double *work = new double[m];
+ bool wantu = true;
+ bool wantv = true;
+
+ // Reduce A to bidiagonal form, storing the diagonal elements
+ // in s and the super-diagonal elements in e.
+
+ int nct = (m-1<n) ? m-1 : n;
+ int nrtx = (n-2<m) ? n-2 : m;
+ int nrt = (nrtx>0) ? nrtx : 0;
+ for (int k = 0; k < 2; k++) {
+ if (k < nct) {
+
+ // Compute the transformation for the k-th column and
+ // place the k-th diagonal in s[k].
+ // Compute 2-norm of k-th column without under/overflow.
+ s[k] = 0;
+ for (int i = k; i < m; i++) {
+ s[k] = svd_hypot(s[k],A(i, k));
+ }
+ if (s[k] != 0.0) {
+ if (A(k, k) < 0.0) {
+ s[k] = -s[k];
+ }
+ for (int i = k; i < m; i++) {
+ A(i, k) /= s[k];
+ }
+ A(k, k) += 1.0;
+ }
+ s[k] = -s[k];
+ }
+ for (int j = k+1; j < n; j++) {
+ if ((k < nct) & (s[k] != 0.0)) {
+
+ // Apply the transformation.
+
+ double t = 0;
+ for (int i = k; i < m; i++) {
+ t += A(i, k) * A(i, j);
+ }
+ t = -t/A(k, k);
+ for (int i = k; i < m; i++) {
+ A(i, j) += t*A(i, k);
+ }
+ }
+
+ // Place the k-th row of A into e for the
+ // subsequent calculation of the row transformation.
+
+ e[j] = A(k, j);
+ }
+ if (wantu & (k < nct)) {
+
+ // Place the transformation in U for subsequent back
+ // multiplication.
+
+ for (int i = k; i < m; i++) {
+ U(i, k) = A(i, k);
+ }
+ }
+ if (k < nrt) {
+
+ // Compute the k-th row transformation and place the
+ // k-th super-diagonal in e[k].
+ // Compute 2-norm without under/overflow.
+ e[k] = 0;
+ for (int i = k+1; i < n; i++) {
+ e[k] = svd_hypot(e[k],e[i]);
+ }
+ if (e[k] != 0.0) {
+ if (e[k+1] < 0.0) {
+ e[k] = -e[k];
+ }
+ for (int i = k+1; i < n; i++) {
+ e[i] /= e[k];
+ }
+ e[k+1] += 1.0;
+ }
+ e[k] = -e[k];
+ if ((k+1 < m) & (e[k] != 0.0)) {
+
+ // Apply the transformation.
+
+ for (int i = k+1; i < m; i++) {
+ work[i] = 0.0;
+ }
+ for (int j = k+1; j < n; j++) {
+ for (int i = k+1; i < m; i++) {
+ work[i] += e[j]*A(i, j);
+ }
+ }
+ for (int j = k+1; j < n; j++) {
+ double t = -e[j]/e[k+1];
+ for (int i = k+1; i < m; i++) {
+ A(i, j) += t*work[i];
+ }
+ }
+ }
+ if (wantv) {
+
+ // Place the transformation in V for subsequent
+ // back multiplication.
+
+ for (int i = k+1; i < n; i++) {
+ V(i, k) = e[i];
+ }
+ }
+ }
+ }
+
+ // Set up the final bidiagonal matrix or order p.
+
+ int p = (n < m+1) ? n : m+1;
+ if (nct < n) {
+ s[nct] = A(nct, nct);
+ }
+ if (m < p) {
+ s[p-1] = 0.0;
+ }
+ if (nrt+1 < p) {
+ e[nrt] = A(nrt, p-1);
+ }
+ e[p-1] = 0.0;
+
+ // If required, generate U.
+
+ if (wantu) {
+ for (int j = nct; j < nu; j++) {
+ for (int i = 0; i < m; i++) {
+ U(i, j) = 0.0;
+ }
+ U(j, j) = 1.0;
+ }
+ for (int k = nct-1; k >= 0; k--) {
+ if (s[k] != 0.0) {
+ for (int j = k+1; j < nu; j++) {
+ double t = 0;
+ for (int i = k; i < m; i++) {
+ t += U(i, k)*U(i, j);
+ }
+ t = -t/U(k, k);
+ for (int i = k; i < m; i++) {
+ U(i, j) += t*U(i, k);
+ }
+ }
+ for (int i = k; i < m; i++ ) {
+ U(i, k) = -U(i, k);
+ }
+ U(k, k) = 1.0 + U(k, k);
+ for (int i = 0; i < k-1; i++) {
+ U(i, k) = 0.0;
+ }
+ } else {
+ for (int i = 0; i < m; i++) {
+ U(i, k) = 0.0;
+ }
+ U(k, k) = 1.0;
+ }
+ }
+ }
+
+ // If required, generate V.
+
+ if (wantv) {
+ for (int k = n-1; k >= 0; k--) {
+ if ((k < nrt) & (e[k] != 0.0)) {
+ for (int j = k+1; j < nu; j++) {
+ double t = 0;
+ for (int i = k+1; i < n; i++) {
+ t += V(i, k)*V(i, j);
+ }
+ t = -t/V(k+1, k);
+ for (int i = k+1; i < n; i++) {
+ V(i, j) += t*V(i, k);
+ }
+ }
+ }
+ for (int i = 0; i < n; i++) {
+ V(i, k) = 0.0;
+ }
+ V(k, k) = 1.0;
+ }
+ }
+
+ // Main iteration loop for the singular values.
+
+ int pp = p-1;
+ //double eps = pow(2.0,-52.0);
+ //double tiny = pow(2.0,-966.0);
+ //let's just calculate these now
+ //a double can be e ± 308.25, so this is safe
+ double eps = 2.22e-16;
+ double tiny = 1.6e-291;
+ while (p > 0) {
+ int k,kase;
+
+ // Here is where a test for too many iterations would go.
+
+ // This section of the program inspects for
+ // negligible elements in the s and e arrays. On
+ // completion the variables kase and k are set as follows.
+
+ // kase = 1 if s(p) and e[k-1] are negligible and k<p
+ // kase = 2 if s(k) is negligible and k<p
+ // kase = 3 if e[k-1] is negligible, k<p, and
+ // s(k), ..., s(p) are not negligible (qr step).
+ // kase = 4 if e(p-1) is negligible (convergence).
+
+ for (k = p-2; k >= -1; k--) {
+ if (k == -1) {
+ break;
+ }
+ if (fabs(e[k]) <=
+ tiny + eps*(fabs(s[k]) + fabs(s[k+1]))) {
+ e[k] = 0.0;
+ break;
+ }
+ }
+ if (k == p-2) {
+ kase = 4;
+ } else {
+ int ks;
+ for (ks = p-1; ks >= k; ks--) {
+ if (ks == k) {
+ break;
+ }
+ double t = (ks != p ? fabs(e[ks]) : 0.) +
+ (ks != k+1 ? fabs(e[ks-1]) : 0.);
+ if (fabs(s[ks]) <= tiny + eps*t) {
+ s[ks] = 0.0;
+ break;
+ }
+ }
+ if (ks == k) {
+ kase = 3;
+ } else if (ks == p-1) {
+ kase = 1;
+ } else {
+ kase = 2;
+ k = ks;
+ }
+ }
+ k++;
+
+ // Perform the task indicated by kase.
+
+ switch (kase) {
+
+ // Deflate negligible s(p).
+
+ case 1: {
+ double f = e[p-2];
+ e[p-2] = 0.0;
+ for (int j = p-2; j >= k; j--) {
+ double t = svd_hypot(s[j],f);
+ double cs = s[j]/t;
+ double sn = f/t;
+ s[j] = t;
+ if (j != k) {
+ f = -sn*e[j-1];
+ e[j-1] = cs*e[j-1];
+ }
+ if (wantv) {
+ for (int i = 0; i < n; i++) {
+ t = cs*V(i, j) + sn*V(i, p-1);
+ V(i, p-1) = -sn*V(i, j) + cs*V(i, p-1);
+ V(i, j) = t;
+ }
+ }
+ }
+ }
+ break;
+
+ // Split at negligible s(k).
+
+ case 2: {
+ double f = e[k-1];
+ e[k-1] = 0.0;
+ for (int j = k; j < p; j++) {
+ double t = svd_hypot(s[j],f);
+ double cs = s[j]/t;
+ double sn = f/t;
+ s[j] = t;
+ f = -sn*e[j];
+ e[j] = cs*e[j];
+ if (wantu) {
+ for (int i = 0; i < m; i++) {
+ t = cs*U(i, j) + sn*U(i, k-1);
+ U(i, k-1) = -sn*U(i, j) + cs*U(i, k-1);
+ U(i, j) = t;
+ }
+ }
+ }
+ }
+ break;
+
+ // Perform one qr step.
+
+ case 3: {
+
+ // Calculate the shift.
+
+ double scale = fabs(s[p-1]);
+ double d = fabs(s[p-2]);
+ if (d>scale) scale=d;
+ d = fabs(e[p-2]);
+ if (d>scale) scale=d;
+ d = fabs(s[k]);
+ if (d>scale) scale=d;
+ d = fabs(e[k]);
+ if (d>scale) scale=d;
+ double sp = s[p-1]/scale;
+ double spm1 = s[p-2]/scale;
+ double epm1 = e[p-2]/scale;
+ double sk = s[k]/scale;
+ double ek = e[k]/scale;
+ double b = ((spm1 + sp)*(spm1 - sp) + epm1*epm1)/2.0;
+ double c = (sp*epm1)*(sp*epm1);
+ double shift = 0.0;
+ if ((b != 0.0) | (c != 0.0)) {
+ shift = sqrt(b*b + c);
+ if (b < 0.0) {
+ shift = -shift;
+ }
+ shift = c/(b + shift);
+ }
+ double f = (sk + sp)*(sk - sp) + shift;
+ double g = sk*ek;
+
+ // Chase zeros.
+
+ for (int j = k; j < p-1; j++) {
+ double t = svd_hypot(f,g);
+ double cs = f/t;
+ double sn = g/t;
+ if (j != k) {
+ e[j-1] = t;
+ }
+ f = cs*s[j] + sn*e[j];
+ e[j] = cs*e[j] - sn*s[j];
+ g = sn*s[j+1];
+ s[j+1] = cs*s[j+1];
+ if (wantv) {
+ for (int i = 0; i < n; i++) {
+ t = cs*V(i, j) + sn*V(i, j+1);
+ V(i, j+1) = -sn*V(i, j) + cs*V(i, j+1);
+ V(i, j) = t;
+ }
+ }
+ t = svd_hypot(f,g);
+ cs = f/t;
+ sn = g/t;
+ s[j] = t;
+ f = cs*e[j] + sn*s[j+1];
+ s[j+1] = -sn*e[j] + cs*s[j+1];
+ g = sn*e[j+1];
+ e[j+1] = cs*e[j+1];
+ if (wantu && (j < m-1)) {
+ for (int i = 0; i < m; i++) {
+ t = cs*U(i, j) + sn*U(i, j+1);
+ U(i, j+1) = -sn*U(i, j) + cs*U(i, j+1);
+ U(i, j) = t;
+ }
+ }
+ }
+ e[p-2] = f;
+ }
+ break;
+
+ // Convergence.
+
+ case 4: {
+
+ // Make the singular values positive.
+
+ if (s[k] <= 0.0) {
+ s[k] = (s[k] < 0.0 ? -s[k] : 0.0);
+ if (wantv) {
+ for (int i = 0; i <= pp; i++) {
+ V(i, k) = -V(i, k);
+ }
+ }
+ }
+
+ // Order the singular values.
+
+ while (k < pp) {
+ if (s[k] >= s[k+1]) {
+ break;
+ }
+ double t = s[k];
+ s[k] = s[k+1];
+ s[k+1] = t;
+ if (wantv && (k < n-1)) {
+ for (int i = 0; i < n; i++) {
+ t = V(i, k+1); V(i, k+1) = V(i, k); V(i, k) = t;
+ }
+ }
+ if (wantu && (k < m-1)) {
+ for (int i = 0; i < m; i++) {
+ t = U(i, k+1); U(i, k+1) = U(i, k); U(i, k) = t;
+ }
+ }
+ k++;
+ }
+ p--;
+ }
+ break;
+ }
+ }
+
+ delete [] e;
+ delete [] work;
+
+}
+
+
+/**
+ * Return the left singular vectors
+ * @return U
+ */
+SVDMatrix &SingularValueDecomposition::getU()
+{
+ return U;
+}
+
+/**
+ * Return the right singular vectors
+ * @return V
+ */
+
+SVDMatrix &SingularValueDecomposition::getV()
+{
+ return V;
+}
+
+/**
+ * Return the s[0] value
+ */
+double SingularValueDecomposition::getS(unsigned int index)
+{
+ if (index >= s_size)
+ return 0.0;
+ return s[index];
+}
+
+/**
+ * Two norm
+ * @return max(S)
+ */
+double SingularValueDecomposition::norm2()
+{
+ return s[0];
+}
+
+/**
+ * Two norm condition number
+ * @return max(S)/min(S)
+ */
+
+double SingularValueDecomposition::cond()
+{
+ return s[0]/s[2];
+}
+
+/**
+ * Effective numerical matrix rank
+ * @return Number of nonnegligible singular values.
+ */
+int SingularValueDecomposition::rank()
+{
+ double eps = pow(2.0,-52.0);
+ double tol = 3.0*s[0]*eps;
+ int r = 0;
+ for (int i = 0; i < 3; i++)
+ {
+ if (s[i] > tol)
+ r++;
+ }
+ return r;
+}
+
+//########################################################################
+//# E N D C L A S S SingularValueDecomposition
+//########################################################################
+
+
+
+
+
+//#define pxToCm 0.0275
+#define pxToCm 0.03
+
+
+//########################################################################
+//# O U T P U T
+//########################################################################
+
+/**
+ * Get the value of a node/attribute pair
+ */
+static Glib::ustring getAttribute( Inkscape::XML::Node *node, char const *attrName)
+{
+ Glib::ustring val;
+ char const *valstr = node->attribute(attrName);
+ if (valstr)
+ val = valstr;
+ return val;
+}
+
+
+static Glib::ustring formatTransform(Geom::Affine &tf)
+{
+ Glib::ustring str;
+ if (!tf.isIdentity())
+ {
+ StringOutputStream outs;
+ OutputStreamWriter out(outs);
+ out.printf("matrix(%.3f %.3f %.3f %.3f %.3f %.3f)",
+ tf[0], tf[1], tf[2], tf[3], tf[4], tf[5]);
+ str = outs.getString();
+ }
+ return str;
+}
+
+
+/**
+ * Get the general transform from SVG pixels to
+ * ODF cm
+ */
+static Geom::Affine getODFTransform(const SPItem *item)
+{
+ //### Get SVG-to-ODF transform
+ Geom::Affine tf (item->i2doc_affine());
+ tf = tf * Geom::Affine(Geom::Scale(pxToCm));
+ return tf;
+}
+
+
+/**
+ * Get the bounding box of an item, as mapped onto
+ * an ODF document, in cm.
+ */
+static Geom::OptRect getODFBoundingBox(const SPItem *item)
+{
+ // TODO: geometric or visual?
+ Geom::OptRect bbox = item->documentVisualBounds();
+ if (bbox) {
+ *bbox *= Geom::Affine(Geom::Scale(pxToCm));
+ }
+ return bbox;
+}
+
+
+/**
+ * Get the transform for an item, including parents, but without
+ * root viewBox transformation.
+ */
+static Geom::Affine getODFItemTransform(const SPItem *item)
+{
+ Geom::Affine itemTransform (item->i2doc_affine() *
+ item->document->getRoot()->c2p.inverse());
+ return itemTransform;
+}
+
+
+/**
+ * Get some fun facts from the transform
+ */
+static void analyzeTransform(Geom::Affine &tf,
+ double &rotate, double &/*xskew*/, double &/*yskew*/,
+ double &xscale, double &yscale)
+{
+ SVDMatrix mat(2, 2);
+ mat(0, 0) = tf[0];
+ mat(0, 1) = tf[1];
+ mat(1, 0) = tf[2];
+ mat(1, 1) = tf[3];
+
+ SingularValueDecomposition svd(mat);
+
+ SVDMatrix U = svd.getU();
+ SVDMatrix V = svd.getV();
+ SVDMatrix Vt = V.transpose();
+ SVDMatrix UVt = U.multiply(Vt);
+ double s0 = svd.getS(0);
+ double s1 = svd.getS(1);
+ xscale = s0;
+ yscale = s1;
+ rotate = UVt(0,0);
+}
+
+static void gatherText(Inkscape::XML::Node *node, Glib::ustring &buf)
+{
+ if (node->type() == Inkscape::XML::NodeType::TEXT_NODE)
+ {
+ char *s = (char *)node->content();
+ if (s)
+ buf.append(s);
+ }
+
+ for (Inkscape::XML::Node *child = node->firstChild() ;
+ child != nullptr; child = child->next())
+ {
+ gatherText(child, buf);
+ }
+
+}
+
+
+/**
+ * FIRST PASS.
+ * Method descends into the repr tree, converting image, style, and gradient info
+ * into forms compatible in ODF.
+ */
+void OdfOutput::preprocess(ZipFile &zf, SPDocument *doc, Inkscape::XML::Node *node)
+{
+ Glib::ustring nodeName = node->name();
+ Glib::ustring id = getAttribute(node, "id");
+
+ //### First, check for metadata
+ if (nodeName == "metadata" || nodeName == "svg:metadata")
+ {
+ Inkscape::XML::Node *mchild = node->firstChild() ;
+ if (!mchild || strcmp(mchild->name(), "rdf:RDF"))
+ return;
+ Inkscape::XML::Node *rchild = mchild->firstChild() ;
+ if (!rchild || strcmp(rchild->name(), "cc:Work"))
+ return;
+ for (Inkscape::XML::Node *cchild = rchild->firstChild() ;
+ cchild ; cchild = cchild->next())
+ {
+ Glib::ustring ccName = cchild->name();
+ Glib::ustring ccVal;
+ gatherText(cchild, ccVal);
+ //g_message("ccName: %s ccVal:%s", ccName.c_str(), ccVal.c_str());
+ metadata[ccName] = ccVal;
+ }
+ return;
+ }
+
+ //Now consider items.
+ SPObject *reprobj = doc->getObjectByRepr(node);
+ if (!reprobj)
+ {
+ return;
+ }
+ if (!SP_IS_ITEM(reprobj))
+ {
+ return;
+ }
+
+ if (nodeName == "image" || nodeName == "svg:image") {
+ Glib::ustring href = getAttribute(node, "xlink:href");
+ if (href.size() > 0 && imageTable.count(href) == 0) {
+ try {
+ auto uri = Inkscape::URI(href.c_str(), docBaseUri.c_str());
+ auto mimetype = uri.getMimeType();
+
+ if (mimetype.substr(0, 6) != "image/") {
+ return;
+ }
+
+ auto ext = mimetype.substr(6);
+ auto newName = Glib::ustring("Pictures/image") + std::to_string(imageTable.size()) + "." + ext;
+
+ imageTable[href] = newName;
+
+ auto ze = zf.newEntry(newName.raw(), "");
+ ze->setUncompressedData(uri.getContents());
+ ze->finish();
+ } catch (...) {
+ g_warning("Could not handle URI '%.100s'", href.c_str());
+ }
+ }
+ }
+
+ for (Inkscape::XML::Node *child = node->firstChild() ;
+ child ; child = child->next())
+ preprocess(zf, doc, child);
+}
+
+
+/**
+ * Writes the manifest. Currently it only changes according to the
+ * file names of images packed into the zip file.
+ */
+bool OdfOutput::writeManifest(ZipFile &zf)
+{
+ BufferOutputStream bouts;
+ OutputStreamWriter outs(bouts);
+
+ time_t tim;
+ time(&tim);
+
+ outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ outs.writeString("<!DOCTYPE manifest:manifest PUBLIC \"-//OpenOffice.org//DTD Manifest 1.0//EN\" \"Manifest.dtd\">\n");
+ outs.writeString("\n");
+ outs.writeString("\n");
+ outs.writeString("<!--\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString(" file: manifest.xml\n");
+ outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
+ outs.writeString(" http://www.inkscape.org\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString("-->\n");
+ outs.writeString("\n");
+ outs.writeString("\n");
+ outs.writeString("<manifest:manifest xmlns:manifest=\"urn:oasis:names:tc:opendocument:xmlns:manifest:1.0\">\n");
+ outs.writeString(" <manifest:file-entry manifest:media-type=\"application/vnd.oasis.opendocument.graphics\" manifest:full-path=\"/\"/>\n");
+ outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"content.xml\"/>\n");
+ outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"styles.xml\"/>\n");
+ outs.writeString(" <manifest:file-entry manifest:media-type=\"text/xml\" manifest:full-path=\"meta.xml\"/>\n");
+ outs.writeString(" <!--List our images here-->\n");
+ std::map<Glib::ustring, Glib::ustring>::iterator iter;
+ for (iter = imageTable.begin() ; iter!=imageTable.end() ; ++iter)
+ {
+ Glib::ustring newName = iter->second;
+
+ // note: mime subtype was added as file extension in OdfOutput::preprocess
+ Glib::ustring mimesubtype = Inkscape::IO::get_file_extension(newName);
+
+ outs.printf(" <manifest:file-entry manifest:media-type=\"");
+ outs.printf("image/");
+ outs.printf("%s", mimesubtype.c_str());
+ outs.printf("\" manifest:full-path=\"");
+ outs.writeString(newName.c_str());
+ outs.printf("\"/>\n");
+ }
+ outs.printf("</manifest:manifest>\n");
+
+ outs.close();
+
+ //Make our entry
+ ZipEntry *ze = zf.newEntry("META-INF/manifest.xml", "ODF file manifest");
+ ze->setUncompressedData(bouts.getBuffer());
+ ze->finish();
+
+ return true;
+}
+
+
+/**
+ * This writes the document meta information to meta.xml
+ */
+bool OdfOutput::writeMeta(ZipFile &zf)
+{
+ BufferOutputStream bouts;
+ OutputStreamWriter outs(bouts);
+
+ time_t tim;
+ time(&tim);
+
+ std::map<Glib::ustring, Glib::ustring>::iterator iter;
+ Glib::ustring InkscapeVersion = Glib::ustring("Inkscape.org - ") + Inkscape::version_string;
+ Glib::ustring creator = InkscapeVersion;
+ iter = metadata.find("dc:creator");
+ if (iter != metadata.end())
+ {
+ creator = iter->second;
+ }
+
+ Glib::ustring date;
+ Glib::ustring moddate;
+ char buf [80];
+ time_t rawtime;
+ struct tm * timeinfo;
+ time (&rawtime);
+ timeinfo = localtime (&rawtime);
+ strftime (buf,80,"%Y-%m-%d %H:%M:%S",timeinfo);
+ moddate = Glib::ustring(buf);
+
+ iter = metadata.find("dc:date");
+ if (iter != metadata.end())
+ {
+ date = iter->second;
+ }
+ else
+ {
+ date = moddate;
+ }
+
+ outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ outs.writeString("\n");
+ outs.writeString("<!--\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString(" file: meta.xml\n");
+ outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
+ outs.writeString(" http://www.inkscape.org\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString("-->\n");
+ outs.writeString("\n");
+ outs.writeString("<office:document-meta\n");
+ outs.writeString("xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
+ outs.writeString("xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
+ outs.writeString("xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
+ outs.writeString("xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
+ outs.writeString("xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
+ outs.writeString("xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
+ outs.writeString("xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
+ outs.writeString("xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
+ outs.writeString("office:version=\"1.0\">\n");
+ outs.writeString("<office:meta>\n");
+ Glib::ustring tmp = Glib::ustring::compose(" <meta:generator>%1</meta:generator>\n", InkscapeVersion);
+ tmp += Glib::ustring::compose(" <meta:initial-creator>%1</meta:initial-creator>\n", creator);
+ tmp += Glib::ustring::compose(" <meta:creation-date>%1</meta:creation-date>\n", date);
+ tmp += Glib::ustring::compose(" <dc:date>%1</dc:date>\n", moddate);
+ outs.writeUString(tmp);
+ for (iter = metadata.begin() ; iter != metadata.end() ; ++iter)
+ {
+ Glib::ustring name = iter->first;
+ Glib::ustring value = iter->second;
+ if (!name.empty() && !value.empty())
+ {
+ tmp = Glib::ustring::compose(" <%1>%2</%3>\n", name, value, name);
+ outs.writeUString(tmp);
+ }
+ }
+ // outs.writeString(" <meta:editing-cycles>2</meta:editing-cycles>\n");
+ // outs.writeString(" <meta:editing-duration>PT56S</meta:editing-duration>\n");
+ // outs.writeString(" <meta:user-defined meta:name=\"Info 1\"/>\n");
+ // outs.writeString(" <meta:user-defined meta:name=\"Info 2\"/>\n");
+ // outs.writeString(" <meta:user-defined meta:name=\"Info 3\"/>\n");
+ // outs.writeString(" <meta:user-defined meta:name=\"Info 4\"/>\n");
+ // outs.writeString(" <meta:document-statistic meta:object-count=\"2\"/>\n");
+ outs.writeString("</office:meta>\n");
+ outs.writeString("</office:document-meta>\n");
+ outs.close();
+
+ //Make our entry
+ ZipEntry *ze = zf.newEntry("meta.xml", "ODF info file");
+ ze->setUncompressedData(bouts.getBuffer());
+ ze->finish();
+
+ return true;
+}
+
+
+/**
+ * Writes an SVG path as an ODF <draw:path> and returns the number of points written
+ */
+static int
+writePath(Writer &outs, Geom::PathVector const &pathv,
+ Geom::Affine const &tf, double xoff, double yoff)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ int nrPoints = 0;
+
+ // convert the path to only lineto's and cubic curveto's:
+ Geom::PathVector pv = pathv_to_linear_and_cubic_beziers(pathv * tf * Geom::Translate(xoff, yoff) * Geom::Scale(1000.));
+
+ for (const auto & pit : pv) {
+
+ double destx = pit.initialPoint()[X];
+ double desty = pit.initialPoint()[Y];
+ if (fabs(destx)<1.0) destx = 0.0; // Why is this needed? Shouldn't we just round all numbers then?
+ if (fabs(desty)<1.0) desty = 0.0;
+ outs.printf("M %.3f %.3f ", destx, desty);
+ nrPoints++;
+
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_closed(); ++cit) {
+
+ if( is_straight_curve(*cit) )
+ {
+ double destx = cit->finalPoint()[X];
+ double desty = cit->finalPoint()[Y];
+ if (fabs(destx)<1.0) destx = 0.0; // Why is this needed? Shouldn't we just round all numbers then?
+ if (fabs(desty)<1.0) desty = 0.0;
+ outs.printf("L %.3f %.3f ", destx, desty);
+ }
+ else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit)) {
+ std::vector<Geom::Point> points = cubic->controlPoints();
+ for (unsigned i = 1; i <= 3; i++) {
+ if (fabs(points[i][X])<1.0) points[i][X] = 0.0; // Why is this needed? Shouldn't we just round all numbers then?
+ if (fabs(points[i][Y])<1.0) points[i][Y] = 0.0;
+ }
+ outs.printf("C %.3f %.3f %.3f %.3f %.3f %.3f ", points[1][X],points[1][Y], points[2][X],points[2][Y], points[3][X],points[3][Y]);
+ }
+ else {
+ g_error ("logical error, because pathv_to_linear_and_cubic_beziers was used");
+ }
+
+ nrPoints++;
+ }
+
+ if (pit.closed()) {
+ outs.printf("Z");
+ }
+ }
+
+ return nrPoints;
+}
+
+bool OdfOutput::processStyle(SPItem *item, const Glib::ustring &id, const Glib::ustring &gradientNameFill, const Glib::ustring &gradientNameStroke, Glib::ustring& output)
+{
+ output.clear();
+ if (!item)
+ {
+ return false;
+ }
+
+ SPStyle *style = item->style;
+ if (!style)
+ {
+ return false;
+ }
+
+ StyleInfo si;
+
+ // FILL
+ if (style->fill.isColor())
+ {
+ guint32 fillCol = style->fill.value.color.toRGBA32( 0 );
+ char buf[16];
+ int r = (fillCol >> 24) & 0xff;
+ int g = (fillCol >> 16) & 0xff;
+ int b = (fillCol >> 8) & 0xff;
+ snprintf(buf, 15, "#%02x%02x%02x", r, g, b);
+ si.fillColor = buf;
+ si.fill = "solid";
+ double opacityPercent = 100.0 *
+ (SP_SCALE24_TO_FLOAT(style->fill_opacity.value));
+ snprintf(buf, 15, "%.3f%%", opacityPercent);
+ si.fillOpacity = buf;
+ }
+ else if (style->fill.isPaintserver())
+ {
+ SPGradient *gradient = SP_GRADIENT(SP_STYLE_FILL_SERVER(style));
+ if (gradient)
+ {
+ si.fill = "gradient";
+ }
+ }
+
+ // STROKE
+ if (style->stroke.isColor())
+ {
+ guint32 strokeCol = style->stroke.value.color.toRGBA32( 0 );
+ char buf[16];
+ int r = (strokeCol >> 24) & 0xff;
+ int g = (strokeCol >> 16) & 0xff;
+ int b = (strokeCol >> 8) & 0xff;
+ snprintf(buf, 15, "#%02x%02x%02x", r, g, b);
+ si.strokeColor = buf;
+ snprintf(buf, 15, "%.3fpt", style->stroke_width.value);
+ si.strokeWidth = buf;
+ si.stroke = "solid";
+ double opacityPercent = 100.0 *
+ (SP_SCALE24_TO_FLOAT(style->stroke_opacity.value));
+ snprintf(buf, 15, "%.3f%%", opacityPercent);
+ si.strokeOpacity = buf;
+ }
+ else if (style->stroke.isPaintserver())
+ {
+ SPGradient *gradient = SP_GRADIENT(SP_STYLE_STROKE_SERVER(style));
+ if (gradient)
+ {
+ si.stroke = "gradient";
+ }
+ }
+
+ //Look for existing identical style;
+ bool styleMatch = false;
+ std::vector<StyleInfo>::iterator iter;
+ for (iter=styleTable.begin() ; iter!=styleTable.end() ; ++iter)
+ {
+ if (si.equals(*iter))
+ {
+ //map to existing styleTable entry
+ Glib::ustring styleName = iter->name;
+ styleLookupTable[id] = styleName;
+ styleMatch = true;
+ break;
+ }
+ }
+
+ // Don't need a new style
+ if (styleMatch)
+ {
+ return false;
+ }
+
+ Glib::ustring styleName = Glib::ustring::compose("style%1", styleTable.size());
+ si.name = styleName;
+ styleTable.push_back(si);
+ styleLookupTable[id] = styleName;
+
+ output = Glib::ustring::compose ("<style:style style:name=\"%1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n", si.name);
+ output += "<style:graphic-properties";
+ if (si.fill == "gradient")
+ {
+ output += Glib::ustring::compose (" draw:fill=\"gradient\" draw:fill-gradient-name=\"%1\"", gradientNameFill);
+ }
+ else
+ {
+ output += Glib::ustring(" draw:fill=\"") + si.fill + "\"";
+ if(si.fill != "none")
+ {
+ output += Glib::ustring::compose(" draw:fill-color=\"%1\"", si.fillColor);
+ }
+ }
+ if (si.stroke == "gradient")
+ {
+ //does not seem to be supported by Open Office.org
+ output += Glib::ustring::compose (" draw:stroke=\"gradient\" draw:stroke-gradient-name=\"%1\"", gradientNameStroke);
+ }
+ else
+ {
+ output += Glib::ustring(" draw:stroke=\"") + si.stroke + "\"";
+ if (si.stroke != "none")
+ {
+ output += Glib::ustring::compose (" svg:stroke-width=\"%1\" svg:stroke-color=\"%2\" ", si.strokeWidth, si.strokeColor);
+ }
+ }
+ output += "/>\n</style:style>\n";
+
+ return true;
+}
+
+bool OdfOutput::processGradient(SPItem *item,
+ const Glib::ustring &id, Geom::Affine &/*tf*/,
+ Glib::ustring& gradientName, Glib::ustring& output, bool checkFillGradient)
+{
+ output.clear();
+ if (!item)
+ {
+ return false;
+ }
+
+ SPStyle *style = item->style;
+ if (!style)
+ {
+ return false;
+ }
+
+ if ((checkFillGradient? (!style->fill.isPaintserver()) : (!style->stroke.isPaintserver())))
+ {
+ return false;
+ }
+
+ //## Gradient
+ SPGradient *gradient = SP_GRADIENT((checkFillGradient?(SP_STYLE_FILL_SERVER(style)) :(SP_STYLE_STROKE_SERVER(style))));
+
+ if (gradient == nullptr)
+ {
+ return false;
+ }
+ GradientInfo gi;
+ SPGradient *grvec = gradient->getVector(FALSE);
+ for (SPStop *stop = grvec->getFirstStop();
+ stop ; stop = stop->getNextStop())
+ {
+ unsigned long rgba = stop->get_rgba32();
+ unsigned long rgb = (rgba >> 8) & 0xffffff;
+ double opacity = (static_cast<double>(rgba & 0xff)) / 256.0;
+ GradientStop gs(rgb, opacity);
+ gi.stops.push_back(gs);
+ }
+
+ Glib::ustring gradientName2;
+ if (SP_IS_LINEARGRADIENT(gradient))
+ {
+ gi.style = "linear";
+ SPLinearGradient *linGrad = SP_LINEARGRADIENT(gradient);
+ gi.x1 = linGrad->x1.value;
+ gi.y1 = linGrad->y1.value;
+ gi.x2 = linGrad->x2.value;
+ gi.y2 = linGrad->y2.value;
+ gradientName2 = Glib::ustring::compose("ImportedLinearGradient%1", gradientTable.size());
+ }
+ else if (SP_IS_RADIALGRADIENT(gradient))
+ {
+ gi.style = "radial";
+ SPRadialGradient *radGrad = SP_RADIALGRADIENT(gradient);
+ Geom::OptRect bbox = item->documentVisualBounds();
+ gi.cx = (radGrad->cx.value-bbox->left())/bbox->width();
+ gi.cy = (radGrad->cy.value-bbox->top())/bbox->height();
+ gradientName2 = Glib::ustring::compose("ImportedRadialGradient%1", gradientTable.size());
+ }
+ else
+ {
+ g_warning("not a supported gradient type");
+ return false;
+ }
+
+ //Look for existing identical style;
+ bool gradientMatch = false;
+ std::vector<GradientInfo>::iterator iter;
+ for (iter=gradientTable.begin() ; iter!=gradientTable.end() ; ++iter)
+ {
+ if (gi.equals(*iter))
+ {
+ //map to existing gradientTable entry
+ gradientName = iter->name;
+ gradientLookupTable[id] = gradientName;
+ gradientMatch = true;
+ break;
+ }
+ }
+
+ if (gradientMatch)
+ {
+ return true;
+ }
+
+ // No match, let us write a new entry
+ gradientName = gradientName2;
+ gi.name = gradientName;
+ gradientTable.push_back(gi);
+ gradientLookupTable[id] = gradientName;
+
+ // int gradientCount = gradientTable.size();
+ char buf[128];
+ if (gi.style == "linear")
+ {
+ /*
+ ===================================================================
+ LINEAR gradient. We need something that looks like this:
+ <draw:gradient draw:name="Gradient_20_7"
+ draw:display-name="Gradient 7"
+ draw:style="linear"
+ draw:start-color="#008080" draw:end-color="#993366"
+ draw:start-intensity="100%" draw:end-intensity="100%"
+ draw:angle="150" draw:border="0%"/>
+ ===================================================================
+ */
+ if (gi.stops.size() < 2)
+ {
+ g_warning("Need at least 2 stops for a linear gradient");
+ return false;
+ }
+ output += Glib::ustring::compose("<draw:gradient draw:name=\"%1\"", gi.name);
+ output += Glib::ustring::compose(" draw:display-name=\"%1\"", gi.name);
+ output += " draw:style=\"linear\"";
+ snprintf(buf, 127, " draw:start-color=\"#%06lx\" draw:end-color=\"#%06lx\"", gi.stops[0].rgb, gi.stops[1].rgb);
+ output += buf;
+ //TODO: apply maths, to define begin of gradient, taking gradient begin and end, as well as object boundary into account
+ double angle = (gi.y2-gi.y1);
+ angle = (angle != 0.) ? (atan((gi.x2-gi.x1)/(gi.y2-gi.y1))* 180. / M_PI) : 90;
+ angle = (angle < 0)?(180+angle):angle;
+ angle = angle * 10; //why do we need this: precision?????????????
+ output += Glib::ustring::compose(" draw:start-intensity=\"%1\" draw:end-intensity=\"%2\" draw:angle=\"%3\"/>\n",
+ gi.stops[0].opacity * 100.0, gi.stops[1].opacity * 100.0, angle);// draw:border=\"0%%\"
+ }
+ else if (gi.style == "radial")
+ {
+ /*
+ ===================================================================
+ RADIAL gradient. We need something that looks like this:
+ <!-- radial gradient, light gray to white, centered, 0% border -->
+ <draw:gradient draw:name="radial_20_borderless"
+ draw:display-name="radial borderless"
+ draw:style="radial"
+ draw:cx="50%" draw:cy="50%"
+ draw:start-color="#999999" draw:end-color="#ffffff"
+ draw:border="0%"/>
+ ===================================================================
+ */
+ if (gi.stops.size() < 2)
+ {
+ g_warning("Need at least 2 stops for a radial gradient");
+ return false;
+ }
+ output += Glib::ustring::compose("<draw:gradient draw:name=\"%1\" draw:display-name=\"%1\" ", gi.name);
+ snprintf(buf, 127, "draw:cx=\"%05.3f\" draw:cy=\"%05.3f\" ", gi.cx*100, gi.cy*100);
+ output += Glib::ustring("draw:style=\"radial\" ") + buf;
+ snprintf(buf, 127, "draw:start-color=\"#%06lx\" draw:end-color=\"#%06lx\" ", gi.stops[0].rgb, gi.stops[1].rgb);
+ output += buf;
+ snprintf(buf, 127, "draw:start-intensity=\"%f%%\" draw:end-intensity=\"%f%%\" ", gi.stops[0].opacity*100.0, gi.stops[1].opacity*100.0);
+ output += buf;
+ output += "/>\n";//draw:border=\"0%\"
+ }
+ else
+ {
+ g_warning("unsupported gradient style '%s'", gi.style.c_str());
+ return false;
+ }
+ return true;
+}
+
+
+/**
+ * SECOND PASS.
+ * This is the main SPObject tree output to ODF.
+ */
+bool OdfOutput::writeTree(Writer &couts, Writer &souts,
+ SPDocument *doc,
+ Inkscape::XML::Node *node)
+{
+ //# Get the SPItem, if applicable
+ SPObject *reprobj = doc->getObjectByRepr(node);
+ if (!reprobj)
+ {
+ return true;
+ }
+ if (!SP_IS_ITEM(reprobj))
+ {
+ return true;
+ }
+ SPItem *item = SP_ITEM(reprobj);
+
+ Glib::ustring nodeName = node->name();
+ Glib::ustring id = getAttribute(node, "id");
+ Geom::Affine tf = getODFTransform(item);//Get SVG-to-ODF transform
+ Geom::OptRect bbox = getODFBoundingBox(item);//Get ODF bounding box params for item
+ if (!bbox) {
+ return true;
+ }
+
+ double bbox_x = bbox->min()[Geom::X];
+ double bbox_y = bbox->min()[Geom::Y];
+ double bbox_width = (*bbox)[Geom::X].extent();
+ double bbox_height = (*bbox)[Geom::Y].extent();
+
+ double rotate;
+ double xskew;
+ double yskew;
+ double xscale;
+ double yscale;
+ analyzeTransform(tf, rotate, xskew, yskew, xscale, yscale);
+
+ //# Do our stuff
+
+ 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, doc, child))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else if (nodeName == "g" || nodeName == "svg:g")
+ {
+ if (!id.empty())
+ {
+ couts.printf("<draw:g id=\"%s\">\n", id.c_str());
+ }
+ else
+ {
+ couts.printf("<draw:g>\n");
+ }
+ //# Iterate through the children
+ for (Inkscape::XML::Node *child = node->firstChild() ;
+ child ; child = child->next())
+ {
+ if (!writeTree(couts, souts, doc, child))
+ {
+ return false;
+ }
+ }
+ if (!id.empty())
+ {
+ couts.printf("</draw:g> <!-- id=\"%s\" -->\n", id.c_str());
+ }
+ else
+ {
+ couts.printf("</draw:g>\n");
+ }
+ return true;
+ }
+
+ //# GRADIENT
+ Glib::ustring gradientNameFill;
+ Glib::ustring gradientNameStroke;
+ Glib::ustring outputFill;
+ Glib::ustring outputStroke;
+ Glib::ustring outputStyle;
+
+ processGradient(item, id, tf, gradientNameFill, outputFill, true);
+ processGradient(item, id, tf, gradientNameStroke, outputStroke, false);
+ souts.writeUString(outputFill);
+ souts.writeUString(outputStroke);
+
+ //# STYLE
+ processStyle(item, id, gradientNameFill, gradientNameStroke, outputStyle);
+ souts.writeUString(outputStyle);
+
+ //# ITEM DATA
+ if (nodeName == "image" || nodeName == "svg:image")
+ {
+ if (!SP_IS_IMAGE(item))
+ {
+ g_warning("<image> is not an SPImage.");
+ return false;
+ }
+
+ SPImage *img = SP_IMAGE(item);
+ double ix = img->x.value;
+ double iy = img->y.value;
+ double iwidth = img->width.value;
+ double iheight = img->height.value;
+
+ Geom::Point ibbox_min = Geom::Point(ix, iy) * tf;
+ ix = ibbox_min.x();
+ iy = ibbox_min.y();
+ iwidth = xscale * iwidth;
+ iheight = yscale * iheight;
+
+ Geom::Affine itemTransform = getODFItemTransform(item);
+
+ Glib::ustring itemTransformString = formatTransform(itemTransform);
+
+ Glib::ustring href = getAttribute(node, "xlink:href");
+ std::map<Glib::ustring, Glib::ustring>::iterator iter = imageTable.find(href);
+ if (iter == imageTable.end())
+ {
+ g_warning("image '%s' not in table", href.c_str());
+ return false;
+ }
+ Glib::ustring newName = iter->second;
+
+ couts.printf("<draw:frame ");
+ if (!id.empty())
+ {
+ couts.printf("id=\"%s\" ", id.c_str());
+ }
+ couts.printf("draw:style-name=\"gr1\" draw:text-style-name=\"P1\" draw:layer=\"layout\" ");
+ //no x or y. make them the translate transform, last one
+ couts.printf("svg:width=\"%.3fcm\" svg:height=\"%.3fcm\" ",
+ iwidth, iheight);
+ if (!itemTransformString.empty())
+ {
+ couts.printf("draw:transform=\"%s translate(%.3fcm, %.3fcm)\" ",
+ itemTransformString.c_str(), ix, iy);
+ }
+ else
+ {
+ couts.printf("draw:transform=\"translate(%.3fcm, %.3fcm)\" ", ix, iy);
+ }
+
+ couts.writeString(">\n");
+ couts.printf(" <draw:image xlink:href=\"%s\" xlink:type=\"simple\"\n",
+ newName.c_str());
+ couts.writeString(" xlink:show=\"embed\" xlink:actuate=\"onLoad\">\n");
+ couts.writeString(" <text:p/>\n");
+ couts.writeString(" </draw:image>\n");
+ couts.writeString("</draw:frame>\n");
+ return true;
+ }
+
+ std::unique_ptr<SPCurve> curve;
+
+ if (auto shape = dynamic_cast<SPShape const *>(item)) {
+ curve = SPCurve::copy(shape->curve());
+ } else if (SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item)) {
+ curve = te_get_layout(item)->convertToCurves();
+ }
+
+ if (curve)
+ {
+ //### Default <path> output
+ couts.writeString("<draw:path ");
+ if (!id.empty())
+ {
+ couts.printf("id=\"%s\" ", id.c_str());
+ }
+
+ std::map<Glib::ustring, Glib::ustring>::iterator siter;
+ siter = styleLookupTable.find(id);
+ if (siter != styleLookupTable.end())
+ {
+ Glib::ustring styleName = siter->second;
+ couts.printf("draw:style-name=\"%s\" ", styleName.c_str());
+ }
+
+ couts.printf("draw:layer=\"layout\" svg:x=\"%.3fcm\" svg:y=\"%.3fcm\" ",
+ bbox_x, bbox_y);
+ couts.printf("svg:width=\"%.3fcm\" svg:height=\"%.3fcm\" ",
+ bbox_width, bbox_height);
+ couts.printf("svg:viewBox=\"0.0 0.0 %.3f %.3f\"",
+ bbox_width * 1000.0, bbox_height * 1000.0);
+
+ couts.printf(" svg:d=\"");
+ int nrPoints = writePath(couts, curve->get_pathvector(),
+ tf, bbox_x, bbox_y);
+ couts.writeString("\"");
+
+ couts.writeString(">\n");
+ couts.printf(" <!-- %d nodes -->\n", nrPoints);
+ couts.writeString("</draw:path>\n\n");
+ }
+
+ return true;
+}
+
+
+/**
+ * Write the header for the content.xml file
+ */
+bool OdfOutput::writeStyleHeader(Writer &outs)
+{
+ time_t tim;
+ time(&tim);
+
+ outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ outs.writeString("\n");
+ outs.writeString("<!--\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString(" file: styles.xml\n");
+ outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
+ outs.writeString(" http://www.inkscape.org\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString("-->\n");
+ outs.writeString("\n");
+ outs.writeString("<office:document-styles\n");
+ outs.writeString(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
+ outs.writeString(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n");
+ outs.writeString(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n");
+ outs.writeString(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n");
+ outs.writeString(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n");
+ outs.writeString(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n");
+ outs.writeString(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
+ outs.writeString(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
+ outs.writeString(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
+ outs.writeString(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n");
+ outs.writeString(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
+ outs.writeString(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n");
+ outs.writeString(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n");
+ outs.writeString(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n");
+ outs.writeString(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n");
+ outs.writeString(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n");
+ outs.writeString(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n");
+ outs.writeString(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
+ outs.writeString(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n");
+ outs.writeString(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n");
+ outs.writeString(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n");
+ outs.writeString(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n");
+ outs.writeString(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n");
+ outs.writeString(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
+ outs.writeString(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
+ outs.writeString(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
+ outs.writeString(" office:version=\"1.0\">\n");
+ outs.writeString("\n");
+ outs.writeString("<!--\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString(" S T Y L E S\n");
+ outs.writeString(" Style entries have been pulled from the svg style and\n");
+ outs.writeString(" representation attributes in the SVG tree. The tree elements\n");
+ outs.writeString(" then refer to them by name, in the ODF manner\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString("-->\n");
+ outs.writeString("\n");
+ outs.writeString("<office:styles>\n");
+ outs.writeString("\n");
+
+ return true;
+}
+
+
+/**
+ * Write the footer for the style.xml file
+ */
+bool OdfOutput::writeStyleFooter(Writer &outs)
+{
+ outs.writeString("\n");
+ outs.writeString("</office:styles>\n");
+ outs.writeString("\n");
+ outs.writeString("<office:automatic-styles>\n");
+ outs.writeString("<!-- ####### 'Standard' styles ####### -->\n");
+ outs.writeString("<style:style style:name=\"dp1\" style:family=\"drawing-page\"/>\n");
+ outs.writeString("<style:style style:name=\"standard\" style:family=\"graphic\">\n");
+
+///TODO: add default document style here
+
+ outs.writeString("</style:style>\n");
+ outs.writeString("<style:style style:name=\"gr1\" style:family=\"graphic\" style:parent-style-name=\"standard\">\n");
+ outs.writeString(" <style:graphic-properties draw:stroke=\"none\" draw:fill=\"none\"\n");
+ outs.writeString(" draw:textarea-horizontal-align=\"center\"\n");
+ outs.writeString(" draw:textarea-vertical-align=\"middle\" draw:color-mode=\"standard\"\n");
+ outs.writeString(" draw:luminance=\"0%\" draw:contrast=\"0%\" draw:gamma=\"100%\" draw:red=\"0%\"\n");
+ outs.writeString(" draw:green=\"0%\" draw:blue=\"0%\" fo:clip=\"rect(0cm 0cm 0cm 0cm)\"\n");
+ outs.writeString(" draw:image-opacity=\"100%\" style:mirror=\"none\"/>\n");
+ outs.writeString("</style:style>\n");
+ outs.writeString("<style:style style:name=\"P1\" style:family=\"paragraph\">\n");
+ outs.writeString(" <style:paragraph-properties fo:text-align=\"center\"/>\n");
+ outs.writeString("</style:style>\n");
+ outs.writeString("</office:automatic-styles>\n");
+ outs.writeString("\n");
+ outs.writeString("<office:master-styles>\n");
+ outs.writeString("<draw:layer-set>\n");
+ outs.writeString(" <draw:layer draw:name=\"layout\"/>\n");
+ outs.writeString(" <draw:layer draw:name=\"background\"/>\n");
+ outs.writeString(" <draw:layer draw:name=\"backgroundobjects\"/>\n");
+ outs.writeString(" <draw:layer draw:name=\"controls\"/>\n");
+ outs.writeString(" <draw:layer draw:name=\"measurelines\"/>\n");
+ outs.writeString("</draw:layer-set>\n");
+ outs.writeString("\n");
+ outs.writeString("<style:master-page style:name=\"Default\"\n");
+ outs.writeString(" style:page-master-name=\"PM1\" draw:style-name=\"dp1\"/>\n");
+ outs.writeString("</office:master-styles>\n");
+ outs.writeString("\n");
+ outs.writeString("</office:document-styles>\n");
+
+ return true;
+}
+
+
+/**
+ * Write the header for the content.xml file
+ */
+bool OdfOutput::writeContentHeader(Writer &outs)
+{
+ time_t tim;
+ time(&tim);
+
+ outs.writeString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ outs.writeString("\n");
+ outs.writeString("<!--\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString(" file: content.xml\n");
+ outs.printf (" Generated by Inkscape: %s", ctime(&tim)); //ctime has its own <cr>
+ outs.writeString(" http://www.inkscape.org\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString("-->\n");
+ outs.writeString("\n");
+ outs.writeString("<office:document-content\n");
+ outs.writeString(" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\"\n");
+ outs.writeString(" xmlns:style=\"urn:oasis:names:tc:opendocument:xmlns:style:1.0\"\n");
+ outs.writeString(" xmlns:text=\"urn:oasis:names:tc:opendocument:xmlns:text:1.0\"\n");
+ outs.writeString(" xmlns:table=\"urn:oasis:names:tc:opendocument:xmlns:table:1.0\"\n");
+ outs.writeString(" xmlns:draw=\"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0\"\n");
+ outs.writeString(" xmlns:fo=\"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0\"\n");
+ outs.writeString(" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n");
+ outs.writeString(" xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
+ outs.writeString(" xmlns:meta=\"urn:oasis:names:tc:opendocument:xmlns:meta:1.0\"\n");
+ outs.writeString(" xmlns:number=\"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0\"\n");
+ outs.writeString(" xmlns:presentation=\"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0\"\n");
+ outs.writeString(" xmlns:svg=\"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0\"\n");
+ outs.writeString(" xmlns:chart=\"urn:oasis:names:tc:opendocument:xmlns:chart:1.0\"\n");
+ outs.writeString(" xmlns:dr3d=\"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0\"\n");
+ outs.writeString(" xmlns:math=\"http://www.w3.org/1998/Math/MathML\"\n");
+ outs.writeString(" xmlns:form=\"urn:oasis:names:tc:opendocument:xmlns:form:1.0\"\n");
+ outs.writeString(" xmlns:script=\"urn:oasis:names:tc:opendocument:xmlns:script:1.0\"\n");
+ outs.writeString(" xmlns:ooo=\"http://openoffice.org/2004/office\"\n");
+ outs.writeString(" xmlns:ooow=\"http://openoffice.org/2004/writer\"\n");
+ outs.writeString(" xmlns:oooc=\"http://openoffice.org/2004/calc\"\n");
+ outs.writeString(" xmlns:dom=\"http://www.w3.org/2001/xml-events\"\n");
+ outs.writeString(" xmlns:xforms=\"http://www.w3.org/2002/xforms\"\n");
+ outs.writeString(" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n");
+ outs.writeString(" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n");
+ outs.writeString(" xmlns:smil=\"urn:oasis:names:tc:opendocument:xmlns:smil-compatible:1.0\"\n");
+ outs.writeString(" xmlns:anim=\"urn:oasis:names:tc:opendocument:xmlns:animation:1.0\"\n");
+ outs.writeString(" office:version=\"1.0\">\n");
+ outs.writeString("<office:scripts/>\n");
+ outs.writeString("\n");
+ outs.writeString("<!--\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString(" D R A W I N G\n");
+ outs.writeString(" This section is the heart of SVG-ODF conversion. We are\n");
+ outs.writeString(" starting with simple conversions, and will slowly evolve\n");
+ outs.writeString(" into a 'smarter' translation as time progresses. Any help\n");
+ outs.writeString(" in improving .odg export is welcome.\n");
+ outs.writeString("*************************************************************************\n");
+ outs.writeString("-->\n");
+ outs.writeString("\n");
+ outs.writeString("<office:body>\n");
+ outs.writeString("<office:drawing>\n");
+ outs.writeString("<draw:page draw:name=\"page1\" draw:style-name=\"dp1\"\n");
+ outs.writeString(" draw:master-page-name=\"Default\">\n");
+ outs.writeString("\n");
+ return true;
+}
+
+
+/**
+ * Write the footer for the content.xml file
+ */
+bool OdfOutput::writeContentFooter(Writer &outs)
+{
+ outs.writeString("\n");
+ outs.writeString("</draw:page>\n");
+ outs.writeString("</office:drawing>\n");
+ outs.writeString("\n");
+ outs.writeString("<!-- ######### CONVERSION FROM SVG ENDS ######## -->\n");
+ outs.writeString("\n");
+ outs.writeString("</office:body>\n");
+ outs.writeString("</office:document-content>\n");
+ return true;
+}
+
+
+/**
+ * Write the content.xml file. Writes the namespace headers, then
+ * calls writeTree().
+ */
+bool OdfOutput::writeContent(ZipFile &zf, SPDocument *doc)
+{
+ //Content.xml stream
+ BufferOutputStream cbouts;
+ OutputStreamWriter couts(cbouts);
+
+ if (!writeContentHeader(couts))
+ {
+ return false;
+ }
+
+ //Style.xml stream
+ BufferOutputStream sbouts;
+ OutputStreamWriter souts(sbouts);
+
+ if (!writeStyleHeader(souts))
+ {
+ return false;
+ }
+
+ //# Descend into the tree, doing all of our conversions
+ //# to both files at the same time
+ char *oldlocale = g_strdup (setlocale (LC_NUMERIC, nullptr));
+ setlocale (LC_NUMERIC, "C");
+ if (!writeTree(couts, souts, doc, doc->getReprRoot()))
+ {
+ g_warning("Failed to convert SVG tree");
+ setlocale (LC_NUMERIC, oldlocale);
+ g_free (oldlocale);
+ return false;
+ }
+ setlocale (LC_NUMERIC, oldlocale);
+ g_free (oldlocale);
+
+ //# Finish content file
+ if (!writeContentFooter(couts))
+ {
+ return false;
+ }
+
+ ZipEntry *ze = zf.newEntry("content.xml", "ODF master content file");
+ ze->setUncompressedData(cbouts.getBuffer());
+ ze->finish();
+
+ //# Finish style file
+ if (!writeStyleFooter(souts))
+ {
+ return false;
+ }
+
+ ze = zf.newEntry("styles.xml", "ODF style file");
+ ze->setUncompressedData(sbouts.getBuffer());
+ ze->finish();
+
+ return true;
+}
+
+
+/**
+ * Resets class to its pristine condition, ready to use again
+ */
+void OdfOutput::reset()
+{
+ metadata.clear();
+ styleTable.clear();
+ styleLookupTable.clear();
+ gradientTable.clear();
+ gradientLookupTable.clear();
+ imageTable.clear();
+}
+
+
+/**
+ * Descends into the SVG tree, mapping things to ODF when appropriate
+ */
+void OdfOutput::save(Inkscape::Extension::Output */*mod*/, SPDocument *doc, gchar const *filename)
+{
+ reset();
+
+ docBaseUri = Inkscape::URI::from_dirname(doc->getDocumentBase()).str();
+
+ ZipFile zf;
+ preprocess(zf, doc, doc->getReprRoot());
+
+ if (!writeManifest(zf))
+ {
+ g_warning("Failed to write manifest");
+ return;
+ }
+
+ if (!writeContent(zf, doc))
+ {
+ g_warning("Failed to write content");
+ return;
+ }
+
+ if (!writeMeta(zf))
+ {
+ g_warning("Failed to write metafile");
+ return;
+ }
+
+ if (!zf.writeFile(filename))
+ {
+ return;
+ }
+}
+
+
+/**
+ * This is the definition of PovRay output. This function just
+ * calls the extension system with the memory allocated XML that
+ * describes the data.
+*/
+void OdfOutput::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("OpenDocument Drawing Output") "</name>\n"
+ "<id>org.inkscape.output.odf</id>\n"
+ "<output>\n"
+ "<extension>.odg</extension>\n"
+ "<mimetype>text/x-povray-script</mimetype>\n"
+ "<filetypename>" N_("OpenDocument drawing (*.odg)") "</filetypename>\n"
+ "<filetypetooltip>" N_("OpenDocument drawing file") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>",
+ new OdfOutput());
+ // clang-format on
+}
+
+/**
+ * Make sure that we are in the database
+ */
+bool OdfOutput::check (Inkscape::Extension::Extension */*module*/)
+{
+ /* We don't need a Key
+ if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV))
+ return FALSE;
+ */
+
+ return TRUE;
+}
+
+} //namespace Internal
+} //namespace Extension
+} //namespace Inkscape
+
+
+//########################################################################
+//# E N D O F F I L E
+//########################################################################
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/odf.h b/src/extension/internal/odf.h
new file mode 100644
index 0000000..236687d
--- /dev/null
+++ b/src/extension/internal/odf.h
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/** @file
+ * OpenDocument (drawing) input and output
+ *//*
+ * Authors:
+ * Bob Jamison
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU LGPL v2.1+, read the file 'COPYING' for more information.
+ */
+
+#ifndef EXTENSION_INTERNAL_ODG_OUT_H
+#define EXTENSION_INTERNAL_ODG_OUT_H
+
+#include <util/ziptool.h>
+
+#include "extension/implementation/implementation.h"
+
+#include <xml/repr.h>
+#include <string>
+#include <map>
+
+#include "object/uri.h"
+class SPItem;
+
+#include <glibmm/ustring.h>
+
+namespace Inkscape
+{
+namespace Extension
+{
+namespace Internal
+{
+
+typedef Inkscape::IO::Writer Writer;
+
+class StyleInfo
+{
+public:
+
+ StyleInfo()
+ {
+ init();
+ }
+
+ StyleInfo(const StyleInfo &other)
+ {
+ assign(other);
+ }
+
+ StyleInfo &operator=(const StyleInfo &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ void assign(const StyleInfo &other)
+ {
+ name = other.name;
+ stroke = other.stroke;
+ strokeColor = other.strokeColor;
+ strokeWidth = other.strokeWidth;
+ strokeOpacity = other.strokeOpacity;
+ fill = other.fill;
+ fillColor = other.fillColor;
+ fillOpacity = other.fillOpacity;
+ }
+
+ void init()
+ {
+ name = "none";
+ stroke = "none";
+ strokeColor = "none";
+ strokeWidth = "none";
+ strokeOpacity = "none";
+ fill = "none";
+ fillColor = "none";
+ fillOpacity = "none";
+ }
+
+ virtual ~StyleInfo()
+ = default;
+
+ //used for eliminating duplicates in the styleTable
+ bool equals(const StyleInfo &other)
+ {
+ if (
+ stroke != other.stroke ||
+ strokeColor != other.strokeColor ||
+ strokeWidth != other.strokeWidth ||
+ strokeOpacity != other.strokeOpacity ||
+ fill != other.fill ||
+ fillColor != other.fillColor ||
+ fillOpacity != other.fillOpacity
+ )
+ return false;
+ return true;
+ }
+
+ Glib::ustring name;
+ Glib::ustring stroke;
+ Glib::ustring strokeColor;
+ Glib::ustring strokeWidth;
+ Glib::ustring strokeOpacity;
+ Glib::ustring fill;
+ Glib::ustring fillColor;
+ Glib::ustring fillOpacity;
+
+};
+
+
+
+
+class GradientStop
+{
+public:
+ GradientStop() : rgb(0), opacity(0)
+ {}
+ GradientStop(unsigned long rgbArg, double opacityArg)
+ { rgb = rgbArg; opacity = opacityArg; }
+ virtual ~GradientStop()
+ = default;
+ GradientStop(const GradientStop &other)
+ { assign(other); }
+ virtual GradientStop& operator=(const GradientStop &other)
+ { assign(other); return *this; }
+ void assign(const GradientStop &other)
+ {
+ rgb = other.rgb;
+ opacity = other.opacity;
+ }
+ unsigned long rgb;
+ double opacity;
+};
+
+
+
+class GradientInfo
+{
+public:
+
+ GradientInfo()
+ {
+ init();
+ }
+
+ GradientInfo(const GradientInfo &other)
+ {
+ assign(other);
+ }
+
+ GradientInfo &operator=(const GradientInfo &other)
+ {
+ assign(other);
+ return *this;
+ }
+
+ void assign(const GradientInfo &other)
+ {
+ name = other.name;
+ style = other.style;
+ cx = other.cx;
+ cy = other.cy;
+ fx = other.fx;
+ fy = other.fy;
+ r = other.r;
+ x1 = other.x1;
+ y1 = other.y1;
+ x2 = other.x2;
+ y2 = other.y2;
+ stops = other.stops;
+ }
+
+ void init()
+ {
+ name = "none";
+ style = "none";
+ cx = 0.0;
+ cy = 0.0;
+ fx = 0.0;
+ fy = 0.0;
+ r = 0.0;
+ x1 = 0.0;
+ y1 = 0.0;
+ x2 = 0.0;
+ y2 = 0.0;
+ stops.clear();
+ }
+
+ virtual ~GradientInfo()
+ = default;
+
+ //used for eliminating duplicates in the styleTable
+ bool equals(const GradientInfo &other)
+ {
+ if (
+ name != other.name ||
+ style != other.style ||
+ cx != other.cx ||
+ cy != other.cy ||
+ fx != other.fx ||
+ fy != other.fy ||
+ r != other.r ||
+ x1 != other.x1 ||
+ y1 != other.y1 ||
+ x2 != other.x2 ||
+ y2 != other.y2
+ )
+ return false;
+ if (stops.size() != other.stops.size())
+ return false;
+ for (unsigned int i=0 ; i<stops.size() ; i++)
+ {
+ GradientStop g1 = stops[i];
+ GradientStop g2 = other.stops[i];
+ if (g1.rgb != g2.rgb)
+ return false;
+ if (g1.opacity != g2.opacity)
+ return false;
+ }
+ return true;
+ }
+
+ Glib::ustring name;
+ Glib::ustring style;
+ double cx;
+ double cy;
+ double fx;
+ double fy;
+ double r;
+ double x1;
+ double y1;
+ double x2;
+ double y2;
+ std::vector<GradientStop> stops;
+
+};
+
+
+
+/**
+ * OpenDocument <drawing> input and output
+ *
+ * This is an an entry in the extensions mechanism to begin to enable
+ * the inputting and outputting of OpenDocument Format (ODF) files from
+ * within Inkscape. Although the initial implementations will be very lossy
+ * do to the differences in the models of SVG and ODF, they will hopefully
+ * improve greatly with time.
+ *
+ * http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/idl-definitions.html
+ */
+class OdfOutput : public Inkscape::Extension::Implementation::Implementation
+{
+
+public:
+
+ bool check (Inkscape::Extension::Extension * module) override;
+
+ void save (Inkscape::Extension::Output *mod,
+ SPDocument *doc,
+ gchar const *filename) override;
+
+ static void init ();
+
+private:
+
+ std::string docBaseUri;
+
+ void reset();
+
+ //cc or dc metadata name/value pairs
+ std::map<Glib::ustring, Glib::ustring> metadata;
+
+ /* Style table
+ Uses a two-stage lookup to avoid style duplication.
+ Use like:
+ StyleInfo si = styleTable[styleLookupTable[id]];
+ but check for errors, of course
+ */
+ //element id -> style entry name
+ std::map<Glib::ustring, Glib::ustring> styleLookupTable;
+ //style entry name -> style info
+ std::vector<StyleInfo> styleTable;
+
+ //element id -> gradient entry name
+ std::map<Glib::ustring, Glib::ustring> gradientLookupTable;
+ //gradient entry name -> gradient info
+ std::vector<GradientInfo> gradientTable;
+
+ //for renaming image file names
+ std::map<Glib::ustring, Glib::ustring> imageTable;
+
+ void preprocess(ZipFile &zf, SPDocument *doc, Inkscape::XML::Node *node);
+
+ bool writeManifest(ZipFile &zf);
+
+ bool writeMeta(ZipFile &zf);
+
+ bool writeStyle(ZipFile &zf);
+
+ bool processStyle(SPItem *item, const Glib::ustring &id, const Glib::ustring &gradientNameFill, const Glib::ustring &gradientNameStroke, Glib::ustring& output);
+
+ bool processGradient(SPItem *item,
+ const Glib::ustring &id, Geom::Affine &tf, Glib::ustring& gradientName, Glib::ustring& output, bool checkFillGradient = true);
+
+ bool writeStyleHeader(Writer &outs);
+
+ bool writeStyleFooter(Writer &outs);
+
+ bool writeContentHeader(Writer &outs);
+
+ bool writeContentFooter(Writer &outs);
+
+ bool writeTree(Writer &couts, Writer &souts, SPDocument *doc, Inkscape::XML::Node *node);
+
+ bool writeContent(ZipFile &zf, SPDocument *doc);
+
+};
+
+
+} //namespace Internal
+} //namespace Extension
+} //namespace Inkscape
+
+
+
+#endif /* EXTENSION_INTERNAL_ODG_OUT_H */
+
diff --git a/src/extension/internal/pdfinput/pdf-input.cpp b/src/extension/internal/pdfinput/pdf-input.cpp
new file mode 100644
index 0000000..cdf2237
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-input.cpp
@@ -0,0 +1,1003 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Native PDF import using libpoppler.
+ *
+ * Authors:
+ * miklos erdelyi
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include "pdf-input.h"
+
+#ifdef HAVE_POPPLER
+#include <poppler/goo/GooString.h>
+#include <poppler/ErrorCodes.h>
+#include <poppler/GlobalParams.h>
+#include <poppler/PDFDoc.h>
+#include <poppler/Page.h>
+#include <poppler/Catalog.h>
+
+#ifdef HAVE_POPPLER_CAIRO
+#include <poppler/glib/poppler.h>
+#include <poppler/glib/poppler-document.h>
+#include <poppler/glib/poppler-page.h>
+#endif
+
+#include <gdkmm/general.h>
+#include <glibmm/convert.h>
+#include <glibmm/i18n.h>
+#include <glibmm/miscutils.h>
+#include <gtk/gtk.h>
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/drawingarea.h>
+#include <gtkmm/frame.h>
+#include <gtkmm/radiobutton.h>
+#include <gtkmm/scale.h>
+#include <utility>
+
+#include "document-undo.h"
+#include "extension/input.h"
+#include "extension/system.h"
+#include "inkscape.h"
+#include "object/sp-root.h"
+#include "pdf-parser.h"
+#include "ui/dialog-events.h"
+#include "ui/widget/frame.h"
+#include "ui/widget/spinbutton.h"
+#include "util/units.h"
+
+
+
+namespace {
+
+void sanitize_page_number(int &page_num, const int num_pages) {
+ if (page_num < 1 || page_num > num_pages) {
+ std::cerr << "Inkscape::Extension::Internal::PdfInput::open: Bad page number "
+ << page_num
+ << ". Import first page instead."
+ << std::endl;
+ page_num = 1;
+ }
+}
+
+}
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ * \brief The PDF import dialog
+ * FIXME: Probably this should be placed into src/ui/dialog
+ */
+
+static const gchar * crop_setting_choices[] = {
+ //TRANSLATORS: The following are document crop settings for PDF import
+ // more info: http://www.acrobatusers.com/tech_corners/javascript_corner/tips/2006/page_bounds/
+ N_("media box"),
+ N_("crop box"),
+ N_("trim box"),
+ N_("bleed box"),
+ N_("art box")
+};
+
+PdfImportDialog::PdfImportDialog(std::shared_ptr<PDFDoc> doc, const gchar */*uri*/)
+ : _pdf_doc(std::move(doc))
+{
+ assert(_pdf_doc);
+#ifdef HAVE_POPPLER_CAIRO
+ _poppler_doc = NULL;
+#endif // HAVE_POPPLER_CAIRO
+ cancelbutton = Gtk::manage(new Gtk::Button(_("_Cancel"), true));
+ okbutton = Gtk::manage(new Gtk::Button(_("_OK"), true));
+ _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:")));
+
+ // All Pages
+ _pageAllPages = Gtk::manage(new class Gtk::CheckButton(_("All")));
+
+ // Page number
+ auto _pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _pdf_doc->getNumPages(), 1, 10, 0);
+ _pageNumberSpin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(_pageNumberSpin_adj, 1, 1));
+ _pageNumberSpin->set_sensitive(false);
+ _labelTotalPages = Gtk::manage(new class Gtk::Label());
+ hbox2 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
+
+ // Disable the page selector when there's only one page
+ int num_pages = _pdf_doc->getCatalog()->getNumPages();
+ if ( num_pages == 1 ) {
+ _pageAllPages->set_sensitive(false);
+ } else {
+ // Display total number of pages
+ gchar *label_text = g_strdup_printf(_("out of %i"), num_pages);
+ _labelTotalPages->set_label(label_text);
+ g_free(label_text);
+ }
+
+ // Crop settings
+ _cropCheck = Gtk::manage(new class Gtk::CheckButton(_("Clip to:")));
+ _cropTypeCombo = Gtk::manage(new class Gtk::ComboBoxText());
+ int num_crop_choices = sizeof(crop_setting_choices) / sizeof(crop_setting_choices[0]);
+ for ( int i = 0 ; i < num_crop_choices ; i++ ) {
+ _cropTypeCombo->append(_(crop_setting_choices[i]));
+ }
+ _cropTypeCombo->set_active_text(_(crop_setting_choices[0]));
+ _cropTypeCombo->set_sensitive(false);
+
+ hbox3 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
+ vbox2 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4));
+ _pageSettingsFrame = Gtk::manage(new class Inkscape::UI::Widget::Frame(_("Page settings")));
+ _labelPrecision = Gtk::manage(new class Gtk::Label(_("Precision of approximating gradient meshes:")));
+ _labelPrecisionWarning = Gtk::manage(new class Gtk::Label(_("<b>Note</b>: setting the precision too high may result in a large SVG file and slow performance.")));
+ _labelPrecisionWarning->set_max_width_chars(50);
+
+#ifdef HAVE_POPPLER_CAIRO
+ Gtk::RadioButton::Group group;
+ _importViaPoppler = Gtk::manage(new class Gtk::RadioButton(group,_("Poppler/Cairo import")));
+ _labelViaPoppler = Gtk::manage(new class Gtk::Label(_("Import via external library. Text consists of groups containing cloned glyphs where each glyph is a path. Images are stored internally. Meshes cause entire document to be rendered as a raster image.")));
+ _labelViaPoppler->set_max_width_chars(50);
+ _importViaInternal = Gtk::manage(new class Gtk::RadioButton(group,_("Internal import")));
+ _labelViaInternal = Gtk::manage(new class Gtk::Label(_("Import via internal (Poppler derived) library. Text is stored as text but white space is missing. Meshes are converted to tiles, the number depends on the precision set below.")));
+ _labelViaInternal->set_max_width_chars(50);
+#endif
+
+ _fallbackPrecisionSlider_adj = Gtk::Adjustment::create(2, 1, 256, 1, 10, 10);
+ _fallbackPrecisionSlider = Gtk::manage(new class Gtk::Scale(_fallbackPrecisionSlider_adj));
+ _fallbackPrecisionSlider->set_value(2.0);
+ _labelPrecisionComment = Gtk::manage(new class Gtk::Label(_("rough")));
+ hbox6 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
+
+ // Text options
+ // _labelText = Gtk::manage(new class Gtk::Label(_("Text handling:")));
+ // _textHandlingCombo = Gtk::manage(new class Gtk::ComboBoxText());
+ // _textHandlingCombo->append(_("Import text as text"));
+ // _textHandlingCombo->set_active_text(_("Import text as text"));
+ // hbox5 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
+
+ // Font option
+ _localFontsCheck = Gtk::manage(new class Gtk::CheckButton(_("Replace PDF fonts by closest-named installed fonts")));
+
+ _embedImagesCheck = Gtk::manage(new class Gtk::CheckButton(_("Embed images")));
+ vbox3 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4));
+ _importSettingsFrame = Gtk::manage(new class Inkscape::UI::Widget::Frame(_("Import settings")));
+ vbox1 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4));
+ _previewArea = Gtk::manage(new class Gtk::DrawingArea());
+ hbox1 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
+ cancelbutton->set_can_focus();
+ cancelbutton->set_can_default();
+ cancelbutton->set_relief(Gtk::RELIEF_NORMAL);
+ okbutton->set_can_focus();
+ okbutton->set_can_default();
+ okbutton->set_relief(Gtk::RELIEF_NORMAL);
+
+ _labelSelect->set_xalign(0.5);
+ _labelSelect->set_yalign(0.5);
+ _labelTotalPages->set_xalign(0.5);
+ _labelTotalPages->set_yalign(0.5);
+ _labelPrecision->set_xalign(0.0);
+ _labelPrecision->set_yalign(0.5);
+ _labelPrecisionWarning->set_xalign(0.0);
+ _labelPrecisionWarning->set_yalign(0.5);
+ _labelPrecisionComment->set_xalign(0.5);
+ _labelPrecisionComment->set_yalign(0.5);
+
+ _labelSelect->set_margin_start(4);
+ _labelSelect->set_margin_end(4);
+ _labelTotalPages->set_margin_start(4);
+ _labelTotalPages->set_margin_end(4);
+ _labelPrecision->set_margin_start(4);
+ _labelPrecision->set_margin_end(4);
+ _labelPrecisionWarning->set_margin_start(4);
+ _labelPrecisionWarning->set_margin_end(4);
+ _labelPrecisionComment->set_margin_start(4);
+ _labelPrecisionComment->set_margin_end(4);
+
+ _labelSelect->set_justify(Gtk::JUSTIFY_LEFT);
+ _labelSelect->set_line_wrap(false);
+ _labelSelect->set_use_markup(false);
+ _labelSelect->set_selectable(false);
+ _pageAllPages->set_can_focus();
+ _pageAllPages->set_relief(Gtk::RELIEF_NORMAL);
+ _pageAllPages->set_mode(true);
+ _pageAllPages->set_active(true);
+ _pageNumberSpin->set_can_focus();
+ _pageNumberSpin->set_update_policy(Gtk::UPDATE_ALWAYS);
+ _pageNumberSpin->set_numeric(true);
+ _pageNumberSpin->set_digits(0);
+ _pageNumberSpin->set_wrap(false);
+ _labelTotalPages->set_justify(Gtk::JUSTIFY_LEFT);
+ _labelTotalPages->set_line_wrap(false);
+ _labelTotalPages->set_use_markup(false);
+ _labelTotalPages->set_selectable(false);
+ hbox2->pack_start(*_pageAllPages, Gtk::PACK_SHRINK, 4);
+ hbox2->pack_start(*_labelSelect, Gtk::PACK_SHRINK, 4);
+ hbox2->pack_start(*_pageNumberSpin, Gtk::PACK_SHRINK, 4);
+ hbox2->pack_start(*_labelTotalPages, Gtk::PACK_SHRINK, 4);
+ _cropCheck->set_can_focus();
+ _cropCheck->set_relief(Gtk::RELIEF_NORMAL);
+ _cropCheck->set_mode(true);
+ _cropCheck->set_active(false);
+ _cropTypeCombo->set_border_width(1);
+ hbox3->pack_start(*_cropCheck, Gtk::PACK_SHRINK, 4);
+ hbox3->pack_start(*_cropTypeCombo, Gtk::PACK_SHRINK, 0);
+ vbox2->pack_start(*hbox2);
+ vbox2->pack_start(*hbox3);
+ _pageSettingsFrame->add(*vbox2);
+ _pageSettingsFrame->set_border_width(4);
+ _labelPrecision->set_justify(Gtk::JUSTIFY_LEFT);
+ _labelPrecision->set_line_wrap(true);
+ _labelPrecision->set_use_markup(false);
+ _labelPrecision->set_selectable(false);
+ _labelPrecisionWarning->set_justify(Gtk::JUSTIFY_LEFT);
+ _labelPrecisionWarning->set_line_wrap(true);
+ _labelPrecisionWarning->set_use_markup(true);
+ _labelPrecisionWarning->set_selectable(false);
+
+#ifdef HAVE_POPPLER_CAIRO
+ _importViaPoppler->set_can_focus();
+ _importViaPoppler->set_relief(Gtk::RELIEF_NORMAL);
+ _importViaPoppler->set_mode(true);
+ _importViaPoppler->set_active(false);
+ _importViaInternal->set_can_focus();
+ _importViaInternal->set_relief(Gtk::RELIEF_NORMAL);
+ _importViaInternal->set_mode(true);
+ _importViaInternal->set_active(true);
+ _labelViaPoppler->set_line_wrap(true);
+ _labelViaInternal->set_line_wrap(true);
+ _labelViaPoppler->set_xalign(0);
+ _labelViaInternal->set_xalign(0);
+#endif
+
+ _fallbackPrecisionSlider->set_size_request(180,-1);
+ _fallbackPrecisionSlider->set_can_focus();
+ _fallbackPrecisionSlider->set_inverted(false);
+ _fallbackPrecisionSlider->set_digits(1);
+ _fallbackPrecisionSlider->set_draw_value(true);
+ _fallbackPrecisionSlider->set_value_pos(Gtk::POS_TOP);
+ _labelPrecisionComment->set_size_request(90,-1);
+ _labelPrecisionComment->set_justify(Gtk::JUSTIFY_LEFT);
+ _labelPrecisionComment->set_line_wrap(false);
+ _labelPrecisionComment->set_use_markup(false);
+ _labelPrecisionComment->set_selectable(false);
+ hbox6->pack_start(*_fallbackPrecisionSlider, Gtk::PACK_SHRINK, 4);
+ hbox6->pack_start(*_labelPrecisionComment, Gtk::PACK_SHRINK, 0);
+ // _labelText->set_alignment(0.5,0.5);
+ // _labelText->set_padding(4,0);
+ // _labelText->set_justify(Gtk::JUSTIFY_LEFT);
+ // _labelText->set_line_wrap(false);
+ // _labelText->set_use_markup(false);
+ // _labelText->set_selectable(false);
+ // hbox5->pack_start(*_labelText, Gtk::PACK_SHRINK, 0);
+ // hbox5->pack_start(*_textHandlingCombo, Gtk::PACK_SHRINK, 0);
+ _localFontsCheck->set_can_focus();
+ _localFontsCheck->set_relief(Gtk::RELIEF_NORMAL);
+ _localFontsCheck->set_mode(true);
+ _localFontsCheck->set_active(true);
+ _embedImagesCheck->set_can_focus();
+ _embedImagesCheck->set_relief(Gtk::RELIEF_NORMAL);
+ _embedImagesCheck->set_mode(true);
+ _embedImagesCheck->set_active(true);
+#ifdef HAVE_POPPLER_CAIRO
+ vbox3->pack_start(*_importViaPoppler, Gtk::PACK_SHRINK, 0);
+ vbox3->pack_start(*_labelViaPoppler, Gtk::PACK_SHRINK, 0);
+ vbox3->pack_start(*_importViaInternal, Gtk::PACK_SHRINK, 0);
+ vbox3->pack_start(*_labelViaInternal, Gtk::PACK_SHRINK, 0);
+#endif
+ vbox3->pack_start(*_localFontsCheck, Gtk::PACK_SHRINK, 0);
+ vbox3->pack_start(*_embedImagesCheck, Gtk::PACK_SHRINK, 0);
+ vbox3->pack_start(*_labelPrecision, Gtk::PACK_SHRINK, 0);
+ vbox3->pack_start(*hbox6, Gtk::PACK_SHRINK, 0);
+ vbox3->pack_start(*_labelPrecisionWarning, Gtk::PACK_SHRINK, 0);
+ // vbox3->pack_start(*hbox5, Gtk::PACK_SHRINK, 4);
+ _importSettingsFrame->add(*vbox3);
+ _importSettingsFrame->set_border_width(4);
+ vbox1->pack_start(*_pageSettingsFrame, Gtk::PACK_SHRINK, 0);
+ vbox1->pack_start(*_importSettingsFrame, Gtk::PACK_SHRINK, 0);
+ hbox1->pack_start(*vbox1);
+ hbox1->pack_start(*_previewArea, Gtk::PACK_SHRINK, 4);
+
+ get_content_area()->set_homogeneous(false);
+ get_content_area()->set_spacing(0);
+ get_content_area()->pack_start(*hbox1);
+
+ this->set_title(_("PDF Import Settings"));
+ this->set_modal(true);
+ sp_transientize(GTK_WIDGET(this->gobj())); //Make transient
+ this->property_window_position().set_value(Gtk::WIN_POS_NONE);
+ this->set_resizable(true);
+ this->property_destroy_with_parent().set_value(false);
+ this->add_action_widget(*cancelbutton, -6);
+ this->add_action_widget(*okbutton, -5);
+
+ this->show_all();
+
+ // Connect signals
+ _previewArea->signal_draw().connect(sigc::mem_fun(*this, &PdfImportDialog::_onDraw));
+ _pageAllPages->signal_toggled().connect(sigc::mem_fun(*this, &PdfImportDialog::_onToggleAllPages));
+ _pageNumberSpin_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PdfImportDialog::_onPageNumberChanged));
+ _cropCheck->signal_toggled().connect(sigc::mem_fun(*this, &PdfImportDialog::_onToggleCropping));
+ _fallbackPrecisionSlider_adj->signal_value_changed().connect(sigc::mem_fun(*this, &PdfImportDialog::_onPrecisionChanged));
+#ifdef HAVE_POPPLER_CAIRO
+ _importViaPoppler->signal_toggled().connect(sigc::mem_fun(*this, &PdfImportDialog::_onToggleImport));
+#endif
+
+ _render_thumb = false;
+#ifdef HAVE_POPPLER_CAIRO
+ _cairo_surface = NULL;
+ _render_thumb = true;
+
+ // Create PopplerDocument
+ std::string filename = _pdf_doc->getFileName()->getCString();
+ if (!Glib::path_is_absolute(filename)) {
+ filename = Glib::build_filename(Glib::get_current_dir(),filename);
+ }
+ Glib::ustring full_uri = Glib::filename_to_uri(filename);
+
+ if (!full_uri.empty()) {
+ _poppler_doc = poppler_document_new_from_file(full_uri.c_str(), NULL, NULL);
+ }
+
+ // Set sensitivity of some widgets based on selected import type.
+ _onToggleImport();
+#endif
+
+ // Set default preview size
+ _preview_width = 200;
+ _preview_height = 300;
+
+ // Init preview
+ _thumb_data = nullptr;
+ _pageNumberSpin_adj->set_value(1.0);
+ _current_page = -1;
+ _setPreviewPage(1);
+
+ set_default (*okbutton);
+ set_focus (*okbutton);
+}
+
+PdfImportDialog::~PdfImportDialog() {
+#ifdef HAVE_POPPLER_CAIRO
+ if (_cairo_surface) {
+ cairo_surface_destroy(_cairo_surface);
+ }
+ if (_poppler_doc) {
+ g_object_unref(G_OBJECT(_poppler_doc));
+ }
+#endif
+ if (_thumb_data) {
+ gfree(_thumb_data);
+ }
+}
+
+bool PdfImportDialog::showDialog() {
+ show();
+ gint b = run();
+ hide();
+ if ( b == Gtk::RESPONSE_OK ) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+int PdfImportDialog::getSelectedPage() {
+ return _current_page;
+}
+
+bool PdfImportDialog::getImportMethod() {
+#ifdef HAVE_POPPLER_CAIRO
+ return _importViaPoppler->get_active();
+#else
+ return false;
+#endif
+}
+
+/**
+ * \brief Retrieves the current settings into a repr which SvgBuilder will use
+ * for determining the behaviour desired by the user
+ */
+void PdfImportDialog::getImportSettings(Inkscape::XML::Node *prefs) {
+ prefs->setAttributeSvgDouble("selectedPage", (double)_current_page);
+ if (_cropCheck->get_active()) {
+ Glib::ustring current_choice = _cropTypeCombo->get_active_text();
+ int num_crop_choices = sizeof(crop_setting_choices) / sizeof(crop_setting_choices[0]);
+ int i = 0;
+ for ( ; i < num_crop_choices ; i++ ) {
+ if ( current_choice == _(crop_setting_choices[i]) ) {
+ break;
+ }
+ }
+ prefs->setAttributeSvgDouble("cropTo", (double)i);
+ } else {
+ prefs->setAttributeSvgDouble("cropTo", -1.0);
+ }
+ prefs->setAttributeSvgDouble("approximationPrecision", _fallbackPrecisionSlider->get_value());
+ if (_localFontsCheck->get_active()) {
+ prefs->setAttribute("localFonts", "1");
+ } else {
+ prefs->setAttribute("localFonts", "0");
+ }
+ if (_embedImagesCheck->get_active()) {
+ prefs->setAttribute("embedImages", "1");
+ } else {
+ prefs->setAttribute("embedImages", "0");
+ }
+#ifdef HAVE_POPPLER_CAIRO
+ if (_importViaPoppler->get_active()) {
+ prefs->setAttribute("importviapoppler", "1");
+ } else {
+ prefs->setAttribute("importviapoppler", "0");
+ }
+#endif
+}
+
+/**
+ * \brief Redisplay the comment on the current approximation precision setting
+ * Evenly divides the interval of possible values between the available labels.
+ */
+void PdfImportDialog::_onPrecisionChanged() {
+
+ static Glib::ustring precision_comments[] = {
+ Glib::ustring(C_("PDF input precision", "rough")),
+ Glib::ustring(C_("PDF input precision", "medium")),
+ Glib::ustring(C_("PDF input precision", "fine")),
+ Glib::ustring(C_("PDF input precision", "very fine"))
+ };
+
+ double min = _fallbackPrecisionSlider_adj->get_lower();
+ double max = _fallbackPrecisionSlider_adj->get_upper();
+ int num_intervals = sizeof(precision_comments) / sizeof(precision_comments[0]);
+ double interval_len = ( max - min ) / (double)num_intervals;
+ double value = _fallbackPrecisionSlider_adj->get_value();
+ int comment_idx = (int)floor( ( value - min ) / interval_len );
+ _labelPrecisionComment->set_label(precision_comments[comment_idx]);
+}
+
+void PdfImportDialog::_onToggleAllPages() {
+ if (_pageAllPages->get_active()) {
+ _pageNumberSpin->set_sensitive(false);
+ _current_page = -1;
+ _setPreviewPage(1);
+ } else {
+ _pageNumberSpin->set_sensitive(true);
+ _onPageNumberChanged();
+ }
+}
+
+void PdfImportDialog::_onToggleCropping() {
+ _cropTypeCombo->set_sensitive(_cropCheck->get_active());
+}
+
+void PdfImportDialog::_onPageNumberChanged() {
+ int page = _pageNumberSpin->get_value_as_int();
+ _current_page = CLAMP(page, 1, _pdf_doc->getCatalog()->getNumPages());
+ _setPreviewPage(_current_page);
+}
+
+#ifdef HAVE_POPPLER_CAIRO
+void PdfImportDialog::_onToggleImport() {
+ if( _importViaPoppler->get_active() ) {
+ hbox3->set_sensitive(false);
+ _localFontsCheck->set_sensitive(false);
+ _embedImagesCheck->set_sensitive(false);
+ hbox6->set_sensitive(false);
+ _pageAllPages->set_sensitive(false);
+ _pageAllPages->set_active(false);
+ } else {
+ hbox3->set_sensitive();
+ _localFontsCheck->set_sensitive();
+ _embedImagesCheck->set_sensitive();
+ hbox6->set_sensitive();
+ _pageAllPages->set_sensitive(true);
+ _pageAllPages->set_active(true);
+ }
+}
+#endif
+
+
+#ifdef HAVE_POPPLER_CAIRO
+/**
+ * \brief Copies image data from a Cairo surface to a pixbuf
+ *
+ * Borrowed from libpoppler, from the file poppler-page.cc
+ * Copyright (C) 2005, Red Hat, Inc.
+ *
+ */
+static void copy_cairo_surface_to_pixbuf (cairo_surface_t *surface,
+ unsigned char *data,
+ GdkPixbuf *pixbuf)
+{
+ int cairo_width, cairo_height, cairo_rowstride;
+ unsigned char *pixbuf_data, *dst, *cairo_data;
+ int pixbuf_rowstride, pixbuf_n_channels;
+ unsigned int *src;
+ int x, y;
+
+ cairo_width = cairo_image_surface_get_width (surface);
+ cairo_height = cairo_image_surface_get_height (surface);
+ cairo_rowstride = cairo_width * 4;
+ cairo_data = data;
+
+ pixbuf_data = gdk_pixbuf_get_pixels (pixbuf);
+ pixbuf_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixbuf_n_channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+ if (cairo_width > gdk_pixbuf_get_width (pixbuf))
+ cairo_width = gdk_pixbuf_get_width (pixbuf);
+ if (cairo_height > gdk_pixbuf_get_height (pixbuf))
+ cairo_height = gdk_pixbuf_get_height (pixbuf);
+ for (y = 0; y < cairo_height; y++)
+ {
+ src = reinterpret_cast<unsigned int *>(cairo_data + y * cairo_rowstride);
+ dst = pixbuf_data + y * pixbuf_rowstride;
+ for (x = 0; x < cairo_width; x++)
+ {
+ dst[0] = (*src >> 16) & 0xff;
+ dst[1] = (*src >> 8) & 0xff;
+ dst[2] = (*src >> 0) & 0xff;
+ if (pixbuf_n_channels == 4)
+ dst[3] = (*src >> 24) & 0xff;
+ dst += pixbuf_n_channels;
+ src++;
+ }
+ }
+}
+
+#endif
+
+bool PdfImportDialog::_onDraw(const Cairo::RefPtr<Cairo::Context>& cr) {
+ // Check if we have a thumbnail at all
+ if (!_thumb_data) {
+ return true;
+ }
+
+ // Create the pixbuf for the thumbnail
+ Glib::RefPtr<Gdk::Pixbuf> thumb;
+
+ if (_render_thumb) {
+ thumb = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, true,
+ 8, _thumb_width, _thumb_height);
+ } else {
+ thumb = Gdk::Pixbuf::create_from_data(_thumb_data, Gdk::COLORSPACE_RGB,
+ false, 8, _thumb_width, _thumb_height, _thumb_rowstride);
+ }
+ if (!thumb) {
+ return true;
+ }
+
+ // Set background to white
+ if (_render_thumb) {
+ thumb->fill(0xffffffff);
+ Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, 0);
+ cr->paint();
+ }
+#ifdef HAVE_POPPLER_CAIRO
+ // Copy the thumbnail image from the Cairo surface
+ if (_render_thumb) {
+ copy_cairo_surface_to_pixbuf(_cairo_surface, _thumb_data, thumb->gobj());
+ }
+#endif
+
+ Gdk::Cairo::set_source_pixbuf(cr, thumb, 0, _render_thumb ? 0 : 20);
+ cr->paint();
+ return true;
+}
+
+/**
+ * \brief Renders the given page's thumbnail using Cairo
+ */
+void PdfImportDialog::_setPreviewPage(int page) {
+
+ _previewed_page = _pdf_doc->getCatalog()->getPage(page);
+ g_return_if_fail(_previewed_page);
+ // Try to get a thumbnail from the PDF if possible
+ if (!_render_thumb) {
+ if (_thumb_data) {
+ gfree(_thumb_data);
+ _thumb_data = nullptr;
+ }
+ if (!_previewed_page->loadThumb(&_thumb_data,
+ &_thumb_width, &_thumb_height, &_thumb_rowstride)) {
+ return;
+ }
+ // Redraw preview area
+ _previewArea->set_size_request(_thumb_width, _thumb_height + 20);
+ _previewArea->queue_draw();
+ return;
+ }
+#ifdef HAVE_POPPLER_CAIRO
+ // Get page size by accounting for rotation
+ double width, height;
+ int rotate = _previewed_page->getRotate();
+ if ( rotate == 90 || rotate == 270 ) {
+ height = _previewed_page->getCropWidth();
+ width = _previewed_page->getCropHeight();
+ } else {
+ width = _previewed_page->getCropWidth();
+ height = _previewed_page->getCropHeight();
+ }
+ // Calculate the needed scaling for the page
+ double scale_x = (double)_preview_width / width;
+ double scale_y = (double)_preview_height / height;
+ double scale_factor = ( scale_x > scale_y ) ? scale_y : scale_x;
+ // Create new Cairo surface
+ _thumb_width = (int)ceil( width * scale_factor );
+ _thumb_height = (int)ceil( height * scale_factor );
+ _thumb_rowstride = _thumb_width * 4;
+ if (_thumb_data) {
+ gfree(_thumb_data);
+ }
+ _thumb_data = reinterpret_cast<unsigned char *>(gmalloc(_thumb_rowstride * _thumb_height));
+ if (_cairo_surface) {
+ cairo_surface_destroy(_cairo_surface);
+ }
+ _cairo_surface = cairo_image_surface_create_for_data(_thumb_data,
+ CAIRO_FORMAT_ARGB32, _thumb_width, _thumb_height, _thumb_rowstride);
+ cairo_t *cr = cairo_create(_cairo_surface);
+ cairo_set_source_rgba(cr, 1.0, 1.0, 1.0, 1.0); // Set fill color to white
+ cairo_paint(cr); // Clear it
+ cairo_scale(cr, scale_factor, scale_factor); // Use Cairo for resizing the image
+ // Render page
+ if (_poppler_doc != NULL) {
+ PopplerPage *poppler_page = poppler_document_get_page(_poppler_doc, page-1);
+ poppler_page_render(poppler_page, cr);
+ g_object_unref(G_OBJECT(poppler_page));
+ }
+ // Clean up
+ cairo_destroy(cr);
+ // Redraw preview area
+ _previewArea->set_size_request(_preview_width, _preview_height);
+ _previewArea->queue_draw();
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_POPPLER_CAIRO
+/// helper method
+static cairo_status_t
+ _write_ustring_cb(void *closure, const unsigned char *data, unsigned int length)
+{
+ Glib::ustring* stream = static_cast<Glib::ustring*>(closure);
+ stream->append(reinterpret_cast<const char*>(data), length);
+
+ return CAIRO_STATUS_SUCCESS;
+}
+#endif
+
+/**
+ * Parses the selected page of the given PDF document using PdfParser.
+ */
+SPDocument *
+PdfInput::open(::Inkscape::Extension::Input * /*mod*/, const gchar * uri) {
+
+ // Initialize the globalParams variable for poppler
+ if (!globalParams) {
+ globalParams = _POPPLER_NEW_GLOBAL_PARAMS();
+ }
+
+
+ // Open the file using poppler
+ // PDFDoc is from poppler. PDFDoc is used for preview and for native import.
+ std::shared_ptr<PDFDoc> pdf_doc;
+
+ // poppler does not use glib g_open. So on win32 we must use unicode call. code was copied from
+ // glib gstdio.c
+ pdf_doc = _POPPLER_MAKE_SHARED_PDFDOC(uri); // TODO: Could ask for password
+
+ if (!pdf_doc->isOk()) {
+ int error = pdf_doc->getErrorCode();
+ if (error == errEncrypted) {
+ g_message("Document is encrypted.");
+ } else if (error == errOpenFile) {
+ g_message("couldn't open the PDF file.");
+ } else if (error == errBadCatalog) {
+ g_message("couldn't read the page catalog.");
+ } else if (error == errDamaged) {
+ g_message("PDF file was damaged and couldn't be repaired.");
+ } else if (error == errHighlightFile) {
+ g_message("nonexistent or invalid highlight file.");
+ } else if (error == errBadPrinter) {
+ g_message("invalid printer.");
+ } else if (error == errPrinting) {
+ g_message("Error during printing.");
+ } else if (error == errPermission) {
+ g_message("PDF file does not allow that operation.");
+ } else if (error == errBadPageNum) {
+ g_message("invalid page number.");
+ } else if (error == errFileIO) {
+ g_message("file IO error.");
+ } else {
+ g_message("Failed to load document from data (error %d)", error);
+ }
+
+ return nullptr;
+ }
+
+
+ std::unique_ptr<PdfImportDialog> dlg;
+ if (INKSCAPE.use_gui()) {
+ dlg = std::make_unique<PdfImportDialog>(pdf_doc, uri);
+ if (!dlg->showDialog()) {
+ throw Input::open_cancelled();
+ }
+ }
+
+ // Get options
+ int page_num = 1;
+ bool is_importvia_poppler = false;
+ if (dlg) {
+ page_num = dlg->getSelectedPage();
+#ifdef HAVE_POPPLER_CAIRO
+ is_importvia_poppler = dlg->getImportMethod();
+ // printf("PDF import via %s.\n", is_importvia_poppler ? "poppler" : "native");
+#endif
+ } else {
+ page_num = INKSCAPE.get_pdf_page();
+#ifdef HAVE_POPPLER_CAIRO
+ is_importvia_poppler = INKSCAPE.get_pdf_poppler();
+#endif
+ }
+
+ // Create Inkscape document from file
+ SPDocument *doc = nullptr;
+ bool saved = false;
+ if(!is_importvia_poppler)
+ {
+ // Create document
+ doc = SPDocument::createNewDoc(nullptr, true, true);
+ saved = DocumentUndo::getUndoSensitive(doc);
+ DocumentUndo::setUndoSensitive(doc, false); // No need to undo in this temporary document
+
+ // Create builder
+ gchar *docname = g_path_get_basename(uri);
+ gchar *dot = g_strrstr(docname, ".");
+ if (dot) {
+ *dot = 0;
+ }
+ SvgBuilder *builder = new SvgBuilder(doc, docname, pdf_doc->getXRef());
+
+ // Get preferences
+ Inkscape::XML::Node *prefs = builder->getPreferences();
+ if (dlg)
+ dlg->getImportSettings(prefs);
+
+ if (page_num > 0) {
+ add_builder_page(pdf_doc, builder, doc, page_num);
+ } else {
+ // Multi page (open all pages)
+ Catalog *catalog = pdf_doc->getCatalog();
+ for (int p = 1; p <= catalog->getNumPages(); p++) {
+ // Incriment the page building here.
+ builder->pushPage();
+ // And then add each of the pages
+ add_builder_page(pdf_doc, builder, doc, p);
+ }
+ }
+
+ delete builder;
+ g_free(docname);
+ }
+ else
+ {
+#ifdef HAVE_POPPLER_CAIRO
+ // the poppler import
+
+ std::string full_path = uri;
+ if (!Glib::path_is_absolute(uri)) {
+ full_path = Glib::build_filename(Glib::get_current_dir(),uri);
+ }
+ Glib::ustring full_uri = Glib::filename_to_uri(full_path);
+
+ GError *error = NULL;
+ /// @todo handle password
+ /// @todo check if win32 unicode needs special attention
+ PopplerDocument* document = poppler_document_new_from_file(full_uri.c_str(), NULL, &error);
+ PopplerPage* page = nullptr;
+
+ if(error != NULL) {
+ std::cerr << "PDFInput::open: error opening document: " << full_uri << std::endl;
+ g_error_free (error);
+ }
+
+ if (document) {
+ int const num_pages = poppler_document_get_n_pages(document);
+ sanitize_page_number(page_num, num_pages);
+ page = poppler_document_get_page(document, page_num - 1);
+ }
+
+ if (page) {
+ double width, height;
+ poppler_page_get_size(page, &width, &height);
+
+ Glib::ustring output;
+ cairo_surface_t* surface = cairo_svg_surface_create_for_stream(Inkscape::Extension::Internal::_write_ustring_cb,
+ &output, width, height);
+
+ // Reset back to PT for cairo 1.17.6 and above which sets to UNIT_USER
+ cairo_svg_surface_set_document_unit(surface, CAIRO_SVG_UNIT_PT);
+
+ // This magical function results in more fine-grain fallbacks. In particular, a mesh
+ // gradient won't necessarily result in the whole PDF being rasterized. Of course, SVG
+ // 1.2 never made it as a standard, but hey, we'll take what we can get. This trick was
+ // found by examining the 'pdftocairo' code.
+ cairo_svg_surface_restrict_to_version( surface, CAIRO_SVG_VERSION_1_2 );
+
+ cairo_t* cr = cairo_create(surface);
+
+ poppler_page_render_for_printing(page, cr);
+ cairo_show_page(cr);
+
+ cairo_destroy(cr);
+ cairo_surface_destroy(surface);
+
+ doc = SPDocument::createNewDocFromMem(output.c_str(), output.length(), TRUE);
+ } else if (document) {
+ std::cerr << "PDFInput::open: error opening page " << page_num << " of document: " << full_uri << std::endl;
+ }
+
+ // Cleanup
+ if (document) {
+ g_object_unref(G_OBJECT(document));
+ if (page) {
+ g_object_unref(G_OBJECT(page));
+ }
+ }
+
+ if (!doc) {
+ return nullptr;
+ }
+
+ saved = DocumentUndo::getUndoSensitive(doc);
+ DocumentUndo::setUndoSensitive(doc, false); // No need to undo in this temporary document
+#endif
+ }
+
+ // Set viewBox if it doesn't exist
+ if (!doc->getRoot()->viewBox_set) {
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit())));
+ }
+
+ // Restore undo
+ DocumentUndo::setUndoSensitive(doc, saved);
+
+ return doc;
+}
+
+/**
+ * Parses the selected page object of the given PDF document using PdfParser.
+ */
+void
+PdfInput::add_builder_page(std::shared_ptr<PDFDoc>pdf_doc, SvgBuilder *builder, SPDocument *doc, int page_num)
+{
+ // native importer
+ Catalog *catalog = pdf_doc->getCatalog();
+
+ int const num_pages = catalog->getNumPages();
+ Inkscape::XML::Node *prefs = builder->getPreferences();
+
+ // Check page exists
+ sanitize_page_number(page_num, num_pages);
+ Page *page = catalog->getPage(page_num);
+ if (!page) {
+ std::cerr << "PDFInput::open: error opening page " << page_num << std::endl;
+ return;
+ }
+
+ // Apply crop settings
+ _POPPLER_CONST PDFRectangle *clipToBox = nullptr;
+ double crop_setting = prefs->getAttributeDouble("cropTo", -1.0);
+
+ if (crop_setting >= 0.0) { // Do page clipping
+ int crop_choice = (int)crop_setting;
+ switch (crop_choice) {
+ case 0: // Media box
+ clipToBox = page->getMediaBox();
+ break;
+ case 1: // Crop box
+ clipToBox = page->getCropBox();
+ break;
+ case 2: // Bleed box
+ clipToBox = page->getBleedBox();
+ break;
+ case 3: // Trim box
+ clipToBox = page->getTrimBox();
+ break;
+ case 4: // Art box
+ clipToBox = page->getArtBox();
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Create parser (extension/internal/pdfinput/pdf-parser.h)
+ PdfParser *pdf_parser = new PdfParser(pdf_doc->getXRef(), builder, page_num-1, page->getRotate(),
+ page->getResourceDict(), page->getCropBox(), clipToBox);
+
+ // Set up approximation precision for parser. Used for converting Mesh Gradients into tiles.
+ double color_delta = prefs->getAttributeDouble("approximationPrecision", 2.0);
+ if ( color_delta <= 0.0 ) {
+ color_delta = 1.0 / 2.0;
+ } else {
+ color_delta = 1.0 / color_delta;
+ }
+ for ( int i = 1 ; i <= pdfNumShadingTypes ; i++ ) {
+ pdf_parser->setApproximationPrecision(i, color_delta, 6);
+ }
+
+ // Parse the document structure
+#if defined(POPPLER_NEW_OBJECT_API)
+ Object obj = page->getContents();
+#else
+ Object obj;
+ page->getContents(&obj);
+#endif
+ if (!obj.isNull()) {
+ pdf_parser->parse(&obj);
+ }
+
+ // Cleanup
+#if !defined(POPPLER_NEW_OBJECT_API)
+ obj.free();
+#endif
+ delete pdf_parser;
+}
+
+#include "../clear-n_.h"
+
+void PdfInput::init() {
+ /* PDF in */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("PDF Input") "</name>\n"
+ "<id>org.inkscape.input.pdf</id>\n"
+ "<input>\n"
+ "<extension>.pdf</extension>\n"
+ "<mimetype>application/pdf</mimetype>\n"
+ "<filetypename>" N_("Portable Document Format (*.pdf)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Portable Document Format") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>", new PdfInput());
+ // clang-format on
+
+ /* AI in */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("AI Input") "</name>\n"
+ "<id>org.inkscape.input.ai</id>\n"
+ "<input>\n"
+ "<extension>.ai</extension>\n"
+ "<mimetype>image/x-adobe-illustrator</mimetype>\n"
+ "<filetypename>" N_("Adobe Illustrator 9.0 and above (*.ai)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Open files saved in Adobe Illustrator 9.0 and newer versions") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>", new PdfInput());
+ // clang-format on
+} // init
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* HAVE_POPPLER */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/pdf-input.h b/src/extension/internal/pdfinput/pdf-input.h
new file mode 100644
index 0000000..fd97277
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-input.h
@@ -0,0 +1,170 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_EXTENSION_INTERNAL_PDFINPUT_H
+#define SEEN_EXTENSION_INTERNAL_PDFINPUT_H
+
+/*
+ * Authors:
+ * miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+#include "poppler-transition-api.h"
+
+#include <gtkmm/dialog.h>
+
+#include "../../implementation/implementation.h"
+#include "svg-builder.h"
+
+#ifdef HAVE_POPPLER_CAIRO
+struct _PopplerDocument;
+typedef struct _PopplerDocument PopplerDocument;
+#endif
+
+struct _GdkEventExpose;
+typedef _GdkEventExpose GdkEventExpose;
+
+class Page;
+class PDFDoc;
+
+namespace Gtk {
+ class Button;
+ class CheckButton;
+ class ComboBoxText;
+ class DrawingArea;
+ class Frame;
+ class Scale;
+ class RadioButton;
+ class Box;
+ class Label;
+}
+
+namespace Inkscape {
+
+namespace UI {
+namespace Widget {
+ class SpinButton;
+ class Frame;
+}
+}
+
+namespace Extension {
+namespace Internal {
+
+/**
+ * PDF import using libpoppler.
+ */
+class PdfImportDialog : public Gtk::Dialog
+{
+public:
+ PdfImportDialog(std::shared_ptr<PDFDoc> doc, const gchar *uri);
+ ~PdfImportDialog() override;
+
+ bool showDialog();
+ int getSelectedPage();
+ bool getImportMethod();
+ void getImportSettings(Inkscape::XML::Node *prefs);
+
+private:
+ void _setPreviewPage(int page);
+
+ // Signal handlers
+ bool _onDraw(const Cairo::RefPtr<Cairo::Context>& cr);
+ void _onPageNumberChanged();
+ void _onToggleAllPages();
+ void _onToggleCropping();
+ void _onPrecisionChanged();
+#ifdef HAVE_POPPLER_CAIRO
+ void _onToggleImport();
+#endif
+
+ class Gtk::Button * cancelbutton;
+ class Gtk::Button * okbutton;
+ class Gtk::Label * _labelSelect;
+ class Gtk::CheckButton *_pageAllPages;
+ class Inkscape::UI::Widget::SpinButton * _pageNumberSpin;
+ class Gtk::Label * _labelTotalPages;
+ class Gtk::Box * hbox2;
+ class Gtk::CheckButton * _cropCheck;
+ class Gtk::ComboBoxText * _cropTypeCombo;
+ class Gtk::Box * hbox3;
+ class Gtk::Box * vbox2;
+ class Inkscape::UI::Widget::Frame * _pageSettingsFrame;
+ class Gtk::Label * _labelPrecision;
+ class Gtk::Label * _labelPrecisionWarning;
+#ifdef HAVE_POPPLER_CAIRO
+ class Gtk::RadioButton * _importViaPoppler; // Use poppler_cairo importing
+ class Gtk::Label * _labelViaPoppler;
+ class Gtk::RadioButton * _importViaInternal; // Use native (poppler based) importing
+ class Gtk::Label * _labelViaInternal;
+#endif
+ Gtk::Scale * _fallbackPrecisionSlider;
+ Glib::RefPtr<Gtk::Adjustment> _fallbackPrecisionSlider_adj;
+ class Gtk::Label * _labelPrecisionComment;
+ class Gtk::Box * hbox6;
+#if 0
+ class Gtk::Label * _labelText;
+ class Gtk::ComboBoxText * _textHandlingCombo;
+ class Gtk::Box * hbox5;
+#endif
+ class Gtk::CheckButton * _localFontsCheck;
+ class Gtk::CheckButton * _embedImagesCheck;
+ class Gtk::Box * vbox3;
+ class Inkscape::UI::Widget::Frame * _importSettingsFrame;
+ class Gtk::Box * vbox1;
+ class Gtk::DrawingArea * _previewArea;
+ class Gtk::Box * hbox1;
+
+ std::shared_ptr<PDFDoc> _pdf_doc; // Document to be imported
+ int _current_page; // Current selected page
+ Page *_previewed_page; // Currently previewed page
+ unsigned char *_thumb_data; // Thumbnail image data
+ int _thumb_width, _thumb_height; // Thumbnail size
+ int _thumb_rowstride;
+ int _preview_width, _preview_height; // Size of the preview area
+ bool _render_thumb; // Whether we can/shall render thumbnails
+#ifdef HAVE_POPPLER_CAIRO
+ cairo_surface_t *_cairo_surface;
+ PopplerDocument *_poppler_doc;
+#endif
+};
+
+
+class PdfInput: public Inkscape::Extension::Implementation::Implementation {
+ PdfInput () = default;;
+public:
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+ static void init( );
+private:
+ void add_builder_page(
+ std::shared_ptr<PDFDoc> pdf_doc,
+ SvgBuilder *builder, SPDocument *doc,
+ int page_num);
+};
+
+} // namespace Implementation
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // HAVE_POPPLER
+
+#endif // SEEN_EXTENSION_INTERNAL_PDFINPUT_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/pdf-parser.cpp b/src/extension/internal/pdfinput/pdf-parser.cpp
new file mode 100644
index 0000000..d32a7fa
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-parser.cpp
@@ -0,0 +1,3368 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF parsing using libpoppler.
+ *//*
+ * Authors:
+ * Derived from poppler's Gfx.cc, which was derived from Xpdf by 1996-2003 Glyph & Cog, LLC
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+
+#ifdef USE_GCC_PRAGMAS
+#pragma implementation
+#endif
+
+#include <cstdlib>
+#include <cstdio>
+#include <cstddef>
+#include <cstring>
+#include <cmath>
+
+#include "svg-builder.h"
+#include "Gfx.h"
+#include "pdf-parser.h"
+#include "util/units.h"
+#include "poppler-transition-api.h"
+
+#include "glib/poppler-features.h"
+#include "goo/gmem.h"
+#include "goo/GooString.h"
+#include "GlobalParams.h"
+#include "CharTypes.h"
+#include "Object.h"
+#include "Array.h"
+#include "Dict.h"
+#include "Stream.h"
+#include "Lexer.h"
+#include "Parser.h"
+#include "GfxFont.h"
+#include "GfxState.h"
+#include "OutputDev.h"
+#include "Page.h"
+#include "Annot.h"
+#include "Error.h"
+
+// the MSVC math.h doesn't define this
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+//------------------------------------------------------------------------
+// constants
+//------------------------------------------------------------------------
+
+// Default max delta allowed in any color component for a shading fill.
+#define defaultShadingColorDelta (dblToCol( 1 / 2.0 ))
+
+// Default max recursive depth for a shading fill.
+#define defaultShadingMaxDepth 6
+
+// Max number of operators kept in the history list.
+#define maxOperatorHistoryDepth 16
+
+//------------------------------------------------------------------------
+// Operator table
+//------------------------------------------------------------------------
+
+PdfOperator PdfParser::opTab[] = {
+ {"\"", 3, {tchkNum, tchkNum, tchkString},
+ &PdfParser::opMoveSetShowText},
+ {"'", 1, {tchkString},
+ &PdfParser::opMoveShowText},
+ {"B", 0, {tchkNone},
+ &PdfParser::opFillStroke},
+ {"B*", 0, {tchkNone},
+ &PdfParser::opEOFillStroke},
+ {"BDC", 2, {tchkName, tchkProps},
+ &PdfParser::opBeginMarkedContent},
+ {"BI", 0, {tchkNone},
+ &PdfParser::opBeginImage},
+ {"BMC", 1, {tchkName},
+ &PdfParser::opBeginMarkedContent},
+ {"BT", 0, {tchkNone},
+ &PdfParser::opBeginText},
+ {"BX", 0, {tchkNone},
+ &PdfParser::opBeginIgnoreUndef},
+ {"CS", 1, {tchkName},
+ &PdfParser::opSetStrokeColorSpace},
+ {"DP", 2, {tchkName, tchkProps},
+ &PdfParser::opMarkPoint},
+ {"Do", 1, {tchkName},
+ &PdfParser::opXObject},
+ {"EI", 0, {tchkNone},
+ &PdfParser::opEndImage},
+ {"EMC", 0, {tchkNone},
+ &PdfParser::opEndMarkedContent},
+ {"ET", 0, {tchkNone},
+ &PdfParser::opEndText},
+ {"EX", 0, {tchkNone},
+ &PdfParser::opEndIgnoreUndef},
+ {"F", 0, {tchkNone},
+ &PdfParser::opFill},
+ {"G", 1, {tchkNum},
+ &PdfParser::opSetStrokeGray},
+ {"ID", 0, {tchkNone},
+ &PdfParser::opImageData},
+ {"J", 1, {tchkInt},
+ &PdfParser::opSetLineCap},
+ {"K", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetStrokeCMYKColor},
+ {"M", 1, {tchkNum},
+ &PdfParser::opSetMiterLimit},
+ {"MP", 1, {tchkName},
+ &PdfParser::opMarkPoint},
+ {"Q", 0, {tchkNone},
+ &PdfParser::opRestore},
+ {"RG", 3, {tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetStrokeRGBColor},
+ {"S", 0, {tchkNone},
+ &PdfParser::opStroke},
+ {"SC", -4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetStrokeColor},
+ {"SCN", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN,},
+ &PdfParser::opSetStrokeColorN},
+ {"T*", 0, {tchkNone},
+ &PdfParser::opTextNextLine},
+ {"TD", 2, {tchkNum, tchkNum},
+ &PdfParser::opTextMoveSet},
+ {"TJ", 1, {tchkArray},
+ &PdfParser::opShowSpaceText},
+ {"TL", 1, {tchkNum},
+ &PdfParser::opSetTextLeading},
+ {"Tc", 1, {tchkNum},
+ &PdfParser::opSetCharSpacing},
+ {"Td", 2, {tchkNum, tchkNum},
+ &PdfParser::opTextMove},
+ {"Tf", 2, {tchkName, tchkNum},
+ &PdfParser::opSetFont},
+ {"Tj", 1, {tchkString},
+ &PdfParser::opShowText},
+ {"Tm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opSetTextMatrix},
+ {"Tr", 1, {tchkInt},
+ &PdfParser::opSetTextRender},
+ {"Ts", 1, {tchkNum},
+ &PdfParser::opSetTextRise},
+ {"Tw", 1, {tchkNum},
+ &PdfParser::opSetWordSpacing},
+ {"Tz", 1, {tchkNum},
+ &PdfParser::opSetHorizScaling},
+ {"W", 0, {tchkNone},
+ &PdfParser::opClip},
+ {"W*", 0, {tchkNone},
+ &PdfParser::opEOClip},
+ {"b", 0, {tchkNone},
+ &PdfParser::opCloseFillStroke},
+ {"b*", 0, {tchkNone},
+ &PdfParser::opCloseEOFillStroke},
+ {"c", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opCurveTo},
+ {"cm", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opConcat},
+ {"cs", 1, {tchkName},
+ &PdfParser::opSetFillColorSpace},
+ {"d", 2, {tchkArray, tchkNum},
+ &PdfParser::opSetDash},
+ {"d0", 2, {tchkNum, tchkNum},
+ &PdfParser::opSetCharWidth},
+ {"d1", 6, {tchkNum, tchkNum, tchkNum, tchkNum,
+ tchkNum, tchkNum},
+ &PdfParser::opSetCacheDevice},
+ {"f", 0, {tchkNone},
+ &PdfParser::opFill},
+ {"f*", 0, {tchkNone},
+ &PdfParser::opEOFill},
+ {"g", 1, {tchkNum},
+ &PdfParser::opSetFillGray},
+ {"gs", 1, {tchkName},
+ &PdfParser::opSetExtGState},
+ {"h", 0, {tchkNone},
+ &PdfParser::opClosePath},
+ {"i", 1, {tchkNum},
+ &PdfParser::opSetFlat},
+ {"j", 1, {tchkInt},
+ &PdfParser::opSetLineJoin},
+ {"k", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetFillCMYKColor},
+ {"l", 2, {tchkNum, tchkNum},
+ &PdfParser::opLineTo},
+ {"m", 2, {tchkNum, tchkNum},
+ &PdfParser::opMoveTo},
+ {"n", 0, {tchkNone},
+ &PdfParser::opEndPath},
+ {"q", 0, {tchkNone},
+ &PdfParser::opSave},
+ {"re", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opRectangle},
+ {"rg", 3, {tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetFillRGBColor},
+ {"ri", 1, {tchkName},
+ &PdfParser::opSetRenderingIntent},
+ {"s", 0, {tchkNone},
+ &PdfParser::opCloseStroke},
+ {"sc", -4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opSetFillColor},
+ {"scn", -33, {tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN, tchkSCN, tchkSCN, tchkSCN,
+ tchkSCN,},
+ &PdfParser::opSetFillColorN},
+ {"sh", 1, {tchkName},
+ &PdfParser::opShFill},
+ {"v", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opCurveTo1},
+ {"w", 1, {tchkNum},
+ &PdfParser::opSetLineWidth},
+ {"y", 4, {tchkNum, tchkNum, tchkNum, tchkNum},
+ &PdfParser::opCurveTo2}
+};
+
+#define numOps (sizeof(opTab) / sizeof(PdfOperator))
+
+namespace {
+
+GfxPatch blankPatch()
+{
+ GfxPatch patch;
+ memset(&patch, 0, sizeof(patch)); // quick-n-dirty
+ return patch;
+}
+
+} // namespace
+
+//------------------------------------------------------------------------
+// ClipHistoryEntry
+//------------------------------------------------------------------------
+
+class ClipHistoryEntry {
+public:
+
+ ClipHistoryEntry(GfxPath *clipPath = nullptr, GfxClipType clipType = clipNormal);
+ virtual ~ClipHistoryEntry();
+
+ // Manipulate clip path stack
+ ClipHistoryEntry *save();
+ ClipHistoryEntry *restore();
+ GBool hasSaves() { return saved != nullptr; }
+ void setClip(_POPPLER_CONST_83 GfxPath *newClipPath, GfxClipType newClipType = clipNormal);
+ GfxPath *getClipPath() { return clipPath; }
+ GfxClipType getClipType() { return clipType; }
+
+private:
+
+ ClipHistoryEntry *saved; // next clip path on stack
+
+ GfxPath *clipPath; // used as the path to be filled for an 'sh' operator
+ GfxClipType clipType;
+
+ ClipHistoryEntry(ClipHistoryEntry *other);
+};
+
+//------------------------------------------------------------------------
+// PdfParser
+//------------------------------------------------------------------------
+
+PdfParser::PdfParser(XRef *xrefA,
+ Inkscape::Extension::Internal::SvgBuilder *builderA,
+ int /*pageNum*/,
+ int rotate,
+ Dict *resDict,
+ _POPPLER_CONST PDFRectangle *box,
+ _POPPLER_CONST PDFRectangle *cropBox) :
+ xref(xrefA),
+ builder(builderA),
+ subPage(gFalse),
+ printCommands(false),
+ res(new GfxResources(xref, resDict, nullptr)), // start the resource stack
+ state(new GfxState(72.0, 72.0, box, rotate, gTrue)),
+ fontChanged(gFalse),
+ clip(clipNone),
+ ignoreUndef(0),
+ baseMatrix(),
+ formDepth(0),
+ parser(nullptr),
+ colorDeltas(),
+ maxDepths(),
+ clipHistory(new ClipHistoryEntry()),
+ operatorHistory(nullptr)
+{
+ setDefaultApproximationPrecision();
+ builder->setDocumentSize(Inkscape::Util::Quantity::convert(state->getPageWidth(), "pt", "px"),
+ Inkscape::Util::Quantity::convert(state->getPageHeight(), "pt", "px"));
+
+ const double *ctm = state->getCTM();
+ double scaledCTM[6];
+ for (int i = 0; i < 6; ++i) {
+ baseMatrix[i] = ctm[i];
+ scaledCTM[i] = Inkscape::Util::Quantity::convert(ctm[i], "pt", "px");
+ }
+ saveState();
+ builder->setTransform((double*)&scaledCTM);
+ formDepth = 0;
+
+ // set crop box
+ if (cropBox) {
+ if (printCommands)
+ printf("cropBox: %f %f %f %f\n", cropBox->x1, cropBox->y1, cropBox->x2, cropBox->y2);
+ // do not clip if it's not needed
+ if (cropBox->x1 != 0.0 || cropBox->y1 != 0.0 ||
+ cropBox->x2 != state->getPageWidth() || cropBox->y2 != state->getPageHeight()) {
+
+ state->moveTo(cropBox->x1, cropBox->y1);
+ state->lineTo(cropBox->x2, cropBox->y1);
+ state->lineTo(cropBox->x2, cropBox->y2);
+ state->lineTo(cropBox->x1, cropBox->y2);
+ state->closePath();
+ state->clip();
+ clipHistory->setClip(state->getPath(), clipNormal);
+ builder->setClipPath(state);
+ state->clearPath();
+ }
+ }
+ pushOperator("startPage");
+}
+
+PdfParser::PdfParser(XRef *xrefA,
+ Inkscape::Extension::Internal::SvgBuilder *builderA,
+ Dict *resDict,
+ _POPPLER_CONST PDFRectangle *box) :
+ xref(xrefA),
+ builder(builderA),
+ subPage(gTrue),
+ printCommands(false),
+ res(new GfxResources(xref, resDict, nullptr)), // start the resource stack
+ state(new GfxState(72, 72, box, 0, gFalse)),
+ fontChanged(gFalse),
+ clip(clipNone),
+ ignoreUndef(0),
+ baseMatrix(),
+ formDepth(0),
+ parser(nullptr),
+ colorDeltas(),
+ maxDepths(),
+ clipHistory(new ClipHistoryEntry()),
+ operatorHistory(nullptr)
+{
+ setDefaultApproximationPrecision();
+
+ for (int i = 0; i < 6; ++i) {
+ baseMatrix[i] = state->getCTM()[i];
+ }
+ formDepth = 0;
+}
+
+PdfParser::~PdfParser() {
+ while(operatorHistory) {
+ OpHistoryEntry *tmp = operatorHistory->next;
+ delete operatorHistory;
+ operatorHistory = tmp;
+ }
+
+ while (state && state->hasSaves()) {
+ restoreState();
+ }
+
+ if (!subPage) {
+ //out->endPage();
+ }
+
+ while (res) {
+ popResources();
+ }
+
+ if (state) {
+ delete state;
+ state = nullptr;
+ }
+
+ if (clipHistory) {
+ delete clipHistory;
+ clipHistory = nullptr;
+ }
+}
+
+void PdfParser::parse(Object *obj, GBool topLevel) {
+ Object obj2;
+
+ if (obj->isArray()) {
+ for (int i = 0; i < obj->arrayGetLength(); ++i) {
+ _POPPLER_CALL_ARGS(obj2, obj->arrayGet, i);
+ if (!obj2.isStream()) {
+ error(errInternal, -1, "Weird page contents");
+ _POPPLER_FREE(obj2);
+ return;
+ }
+ _POPPLER_FREE(obj2);
+ }
+ } else if (!obj->isStream()) {
+ error(errInternal, -1, "Weird page contents");
+ return;
+ }
+ parser = new _POPPLER_NEW_PARSER(xref, obj);
+ go(topLevel);
+ delete parser;
+ parser = nullptr;
+}
+
+void PdfParser::go(GBool /*topLevel*/)
+{
+ Object obj;
+ Object args[maxArgs];
+
+ // scan a sequence of objects
+ int numArgs = 0;
+ _POPPLER_CALL(obj, parser->getObj);
+ while (!obj.isEOF()) {
+
+ // got a command - execute it
+ if (obj.isCmd()) {
+ if (printCommands) {
+ obj.print(stdout);
+ for (int i = 0; i < numArgs; ++i) {
+ printf(" ");
+ args[i].print(stdout);
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+
+ // Run the operation
+ execOp(&obj, args, numArgs);
+
+#if !defined(POPPLER_NEW_OBJECT_API)
+ _POPPLER_FREE(obj);
+ for (int i = 0; i < numArgs; ++i)
+ _POPPLER_FREE(args[i]);
+#endif
+ numArgs = 0;
+
+ // got an argument - save it
+ } else if (numArgs < maxArgs) {
+ args[numArgs++] = std::move(obj);
+
+ // too many arguments - something is wrong
+ } else {
+ error(errSyntaxError, getPos(), "Too many args in content stream");
+ if (printCommands) {
+ printf("throwing away arg: ");
+ obj.print(stdout);
+ printf("\n");
+ fflush(stdout);
+ }
+ _POPPLER_FREE(obj);
+ }
+
+ // grab the next object
+ _POPPLER_CALL(obj, parser->getObj);
+ }
+ _POPPLER_FREE(obj);
+
+ // args at end with no command
+ if (numArgs > 0) {
+ error(errSyntaxError, getPos(), "Leftover args in content stream");
+ if (printCommands) {
+ printf("%d leftovers:", numArgs);
+ for (int i = 0; i < numArgs; ++i) {
+ printf(" ");
+ args[i].print(stdout);
+ }
+ printf("\n");
+ fflush(stdout);
+ }
+#if !defined(POPPLER_NEW_OBJECT_API)
+ for (int i = 0; i < numArgs; ++i)
+ _POPPLER_FREE(args[i]);
+#endif
+ }
+}
+
+void PdfParser::pushOperator(const char *name)
+{
+ OpHistoryEntry *newEntry = new OpHistoryEntry;
+ newEntry->name = name;
+ newEntry->state = nullptr;
+ newEntry->depth = (operatorHistory != nullptr ? (operatorHistory->depth+1) : 0);
+ newEntry->next = operatorHistory;
+ operatorHistory = newEntry;
+
+ // Truncate list if needed
+ if (operatorHistory->depth > maxOperatorHistoryDepth) {
+ OpHistoryEntry *curr = operatorHistory;
+ OpHistoryEntry *prev = nullptr;
+ while (curr && curr->next != nullptr) {
+ curr->depth--;
+ prev = curr;
+ curr = curr->next;
+ }
+ if (prev) {
+ if (curr->state != nullptr)
+ delete curr->state;
+ delete curr;
+ prev->next = nullptr;
+ }
+ }
+}
+
+const char *PdfParser::getPreviousOperator(unsigned int look_back) {
+ OpHistoryEntry *prev = nullptr;
+ if (operatorHistory != nullptr && look_back > 0) {
+ prev = operatorHistory->next;
+ while (--look_back > 0 && prev != nullptr) {
+ prev = prev->next;
+ }
+ }
+ if (prev != nullptr) {
+ return prev->name;
+ } else {
+ return "";
+ }
+}
+
+void PdfParser::execOp(Object *cmd, Object args[], int numArgs) {
+ PdfOperator *op;
+ const char *name;
+ Object *argPtr;
+ int i;
+
+ // find operator
+ name = cmd->getCmd();
+ if (!(op = findOp(name))) {
+ if (ignoreUndef == 0)
+ error(errSyntaxError, getPos(), "Unknown operator '{0:s}'", name);
+ return;
+ }
+
+ // type check args
+ argPtr = args;
+ if (op->numArgs >= 0) {
+ if (numArgs < op->numArgs) {
+ error(errSyntaxError, getPos(), "Too few ({0:d}) args to '{1:d}' operator", numArgs, name);
+ return;
+ }
+ if (numArgs > op->numArgs) {
+#if 0
+ error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator", numArgs, name);
+#endif
+ argPtr += numArgs - op->numArgs;
+ numArgs = op->numArgs;
+ }
+ } else {
+ if (numArgs > -op->numArgs) {
+ error(errSyntaxError, getPos(), "Too many ({0:d}) args to '{1:s}' operator",
+ numArgs, name);
+ return;
+ }
+ }
+ for (i = 0; i < numArgs; ++i) {
+ if (!checkArg(&argPtr[i], op->tchk[i])) {
+ error(errSyntaxError, getPos(), "Arg #{0:d} to '{1:s}' operator is wrong type ({2:s})",
+ i, name, argPtr[i].getTypeName());
+ return;
+ }
+ }
+
+ // add to history
+ pushOperator((char*)&op->name);
+
+ // do it
+ (this->*op->func)(argPtr, numArgs);
+}
+
+PdfOperator* PdfParser::findOp(const char *name) {
+ int a = -1;
+ int b = numOps;
+ int cmp = -1;
+ // invariant: opTab[a] < name < opTab[b]
+ while (b - a > 1) {
+ const int m = (a + b) / 2;
+ cmp = strcmp(opTab[m].name, name);
+ if (cmp < 0)
+ a = m;
+ else if (cmp > 0)
+ b = m;
+ else
+ a = b = m;
+ }
+ if (cmp != 0)
+ return nullptr;
+ return &opTab[a];
+}
+
+GBool PdfParser::checkArg(Object *arg, TchkType type) {
+ switch (type) {
+ case tchkBool: return arg->isBool();
+ case tchkInt: return arg->isInt();
+ case tchkNum: return arg->isNum();
+ case tchkString: return arg->isString();
+ case tchkName: return arg->isName();
+ case tchkArray: return arg->isArray();
+ case tchkProps: return arg->isDict() || arg->isName();
+ case tchkSCN: return arg->isNum() || arg->isName();
+ case tchkNone: return gFalse;
+ }
+ return gFalse;
+}
+
+int PdfParser::getPos() {
+ return parser ? parser->getPos() : -1;
+}
+
+//------------------------------------------------------------------------
+// graphics state operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSave(Object /*args*/[], int /*numArgs*/)
+{
+ saveState();
+}
+
+void PdfParser::opRestore(Object /*args*/[], int /*numArgs*/)
+{
+ restoreState();
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opConcat(Object args[], int /*numArgs*/)
+{
+ state->concatCTM(args[0].getNum(), args[1].getNum(),
+ args[2].getNum(), args[3].getNum(),
+ args[4].getNum(), args[5].getNum());
+ const char *prevOp = getPreviousOperator();
+ double a0 = args[0].getNum();
+ double a1 = args[1].getNum();
+ double a2 = args[2].getNum();
+ double a3 = args[3].getNum();
+ double a4 = args[4].getNum();
+ double a5 = args[5].getNum();
+ if (!strcmp(prevOp, "q")) {
+ builder->setTransform(a0, a1, a2, a3, a4, a5);
+ } else if (!strcmp(prevOp, "cm") || !strcmp(prevOp, "startPage")) {
+ // multiply it with the previous transform
+ double otherMatrix[6];
+ if (!builder->getTransform(otherMatrix)) { // invalid transform
+ // construct identity matrix
+ otherMatrix[0] = otherMatrix[3] = 1.0;
+ otherMatrix[1] = otherMatrix[2] = otherMatrix[4] = otherMatrix[5] = 0.0;
+ }
+ double c0 = a0*otherMatrix[0] + a1*otherMatrix[2];
+ double c1 = a0*otherMatrix[1] + a1*otherMatrix[3];
+ double c2 = a2*otherMatrix[0] + a3*otherMatrix[2];
+ double c3 = a2*otherMatrix[1] + a3*otherMatrix[3];
+ double c4 = a4*otherMatrix[0] + a5*otherMatrix[2] + otherMatrix[4];
+ double c5 = a4*otherMatrix[1] + a5*otherMatrix[3] + otherMatrix[5];
+ builder->setTransform(c0, c1, c2, c3, c4, c5);
+ } else {
+ builder->pushGroup();
+ builder->setTransform(a0, a1, a2, a3, a4, a5);
+ }
+ fontChanged = gTrue;
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetDash(Object args[], int /*numArgs*/)
+{
+ double *dash = nullptr;
+
+ Array *a = args[0].getArray();
+ int length = a->getLength();
+ if (length != 0) {
+ dash = (double *)gmallocn(length, sizeof(double));
+ for (int i = 0; i < length; ++i) {
+ Object obj;
+ dash[i] = _POPPLER_CALL_ARGS_DEREF(obj, a->get, i).getNum();
+ _POPPLER_FREE(obj);
+ }
+ }
+#if POPPLER_CHECK_VERSION(22, 9, 0)
+ state->setLineDash(std::vector<double> (dash, dash + length), args[1].getNum());
+#else
+ state->setLineDash(dash, length, args[1].getNum());
+#endif
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFlat(Object args[], int /*numArgs*/)
+{
+ state->setFlatness((int)args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetLineJoin(Object args[], int /*numArgs*/)
+{
+ state->setLineJoin(args[0].getInt());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetLineCap(Object args[], int /*numArgs*/)
+{
+ state->setLineCap(args[0].getInt());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetMiterLimit(Object args[], int /*numArgs*/)
+{
+ state->setMiterLimit(args[0].getNum());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetLineWidth(Object args[], int /*numArgs*/)
+{
+ state->setLineWidth(args[0].getNum());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetExtGState(Object args[], int /*numArgs*/)
+{
+ Object obj1, obj2, obj3, obj4, obj5;
+ Function *funcs[4] = {nullptr, nullptr, nullptr, nullptr};
+ GfxColor backdropColor;
+ GBool haveBackdropColor = gFalse;
+ GBool alpha = gFalse;
+
+ _POPPLER_CALL_ARGS(obj1, res->lookupGState, args[0].getName());
+ if (obj1.isNull()) {
+ return;
+ }
+ if (!obj1.isDict()) {
+ error(errSyntaxError, getPos(), "ExtGState '{0:s}' is wrong type"), args[0].getName();
+ _POPPLER_FREE(obj1);
+ return;
+ }
+ if (printCommands) {
+ printf(" gfx state dict: ");
+ obj1.print();
+ printf("\n");
+ }
+
+ // transparency support: blend mode, fill/stroke opacity
+ if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "BM").isNull()) {
+ GfxBlendMode mode = gfxBlendNormal;
+ if (state->parseBlendMode(&obj2, &mode)) {
+ state->setBlendMode(mode);
+ } else {
+ error(errSyntaxError, getPos(), "Invalid blend mode in ExtGState");
+ }
+ }
+ _POPPLER_FREE(obj2);
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "ca").isNum()) {
+ state->setFillOpacity(obj2.getNum());
+ }
+ _POPPLER_FREE(obj2);
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "CA").isNum()) {
+ state->setStrokeOpacity(obj2.getNum());
+ }
+ _POPPLER_FREE(obj2);
+
+ // fill/stroke overprint
+ GBool haveFillOP = gFalse;
+ if ((haveFillOP = _POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "op").isBool())) {
+ state->setFillOverprint(obj2.getBool());
+ }
+ _POPPLER_FREE(obj2);
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "OP").isBool()) {
+ state->setStrokeOverprint(obj2.getBool());
+ if (!haveFillOP) {
+ state->setFillOverprint(obj2.getBool());
+ }
+ }
+ _POPPLER_FREE(obj2);
+
+ // stroke adjust
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SA").isBool()) {
+ state->setStrokeAdjust(obj2.getBool());
+ }
+ _POPPLER_FREE(obj2);
+
+ // transfer function
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "TR2").isNull()) {
+ _POPPLER_FREE(obj2);
+ _POPPLER_CALL_ARGS(obj2, obj1.dictLookup, "TR");
+ }
+ if (obj2.isName(const_cast<char*>("Default")) ||
+ obj2.isName(const_cast<char*>("Identity"))) {
+ funcs[0] = funcs[1] = funcs[2] = funcs[3] = nullptr;
+ state->setTransfer(funcs);
+ } else if (obj2.isArray() && obj2.arrayGetLength() == 4) {
+ int pos = 4;
+ for (int i = 0; i < 4; ++i) {
+ _POPPLER_CALL_ARGS(obj3, obj2.arrayGet, i);
+ funcs[i] = Function::parse(&obj3);
+ _POPPLER_FREE(obj3);
+ if (!funcs[i]) {
+ pos = i;
+ break;
+ }
+ }
+ if (pos == 4) {
+ state->setTransfer(funcs);
+ }
+ } else if (obj2.isName() || obj2.isDict() || obj2.isStream()) {
+ if ((funcs[0] = Function::parse(&obj2))) {
+ funcs[1] = funcs[2] = funcs[3] = nullptr;
+ state->setTransfer(funcs);
+ }
+ } else if (!obj2.isNull()) {
+ error(errSyntaxError, getPos(), "Invalid transfer function in ExtGState");
+ }
+ _POPPLER_FREE(obj2);
+
+ // soft mask
+ if (!_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "SMask").isNull()) {
+ if (obj2.isName(const_cast<char*>("None"))) {
+ builder->clearSoftMask(state);
+ } else if (obj2.isDict()) {
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "S").isName("Alpha")) {
+ alpha = gTrue;
+ } else { // "Luminosity"
+ alpha = gFalse;
+ }
+ _POPPLER_FREE(obj3);
+ funcs[0] = nullptr;
+ if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "TR").isNull()) {
+ funcs[0] = Function::parse(&obj3);
+ if (funcs[0]->getInputSize() != 1 ||
+ funcs[0]->getOutputSize() != 1) {
+ error(errSyntaxError, getPos(), "Invalid transfer function in soft mask in ExtGState");
+ delete funcs[0];
+ funcs[0] = nullptr;
+ }
+ }
+ _POPPLER_FREE(obj3);
+ if ((haveBackdropColor = _POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "BC").isArray())) {
+ for (int & i : backdropColor.c) {
+ i = 0;
+ }
+ for (int i = 0; i < obj3.arrayGetLength() && i < gfxColorMaxComps; ++i) {
+ _POPPLER_CALL_ARGS(obj4, obj3.arrayGet, i);
+ if (obj4.isNum()) {
+ backdropColor.c[i] = dblToCol(obj4.getNum());
+ }
+ _POPPLER_FREE(obj4);
+ }
+ }
+ _POPPLER_FREE(obj3);
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj2.dictLookup, "G").isStream()) {
+ if (_POPPLER_CALL_ARGS_DEREF(obj4, obj3.streamGetDict()->lookup, "Group").isDict()) {
+ GfxColorSpace *blendingColorSpace = nullptr;
+ GBool isolated = gFalse;
+ GBool knockout = gFalse;
+ if (!_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "CS").isNull()) {
+ blendingColorSpace = GfxColorSpace::parse(nullptr, &obj5, nullptr, state);
+ }
+ _POPPLER_FREE(obj5);
+ if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "I").isBool()) {
+ isolated = obj5.getBool();
+ }
+ _POPPLER_FREE(obj5);
+ if (_POPPLER_CALL_ARGS_DEREF(obj5, obj4.dictLookup, "K").isBool()) {
+ knockout = obj5.getBool();
+ }
+ _POPPLER_FREE(obj5);
+ if (!haveBackdropColor) {
+ if (blendingColorSpace) {
+ blendingColorSpace->getDefaultColor(&backdropColor);
+ } else {
+ //~ need to get the parent or default color space (?)
+ for (int & i : backdropColor.c) {
+ i = 0;
+ }
+ }
+ }
+ doSoftMask(&obj3, alpha, blendingColorSpace,
+ isolated, knockout, funcs[0], &backdropColor);
+ if (funcs[0]) {
+ delete funcs[0];
+ }
+ } else {
+ error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
+ }
+ _POPPLER_FREE(obj4);
+ } else {
+ error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState - missing group");
+ }
+ _POPPLER_FREE(obj3);
+ } else if (!obj2.isNull()) {
+ error(errSyntaxError, getPos(), "Invalid soft mask in ExtGState");
+ }
+ }
+ _POPPLER_FREE(obj2);
+
+ _POPPLER_FREE(obj1);
+}
+
+void PdfParser::doSoftMask(Object *str, GBool alpha,
+ GfxColorSpace *blendingColorSpace,
+ GBool isolated, GBool knockout,
+ Function *transferFunc, GfxColor *backdropColor) {
+ Dict *dict, *resDict;
+ double m[6], bbox[4];
+ Object obj1, obj2;
+ int i;
+
+ // check for excessive recursion
+ if (formDepth > 20) {
+ return;
+ }
+
+ // get stream dict
+ dict = str->streamGetDict();
+
+ // check form type
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType");
+ if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+ error(errSyntaxError, getPos(), "Unknown form type");
+ }
+ _POPPLER_FREE(obj1);
+
+ // get bounding box
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "BBox");
+ if (!obj1.isArray()) {
+ _POPPLER_FREE(obj1);
+ error(errSyntaxError, getPos(), "Bad form bounding box");
+ return;
+ }
+ for (i = 0; i < 4; ++i) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i);
+ bbox[i] = obj2.getNum();
+ _POPPLER_FREE(obj2);
+ }
+ _POPPLER_FREE(obj1);
+
+ // get matrix
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Matrix");
+ if (obj1.isArray()) {
+ for (i = 0; i < 6; ++i) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, i);
+ m[i] = obj2.getNum();
+ _POPPLER_FREE(obj2);
+ }
+ } else {
+ m[0] = 1; m[1] = 0;
+ m[2] = 0; m[3] = 1;
+ m[4] = 0; m[5] = 0;
+ }
+ _POPPLER_FREE(obj1);
+
+ // get resources
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Resources");
+ resDict = obj1.isDict() ? obj1.getDict() : (Dict *)nullptr;
+
+ // draw it
+ ++formDepth;
+ doForm1(str, resDict, m, bbox, gTrue, gTrue,
+ blendingColorSpace, isolated, knockout,
+ alpha, transferFunc, backdropColor);
+ --formDepth;
+
+ if (blendingColorSpace) {
+ delete blendingColorSpace;
+ }
+ _POPPLER_FREE(obj1);
+}
+
+void PdfParser::opSetRenderingIntent(Object /*args*/[], int /*numArgs*/)
+{
+}
+
+//------------------------------------------------------------------------
+// color operators
+//------------------------------------------------------------------------
+
+/**
+ * Get a newly allocated color space instance by CS operation argument.
+ *
+ * Maintains a cache for named color spaces to avoid expensive re-parsing.
+ */
+GfxColorSpace *PdfParser::lookupColorSpaceCopy(Object &arg)
+{
+ assert(!arg.isNull());
+
+ char const *name = arg.isName() ? arg.getName() : nullptr;
+ GfxColorSpace *colorSpace = nullptr;
+
+ if (name && (colorSpace = colorSpacesCache[name].get())) {
+ return colorSpace->copy();
+ }
+
+ Object *argPtr = &arg;
+ Object obj;
+
+ if (name) {
+ _POPPLER_CALL_ARGS(obj, res->lookupColorSpace, name);
+ if (!obj.isNull()) {
+ argPtr = &obj;
+ }
+ }
+
+ colorSpace = GfxColorSpace::parse(res, argPtr, nullptr, state);
+
+ _POPPLER_FREE(obj);
+
+ if (name && colorSpace) {
+ colorSpacesCache[name].reset(colorSpace->copy());
+ }
+
+ return colorSpace;
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillGray(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setFillPattern(nullptr);
+ state->setFillColorSpace(new GfxDeviceGrayColorSpace());
+ color.c[0] = dblToCol(args[0].getNum());
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeGray(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setStrokePattern(nullptr);
+ state->setStrokeColorSpace(new GfxDeviceGrayColorSpace());
+ color.c[0] = dblToCol(args[0].getNum());
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillCMYKColor(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+ int i;
+
+ state->setFillPattern(nullptr);
+ state->setFillColorSpace(new GfxDeviceCMYKColorSpace());
+ for (i = 0; i < 4; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeCMYKColor(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setStrokePattern(nullptr);
+ state->setStrokeColorSpace(new GfxDeviceCMYKColorSpace());
+ for (int i = 0; i < 4; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillRGBColor(Object args[], int /*numArgs*/)
+{
+ GfxColor color;
+
+ state->setFillPattern(nullptr);
+ state->setFillColorSpace(new GfxDeviceRGBColorSpace());
+ for (int i = 0; i < 3; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeRGBColor(Object args[], int /*numArgs*/) {
+ GfxColor color;
+
+ state->setStrokePattern(nullptr);
+ state->setStrokeColorSpace(new GfxDeviceRGBColorSpace());
+ for (int i = 0; i < 3; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFillColorSpace(Object args[], int numArgs)
+{
+ assert(numArgs >= 1);
+ GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]);
+
+ state->setFillPattern(nullptr);
+
+ if (colorSpace) {
+ GfxColor color;
+ state->setFillColorSpace(colorSpace);
+ colorSpace->getDefaultColor(&color);
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+ } else {
+ error(errSyntaxError, getPos(), "Bad color space (fill)");
+ }
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetStrokeColorSpace(Object args[], int numArgs)
+{
+ assert(numArgs >= 1);
+ GfxColorSpace *colorSpace = lookupColorSpaceCopy(args[0]);
+
+ state->setStrokePattern(nullptr);
+
+ if (colorSpace) {
+ GfxColor color;
+ state->setStrokeColorSpace(colorSpace);
+ colorSpace->getDefaultColor(&color);
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+ } else {
+ error(errSyntaxError, getPos(), "Bad color space (stroke)");
+ }
+}
+
+void PdfParser::opSetFillColor(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (numArgs != state->getFillColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'sc' command");
+ return;
+ }
+ state->setFillPattern(nullptr);
+ for (i = 0; i < numArgs; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+}
+
+void PdfParser::opSetStrokeColor(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SC' command");
+ return;
+ }
+ state->setStrokePattern(nullptr);
+ for (i = 0; i < numArgs; ++i) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+}
+
+void PdfParser::opSetFillColorN(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (state->getFillColorSpace()->getMode() == csPattern) {
+ if (numArgs > 1) {
+ if (!((GfxPatternColorSpace *)state->getFillColorSpace())->getUnder() ||
+ numArgs - 1 != ((GfxPatternColorSpace *)state->getFillColorSpace())
+ ->getUnder()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
+ return;
+ }
+ for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+ }
+ GfxPattern *pattern;
+ if (args[numArgs-1].isName() &&
+ (pattern = res->lookupPattern(args[numArgs-1].getName(), nullptr, state))) {
+ state->setFillPattern(pattern);
+ builder->updateStyle(state);
+ }
+
+ } else {
+ if (numArgs != state->getFillColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'scn' command");
+ return;
+ }
+ state->setFillPattern(nullptr);
+ for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setFillColor(&color);
+ builder->updateStyle(state);
+ }
+}
+
+void PdfParser::opSetStrokeColorN(Object args[], int numArgs) {
+ GfxColor color;
+ int i;
+
+ if (state->getStrokeColorSpace()->getMode() == csPattern) {
+ if (numArgs > 1) {
+ if (!((GfxPatternColorSpace *)state->getStrokeColorSpace())
+ ->getUnder() ||
+ numArgs - 1 != ((GfxPatternColorSpace *)state->getStrokeColorSpace())
+ ->getUnder()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
+ return;
+ }
+ for (i = 0; i < numArgs - 1 && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+ }
+ GfxPattern *pattern;
+ if (args[numArgs-1].isName() &&
+ (pattern = res->lookupPattern(args[numArgs-1].getName(), nullptr, state))) {
+ state->setStrokePattern(pattern);
+ builder->updateStyle(state);
+ }
+
+ } else {
+ if (numArgs != state->getStrokeColorSpace()->getNComps()) {
+ error(errSyntaxError, getPos(), "Incorrect number of arguments in 'SCN' command");
+ return;
+ }
+ state->setStrokePattern(nullptr);
+ for (i = 0; i < numArgs && i < gfxColorMaxComps; ++i) {
+ if (args[i].isNum()) {
+ color.c[i] = dblToCol(args[i].getNum());
+ }
+ }
+ state->setStrokeColor(&color);
+ builder->updateStyle(state);
+ }
+}
+
+//------------------------------------------------------------------------
+// path segment operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opMoveTo(Object args[], int /*numArgs*/)
+{
+ state->moveTo(args[0].getNum(), args[1].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opLineTo(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in lineto");
+ return;
+ }
+ state->lineTo(args[0].getNum(), args[1].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opCurveTo(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in curveto");
+ return;
+ }
+ double x1 = args[0].getNum();
+ double y1 = args[1].getNum();
+ double x2 = args[2].getNum();
+ double y2 = args[3].getNum();
+ double x3 = args[4].getNum();
+ double y3 = args[5].getNum();
+ state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opCurveTo1(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in curveto1");
+ return;
+ }
+ double x1 = state->getCurX();
+ double y1 = state->getCurY();
+ double x2 = args[0].getNum();
+ double y2 = args[1].getNum();
+ double x3 = args[2].getNum();
+ double y3 = args[3].getNum();
+ state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opCurveTo2(Object args[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in curveto2");
+ return;
+ }
+ double x1 = args[0].getNum();
+ double y1 = args[1].getNum();
+ double x2 = args[2].getNum();
+ double y2 = args[3].getNum();
+ double x3 = x2;
+ double y3 = y2;
+ state->curveTo(x1, y1, x2, y2, x3, y3);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opRectangle(Object args[], int /*numArgs*/)
+{
+ double x = args[0].getNum();
+ double y = args[1].getNum();
+ double w = args[2].getNum();
+ double h = args[3].getNum();
+ state->moveTo(x, y);
+ state->lineTo(x + w, y);
+ state->lineTo(x + w, y + h);
+ state->lineTo(x, y + h);
+ state->closePath();
+}
+
+void PdfParser::opClosePath(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ error(errSyntaxError, getPos(), "No current point in closepath");
+ return;
+ }
+ state->closePath();
+}
+
+//------------------------------------------------------------------------
+// path painting operators
+//------------------------------------------------------------------------
+
+void PdfParser::opEndPath(Object /*args*/[], int /*numArgs*/)
+{
+ doEndPath();
+}
+
+void PdfParser::opStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ if (state->getStrokeColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getStrokePattern())) {
+ doPatternStrokeFallback();
+ } else {
+ builder->addPath(state, false, true);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opCloseStroke(Object * /*args[]*/, int /*numArgs*/) {
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in closepath/stroke"));
+ return;
+ }
+ state->closePath();
+ if (state->isPath()) {
+ if (state->getStrokeColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getStrokePattern())) {
+ doPatternStrokeFallback();
+ } else {
+ builder->addPath(state, false, true);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opFill(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in fill"));
+ return;
+ }
+ if (state->isPath()) {
+ if (state->getFillColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getFillPattern())) {
+ doPatternFillFallback(gFalse);
+ } else {
+ builder->addPath(state, true, false);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opEOFill(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in eofill"));
+ return;
+ }
+ if (state->isPath()) {
+ if (state->getFillColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getFillPattern())) {
+ doPatternFillFallback(gTrue);
+ } else {
+ builder->addPath(state, true, false, true);
+ }
+ }
+ doEndPath();
+}
+
+void PdfParser::opFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in fill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ doFillAndStroke(gFalse);
+ } else {
+ builder->addPath(state, true, true);
+ }
+ doEndPath();
+}
+
+void PdfParser::opCloseFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in closepath/fill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ state->closePath();
+ doFillAndStroke(gFalse);
+ }
+ doEndPath();
+}
+
+void PdfParser::opEOFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in eofill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ doFillAndStroke(gTrue);
+ }
+ doEndPath();
+}
+
+void PdfParser::opCloseEOFillStroke(Object /*args*/[], int /*numArgs*/)
+{
+ if (!state->isCurPt()) {
+ //error(getPos(), const_cast<char*>("No path in closepath/eofill/stroke"));
+ return;
+ }
+ if (state->isPath()) {
+ state->closePath();
+ doFillAndStroke(gTrue);
+ }
+ doEndPath();
+}
+
+void PdfParser::doFillAndStroke(GBool eoFill) {
+ GBool fillOk = gTrue, strokeOk = gTrue;
+ if (state->getFillColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getFillPattern())) {
+ fillOk = gFalse;
+ }
+ if (state->getStrokeColorSpace()->getMode() == csPattern &&
+ !builder->isPatternTypeSupported(state->getStrokePattern())) {
+ strokeOk = gFalse;
+ }
+ if (fillOk && strokeOk) {
+ builder->addPath(state, true, true, eoFill);
+ } else {
+ doPatternFillFallback(eoFill);
+ doPatternStrokeFallback();
+ }
+}
+
+void PdfParser::doPatternFillFallback(GBool eoFill) {
+ GfxPattern *pattern;
+
+ if (!(pattern = state->getFillPattern())) {
+ return;
+ }
+ switch (pattern->getType()) {
+ case 1:
+ break;
+ case 2:
+ doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gFalse, eoFill);
+ break;
+ default:
+ error(errUnimplemented, getPos(), "Unimplemented pattern type (%d) in fill",
+ pattern->getType());
+ break;
+ }
+}
+
+void PdfParser::doPatternStrokeFallback() {
+ GfxPattern *pattern;
+
+ if (!(pattern = state->getStrokePattern())) {
+ return;
+ }
+ switch (pattern->getType()) {
+ case 1:
+ break;
+ case 2:
+ doShadingPatternFillFallback(static_cast<GfxShadingPattern *>(pattern), gTrue, gFalse);
+ break;
+ default:
+ error(errUnimplemented, getPos(), "Unimplemented pattern type ({0:d}) in stroke",
+ pattern->getType());
+ break;
+ }
+}
+
+void PdfParser::doShadingPatternFillFallback(GfxShadingPattern *sPat,
+ GBool stroke, GBool eoFill) {
+ GfxShading *shading;
+ GfxPath *savedPath;
+ const double *ctm, *btm, *ptm;
+ double m[6], ictm[6], m1[6];
+ double xMin, yMin, xMax, yMax;
+ double det;
+
+ shading = sPat->getShading();
+
+ // save current graphics state
+ savedPath = state->getPath()->copy();
+ saveState();
+
+ // clip to bbox
+ if (false ){//shading->getHasBBox()) {
+ shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+ state->moveTo(xMin, yMin);
+ state->lineTo(xMax, yMin);
+ state->lineTo(xMax, yMax);
+ state->lineTo(xMin, yMax);
+ state->closePath();
+ state->clip();
+ //builder->clip(state);
+ state->setPath(savedPath->copy());
+ }
+
+ // clip to current path
+ if (stroke) {
+ state->clipToStrokePath();
+ //out->clipToStrokePath(state);
+ } else {
+ state->clip();
+ if (eoFill) {
+ builder->setClipPath(state, true);
+ } else {
+ builder->setClipPath(state);
+ }
+ }
+
+ // set the color space
+ state->setFillColorSpace(shading->getColorSpace()->copy());
+
+ // background color fill
+ if (shading->getHasBackground()) {
+ state->setFillColor(shading->getBackground());
+ builder->addPath(state, true, false);
+ }
+ state->clearPath();
+
+ // construct a (pattern space) -> (current space) transform matrix
+ ctm = state->getCTM();
+ btm = baseMatrix;
+ ptm = sPat->getMatrix();
+ // iCTM = invert CTM
+ det = 1 / (ctm[0] * ctm[3] - ctm[1] * ctm[2]);
+ ictm[0] = ctm[3] * det;
+ ictm[1] = -ctm[1] * det;
+ ictm[2] = -ctm[2] * det;
+ ictm[3] = ctm[0] * det;
+ ictm[4] = (ctm[2] * ctm[5] - ctm[3] * ctm[4]) * det;
+ ictm[5] = (ctm[1] * ctm[4] - ctm[0] * ctm[5]) * det;
+ // m1 = PTM * BTM = PTM * base transform matrix
+ m1[0] = ptm[0] * btm[0] + ptm[1] * btm[2];
+ m1[1] = ptm[0] * btm[1] + ptm[1] * btm[3];
+ m1[2] = ptm[2] * btm[0] + ptm[3] * btm[2];
+ m1[3] = ptm[2] * btm[1] + ptm[3] * btm[3];
+ m1[4] = ptm[4] * btm[0] + ptm[5] * btm[2] + btm[4];
+ m1[5] = ptm[4] * btm[1] + ptm[5] * btm[3] + btm[5];
+ // m = m1 * iCTM = (PTM * BTM) * (iCTM)
+ m[0] = m1[0] * ictm[0] + m1[1] * ictm[2];
+ m[1] = m1[0] * ictm[1] + m1[1] * ictm[3];
+ m[2] = m1[2] * ictm[0] + m1[3] * ictm[2];
+ m[3] = m1[2] * ictm[1] + m1[3] * ictm[3];
+ m[4] = m1[4] * ictm[0] + m1[5] * ictm[2] + ictm[4];
+ m[5] = m1[4] * ictm[1] + m1[5] * ictm[3] + ictm[5];
+
+ // set the new matrix
+ state->concatCTM(m[0], m[1], m[2], m[3], m[4], m[5]);
+ builder->setTransform(m[0], m[1], m[2], m[3], m[4], m[5]);
+
+ // do shading type-specific operations
+ switch (shading->getType()) {
+ case 1:
+ doFunctionShFill(static_cast<GfxFunctionShading *>(shading));
+ break;
+ case 2:
+ case 3:
+ // no need to implement these
+ break;
+ case 4:
+ case 5:
+ doGouraudTriangleShFill(static_cast<GfxGouraudTriangleShading *>(shading));
+ break;
+ case 6:
+ case 7:
+ doPatchMeshShFill(static_cast<GfxPatchMeshShading *>(shading));
+ break;
+ }
+
+ // restore graphics state
+ restoreState();
+ state->setPath(savedPath);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opShFill(Object args[], int /*numArgs*/)
+{
+ GfxShading *shading = nullptr;
+ GfxPath *savedPath = nullptr;
+ double xMin, yMin, xMax, yMax;
+ double xTemp, yTemp;
+ double gradientTransform[6];
+ double *matrix = nullptr;
+ GBool savedState = gFalse;
+
+ if (!(shading = res->lookupShading(args[0].getName(), nullptr, state))) {
+ return;
+ }
+
+ // save current graphics state
+ if (shading->getType() != 2 && shading->getType() != 3) {
+ savedPath = state->getPath()->copy();
+ saveState();
+ savedState = gTrue;
+ } else { // get gradient transform if possible
+ // check proper operator sequence
+ // first there should be one W(*) and then one 'cm' somewhere before 'sh'
+ GBool seenClip, seenConcat;
+ seenClip = (clipHistory->getClipPath() != nullptr);
+ seenConcat = gFalse;
+ int i = 1;
+ while (i <= maxOperatorHistoryDepth) {
+ const char *opName = getPreviousOperator(i);
+ if (!strcmp(opName, "cm")) {
+ if (seenConcat) { // more than one 'cm'
+ break;
+ } else {
+ seenConcat = gTrue;
+ }
+ }
+ i++;
+ }
+
+ if (seenConcat && seenClip) {
+ if (builder->getTransform(gradientTransform)) {
+ matrix = (double*)&gradientTransform;
+ builder->setTransform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0); // remove transform
+ }
+ }
+ }
+
+ // clip to bbox
+ if (shading->getHasBBox()) {
+ shading->getBBox(&xMin, &yMin, &xMax, &yMax);
+ if (matrix != nullptr) {
+ xTemp = matrix[0]*xMin + matrix[2]*yMin + matrix[4];
+ yTemp = matrix[1]*xMin + matrix[3]*yMin + matrix[5];
+ state->moveTo(xTemp, yTemp);
+ xTemp = matrix[0]*xMax + matrix[2]*yMin + matrix[4];
+ yTemp = matrix[1]*xMax + matrix[3]*yMin + matrix[5];
+ state->lineTo(xTemp, yTemp);
+ xTemp = matrix[0]*xMax + matrix[2]*yMax + matrix[4];
+ yTemp = matrix[1]*xMax + matrix[3]*yMax + matrix[5];
+ state->lineTo(xTemp, yTemp);
+ xTemp = matrix[0]*xMin + matrix[2]*yMax + matrix[4];
+ yTemp = matrix[1]*xMin + matrix[3]*yMax + matrix[5];
+ state->lineTo(xTemp, yTemp);
+ }
+ else {
+ state->moveTo(xMin, yMin);
+ state->lineTo(xMax, yMin);
+ state->lineTo(xMax, yMax);
+ state->lineTo(xMin, yMax);
+ }
+ state->closePath();
+ state->clip();
+ if (savedState)
+ builder->setClipPath(state);
+ else
+ builder->clip(state);
+ state->clearPath();
+ }
+
+ // set the color space
+ if (savedState)
+ state->setFillColorSpace(shading->getColorSpace()->copy());
+
+ // do shading type-specific operations
+ switch (shading->getType()) {
+ case 1:
+ doFunctionShFill(static_cast<GfxFunctionShading *>(shading));
+ break;
+ case 2:
+ case 3:
+ if (clipHistory->getClipPath()) {
+ builder->addShadedFill(shading, matrix, clipHistory->getClipPath(),
+ clipHistory->getClipType() == clipEO ? true : false);
+ }
+ break;
+ case 4:
+ case 5:
+ doGouraudTriangleShFill(static_cast<GfxGouraudTriangleShading *>(shading));
+ break;
+ case 6:
+ case 7:
+ doPatchMeshShFill(static_cast<GfxPatchMeshShading *>(shading));
+ break;
+ }
+
+ // restore graphics state
+ if (savedState) {
+ restoreState();
+ state->setPath(savedPath);
+ }
+
+ delete shading;
+}
+
+void PdfParser::doFunctionShFill(GfxFunctionShading *shading) {
+ double x0, y0, x1, y1;
+ GfxColor colors[4];
+
+ shading->getDomain(&x0, &y0, &x1, &y1);
+ shading->getColor(x0, y0, &colors[0]);
+ shading->getColor(x0, y1, &colors[1]);
+ shading->getColor(x1, y0, &colors[2]);
+ shading->getColor(x1, y1, &colors[3]);
+ doFunctionShFill1(shading, x0, y0, x1, y1, colors, 0);
+}
+
+void PdfParser::doFunctionShFill1(GfxFunctionShading *shading,
+ double x0, double y0,
+ double x1, double y1,
+ GfxColor *colors, int depth) {
+ GfxColor fillColor;
+ GfxColor color0M, color1M, colorM0, colorM1, colorMM;
+ GfxColor colors2[4];
+ double functionColorDelta = colorDeltas[pdfFunctionShading-1];
+ const double *matrix;
+ double xM, yM;
+ int nComps, i, j;
+
+ nComps = shading->getColorSpace()->getNComps();
+ matrix = shading->getMatrix();
+
+ // compare the four corner colors
+ for (i = 0; i < 4; ++i) {
+ for (j = 0; j < nComps; ++j) {
+ if (abs(colors[i].c[j] - colors[(i+1)&3].c[j]) > functionColorDelta) {
+ break;
+ }
+ }
+ if (j < nComps) {
+ break;
+ }
+ }
+
+ // center of the rectangle
+ xM = 0.5 * (x0 + x1);
+ yM = 0.5 * (y0 + y1);
+
+ // the four corner colors are close (or we hit the recursive limit)
+ // -- fill the rectangle; but require at least one subdivision
+ // (depth==0) to avoid problems when the four outer corners of the
+ // shaded region are the same color
+ if ((i == 4 && depth > 0) || depth == maxDepths[pdfFunctionShading-1]) {
+
+ // use the center color
+ shading->getColor(xM, yM, &fillColor);
+ state->setFillColor(&fillColor);
+
+ // fill the rectangle
+ state->moveTo(x0 * matrix[0] + y0 * matrix[2] + matrix[4],
+ x0 * matrix[1] + y0 * matrix[3] + matrix[5]);
+ state->lineTo(x1 * matrix[0] + y0 * matrix[2] + matrix[4],
+ x1 * matrix[1] + y0 * matrix[3] + matrix[5]);
+ state->lineTo(x1 * matrix[0] + y1 * matrix[2] + matrix[4],
+ x1 * matrix[1] + y1 * matrix[3] + matrix[5]);
+ state->lineTo(x0 * matrix[0] + y1 * matrix[2] + matrix[4],
+ x0 * matrix[1] + y1 * matrix[3] + matrix[5]);
+ state->closePath();
+ builder->addPath(state, true, false);
+ state->clearPath();
+
+ // the four corner colors are not close enough -- subdivide the
+ // rectangle
+ } else {
+
+ // colors[0] colorM0 colors[2]
+ // (x0,y0) (xM,y0) (x1,y0)
+ // +----------+----------+
+ // | | |
+ // | UL | UR |
+ // color0M | colorMM | color1M
+ // (x0,yM) +----------+----------+ (x1,yM)
+ // | (xM,yM) |
+ // | LL | LR |
+ // | | |
+ // +----------+----------+
+ // colors[1] colorM1 colors[3]
+ // (x0,y1) (xM,y1) (x1,y1)
+
+ shading->getColor(x0, yM, &color0M);
+ shading->getColor(x1, yM, &color1M);
+ shading->getColor(xM, y0, &colorM0);
+ shading->getColor(xM, y1, &colorM1);
+ shading->getColor(xM, yM, &colorMM);
+
+ // upper-left sub-rectangle
+ colors2[0] = colors[0];
+ colors2[1] = color0M;
+ colors2[2] = colorM0;
+ colors2[3] = colorMM;
+ doFunctionShFill1(shading, x0, y0, xM, yM, colors2, depth + 1);
+
+ // lower-left sub-rectangle
+ colors2[0] = color0M;
+ colors2[1] = colors[1];
+ colors2[2] = colorMM;
+ colors2[3] = colorM1;
+ doFunctionShFill1(shading, x0, yM, xM, y1, colors2, depth + 1);
+
+ // upper-right sub-rectangle
+ colors2[0] = colorM0;
+ colors2[1] = colorMM;
+ colors2[2] = colors[2];
+ colors2[3] = color1M;
+ doFunctionShFill1(shading, xM, y0, x1, yM, colors2, depth + 1);
+
+ // lower-right sub-rectangle
+ colors2[0] = colorMM;
+ colors2[1] = colorM1;
+ colors2[2] = color1M;
+ colors2[3] = colors[3];
+ doFunctionShFill1(shading, xM, yM, x1, y1, colors2, depth + 1);
+ }
+}
+
+void PdfParser::doGouraudTriangleShFill(GfxGouraudTriangleShading *shading) {
+ double x0, y0, x1, y1, x2, y2;
+ GfxColor color0, color1, color2;
+ int i;
+
+ for (i = 0; i < shading->getNTriangles(); ++i) {
+ shading->getTriangle(i, &x0, &y0, &color0,
+ &x1, &y1, &color1,
+ &x2, &y2, &color2);
+ gouraudFillTriangle(x0, y0, &color0, x1, y1, &color1, x2, y2, &color2,
+ shading->getColorSpace()->getNComps(), 0);
+ }
+}
+
+void PdfParser::gouraudFillTriangle(double x0, double y0, GfxColor *color0,
+ double x1, double y1, GfxColor *color1,
+ double x2, double y2, GfxColor *color2,
+ int nComps, int depth) {
+ double x01, y01, x12, y12, x20, y20;
+ double gouraudColorDelta = colorDeltas[pdfGouraudTriangleShading-1];
+ GfxColor color01, color12, color20;
+ int i;
+
+ for (i = 0; i < nComps; ++i) {
+ if (abs(color0->c[i] - color1->c[i]) > gouraudColorDelta ||
+ abs(color1->c[i] - color2->c[i]) > gouraudColorDelta) {
+ break;
+ }
+ }
+ if (i == nComps || depth == maxDepths[pdfGouraudTriangleShading-1]) {
+ state->setFillColor(color0);
+ state->moveTo(x0, y0);
+ state->lineTo(x1, y1);
+ state->lineTo(x2, y2);
+ state->closePath();
+ builder->addPath(state, true, false);
+ state->clearPath();
+ } else {
+ x01 = 0.5 * (x0 + x1);
+ y01 = 0.5 * (y0 + y1);
+ x12 = 0.5 * (x1 + x2);
+ y12 = 0.5 * (y1 + y2);
+ x20 = 0.5 * (x2 + x0);
+ y20 = 0.5 * (y2 + y0);
+ //~ if the shading has a Function, this should interpolate on the
+ //~ function parameter, not on the color components
+ for (i = 0; i < nComps; ++i) {
+ color01.c[i] = (color0->c[i] + color1->c[i]) / 2;
+ color12.c[i] = (color1->c[i] + color2->c[i]) / 2;
+ color20.c[i] = (color2->c[i] + color0->c[i]) / 2;
+ }
+ gouraudFillTriangle(x0, y0, color0, x01, y01, &color01,
+ x20, y20, &color20, nComps, depth + 1);
+ gouraudFillTriangle(x01, y01, &color01, x1, y1, color1,
+ x12, y12, &color12, nComps, depth + 1);
+ gouraudFillTriangle(x01, y01, &color01, x12, y12, &color12,
+ x20, y20, &color20, nComps, depth + 1);
+ gouraudFillTriangle(x20, y20, &color20, x12, y12, &color12,
+ x2, y2, color2, nComps, depth + 1);
+ }
+}
+
+void PdfParser::doPatchMeshShFill(GfxPatchMeshShading *shading) {
+ int start, i;
+
+ if (shading->getNPatches() > 128) {
+ start = 3;
+ } else if (shading->getNPatches() > 64) {
+ start = 2;
+ } else if (shading->getNPatches() > 16) {
+ start = 1;
+ } else {
+ start = 0;
+ }
+ for (i = 0; i < shading->getNPatches(); ++i) {
+ fillPatch(shading->getPatch(i), shading->getColorSpace()->getNComps(),
+ start);
+ }
+}
+
+void PdfParser::fillPatch(_POPPLER_CONST GfxPatch *patch, int nComps, int depth) {
+ GfxPatch patch00 = blankPatch();
+ GfxPatch patch01 = blankPatch();
+ GfxPatch patch10 = blankPatch();
+ GfxPatch patch11 = blankPatch();
+ GfxColor color = {{0}};
+ double xx[4][8];
+ double yy[4][8];
+ double xxm;
+ double yym;
+ double patchColorDelta = colorDeltas[pdfPatchMeshShading - 1];
+
+ int i;
+
+ for (i = 0; i < nComps; ++i) {
+ if (std::abs(patch->color[0][0].c[i] - patch->color[0][1].c[i])
+ > patchColorDelta ||
+ std::abs(patch->color[0][1].c[i] - patch->color[1][1].c[i])
+ > patchColorDelta ||
+ std::abs(patch->color[1][1].c[i] - patch->color[1][0].c[i])
+ > patchColorDelta ||
+ std::abs(patch->color[1][0].c[i] - patch->color[0][0].c[i])
+ > patchColorDelta) {
+ break;
+ }
+ color.c[i] = GfxColorComp(patch->color[0][0].c[i]);
+ }
+ if (i == nComps || depth == maxDepths[pdfPatchMeshShading-1]) {
+ state->setFillColor(&color);
+ state->moveTo(patch->x[0][0], patch->y[0][0]);
+ state->curveTo(patch->x[0][1], patch->y[0][1],
+ patch->x[0][2], patch->y[0][2],
+ patch->x[0][3], patch->y[0][3]);
+ state->curveTo(patch->x[1][3], patch->y[1][3],
+ patch->x[2][3], patch->y[2][3],
+ patch->x[3][3], patch->y[3][3]);
+ state->curveTo(patch->x[3][2], patch->y[3][2],
+ patch->x[3][1], patch->y[3][1],
+ patch->x[3][0], patch->y[3][0]);
+ state->curveTo(patch->x[2][0], patch->y[2][0],
+ patch->x[1][0], patch->y[1][0],
+ patch->x[0][0], patch->y[0][0]);
+ state->closePath();
+ builder->addPath(state, true, false);
+ state->clearPath();
+ } else {
+ for (i = 0; i < 4; ++i) {
+ xx[i][0] = patch->x[i][0];
+ yy[i][0] = patch->y[i][0];
+ xx[i][1] = 0.5 * (patch->x[i][0] + patch->x[i][1]);
+ yy[i][1] = 0.5 * (patch->y[i][0] + patch->y[i][1]);
+ xxm = 0.5 * (patch->x[i][1] + patch->x[i][2]);
+ yym = 0.5 * (patch->y[i][1] + patch->y[i][2]);
+ xx[i][6] = 0.5 * (patch->x[i][2] + patch->x[i][3]);
+ yy[i][6] = 0.5 * (patch->y[i][2] + patch->y[i][3]);
+ xx[i][2] = 0.5 * (xx[i][1] + xxm);
+ yy[i][2] = 0.5 * (yy[i][1] + yym);
+ xx[i][5] = 0.5 * (xxm + xx[i][6]);
+ yy[i][5] = 0.5 * (yym + yy[i][6]);
+ xx[i][3] = xx[i][4] = 0.5 * (xx[i][2] + xx[i][5]);
+ yy[i][3] = yy[i][4] = 0.5 * (yy[i][2] + yy[i][5]);
+ xx[i][7] = patch->x[i][3];
+ yy[i][7] = patch->y[i][3];
+ }
+ for (i = 0; i < 4; ++i) {
+ patch00.x[0][i] = xx[0][i];
+ patch00.y[0][i] = yy[0][i];
+ patch00.x[1][i] = 0.5 * (xx[0][i] + xx[1][i]);
+ patch00.y[1][i] = 0.5 * (yy[0][i] + yy[1][i]);
+ xxm = 0.5 * (xx[1][i] + xx[2][i]);
+ yym = 0.5 * (yy[1][i] + yy[2][i]);
+ patch10.x[2][i] = 0.5 * (xx[2][i] + xx[3][i]);
+ patch10.y[2][i] = 0.5 * (yy[2][i] + yy[3][i]);
+ patch00.x[2][i] = 0.5 * (patch00.x[1][i] + xxm);
+ patch00.y[2][i] = 0.5 * (patch00.y[1][i] + yym);
+ patch10.x[1][i] = 0.5 * (xxm + patch10.x[2][i]);
+ patch10.y[1][i] = 0.5 * (yym + patch10.y[2][i]);
+ patch00.x[3][i] = 0.5 * (patch00.x[2][i] + patch10.x[1][i]);
+ patch00.y[3][i] = 0.5 * (patch00.y[2][i] + patch10.y[1][i]);
+ patch10.x[0][i] = patch00.x[3][i];
+ patch10.y[0][i] = patch00.y[3][i];
+ patch10.x[3][i] = xx[3][i];
+ patch10.y[3][i] = yy[3][i];
+ }
+ for (i = 4; i < 8; ++i) {
+ patch01.x[0][i-4] = xx[0][i];
+ patch01.y[0][i-4] = yy[0][i];
+ patch01.x[1][i-4] = 0.5 * (xx[0][i] + xx[1][i]);
+ patch01.y[1][i-4] = 0.5 * (yy[0][i] + yy[1][i]);
+ xxm = 0.5 * (xx[1][i] + xx[2][i]);
+ yym = 0.5 * (yy[1][i] + yy[2][i]);
+ patch11.x[2][i-4] = 0.5 * (xx[2][i] + xx[3][i]);
+ patch11.y[2][i-4] = 0.5 * (yy[2][i] + yy[3][i]);
+ patch01.x[2][i-4] = 0.5 * (patch01.x[1][i-4] + xxm);
+ patch01.y[2][i-4] = 0.5 * (patch01.y[1][i-4] + yym);
+ patch11.x[1][i-4] = 0.5 * (xxm + patch11.x[2][i-4]);
+ patch11.y[1][i-4] = 0.5 * (yym + patch11.y[2][i-4]);
+ patch01.x[3][i-4] = 0.5 * (patch01.x[2][i-4] + patch11.x[1][i-4]);
+ patch01.y[3][i-4] = 0.5 * (patch01.y[2][i-4] + patch11.y[1][i-4]);
+ patch11.x[0][i-4] = patch01.x[3][i-4];
+ patch11.y[0][i-4] = patch01.y[3][i-4];
+ patch11.x[3][i-4] = xx[3][i];
+ patch11.y[3][i-4] = yy[3][i];
+ }
+ //~ if the shading has a Function, this should interpolate on the
+ //~ function parameter, not on the color components
+ for (i = 0; i < nComps; ++i) {
+ patch00.color[0][0].c[i] = patch->color[0][0].c[i];
+ patch00.color[0][1].c[i] = (patch->color[0][0].c[i] +
+ patch->color[0][1].c[i]) / 2;
+ patch01.color[0][0].c[i] = patch00.color[0][1].c[i];
+ patch01.color[0][1].c[i] = patch->color[0][1].c[i];
+ patch01.color[1][1].c[i] = (patch->color[0][1].c[i] +
+ patch->color[1][1].c[i]) / 2;
+ patch11.color[0][1].c[i] = patch01.color[1][1].c[i];
+ patch11.color[1][1].c[i] = patch->color[1][1].c[i];
+ patch11.color[1][0].c[i] = (patch->color[1][1].c[i] +
+ patch->color[1][0].c[i]) / 2;
+ patch10.color[1][1].c[i] = patch11.color[1][0].c[i];
+ patch10.color[1][0].c[i] = patch->color[1][0].c[i];
+ patch10.color[0][0].c[i] = (patch->color[1][0].c[i] +
+ patch->color[0][0].c[i]) / 2;
+ patch00.color[1][0].c[i] = patch10.color[0][0].c[i];
+ patch00.color[1][1].c[i] = (patch00.color[1][0].c[i] +
+ patch01.color[1][1].c[i]) / 2;
+ patch01.color[1][0].c[i] = patch00.color[1][1].c[i];
+ patch11.color[0][0].c[i] = patch00.color[1][1].c[i];
+ patch10.color[0][1].c[i] = patch00.color[1][1].c[i];
+ }
+ fillPatch(&patch00, nComps, depth + 1);
+ fillPatch(&patch10, nComps, depth + 1);
+ fillPatch(&patch01, nComps, depth + 1);
+ fillPatch(&patch11, nComps, depth + 1);
+ }
+}
+
+void PdfParser::doEndPath() {
+ if (state->isCurPt() && clip != clipNone) {
+ state->clip();
+ if (clip == clipNormal) {
+ clipHistory->setClip(state->getPath(), clipNormal);
+ builder->clip(state);
+ } else {
+ clipHistory->setClip(state->getPath(), clipEO);
+ builder->clip(state, true);
+ }
+ }
+ clip = clipNone;
+ state->clearPath();
+}
+
+//------------------------------------------------------------------------
+// path clipping operators
+//------------------------------------------------------------------------
+
+void PdfParser::opClip(Object /*args*/[], int /*numArgs*/)
+{
+ clip = clipNormal;
+}
+
+void PdfParser::opEOClip(Object /*args*/[], int /*numArgs*/)
+{
+ clip = clipEO;
+}
+
+//------------------------------------------------------------------------
+// text object operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginText(Object /*args*/[], int /*numArgs*/)
+{
+ state->setTextMat(1, 0, 0, 1, 0, 0);
+ state->textMoveTo(0, 0);
+ builder->updateTextPosition(0.0, 0.0);
+ fontChanged = gTrue;
+ builder->beginTextObject(state);
+}
+
+void PdfParser::opEndText(Object /*args*/[], int /*numArgs*/)
+{
+ builder->endTextObject(state);
+}
+
+//------------------------------------------------------------------------
+// text state operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetCharSpacing(Object args[], int /*numArgs*/)
+{
+ state->setCharSpace(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetFont(Object args[], int /*numArgs*/)
+{
+ auto font = res->lookupFont(args[0].getName());
+
+ if (!font) {
+ // unsetting the font (drawing no text) is better than using the
+ // previous one and drawing random glyphs from it
+ state->setFont(nullptr, args[1].getNum());
+ fontChanged = gTrue;
+ return;
+ }
+ if (printCommands) {
+ printf(" font: tag=%s name='%s' %g\n",
+#if POPPLER_CHECK_VERSION(21,11,0)
+ font->getTag().c_str(),
+#else
+ font->getTag()->getCString(),
+#endif
+ font->getName() ? font->getName()->getCString() : "???",
+ args[1].getNum());
+ fflush(stdout);
+ }
+
+#if !POPPLER_CHECK_VERSION(22, 4, 0)
+ font->incRefCnt();
+#endif
+ state->setFont(font, args[1].getNum());
+ fontChanged = gTrue;
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextLeading(Object args[], int /*numArgs*/)
+{
+ state->setLeading(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextRender(Object args[], int /*numArgs*/)
+{
+ state->setRender(args[0].getInt());
+ builder->updateStyle(state);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextRise(Object args[], int /*numArgs*/)
+{
+ state->setRise(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetWordSpacing(Object args[], int /*numArgs*/)
+{
+ state->setWordSpace(args[0].getNum());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetHorizScaling(Object args[], int /*numArgs*/)
+{
+ state->setHorizScaling(args[0].getNum());
+ builder->updateTextMatrix(state);
+ fontChanged = gTrue;
+}
+
+//------------------------------------------------------------------------
+// text positioning operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opTextMove(Object args[], int /*numArgs*/)
+{
+ double tx, ty;
+
+ tx = state->getLineX() + args[0].getNum();
+ ty = state->getLineY() + args[1].getNum();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opTextMoveSet(Object args[], int /*numArgs*/)
+{
+ double tx, ty;
+
+ tx = state->getLineX() + args[0].getNum();
+ ty = args[1].getNum();
+ state->setLeading(-ty);
+ ty += state->getLineY();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opSetTextMatrix(Object args[], int /*numArgs*/)
+{
+ state->setTextMat(args[0].getNum(), args[1].getNum(),
+ args[2].getNum(), args[3].getNum(),
+ args[4].getNum(), args[5].getNum());
+ state->textMoveTo(0, 0);
+ builder->updateTextMatrix(state);
+ builder->updateTextPosition(0.0, 0.0);
+ fontChanged = gTrue;
+}
+
+void PdfParser::opTextNextLine(Object /*args*/[], int /*numArgs*/)
+{
+ double tx, ty;
+
+ tx = state->getLineX();
+ ty = state->getLineY() - state->getLeading();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+}
+
+//------------------------------------------------------------------------
+// text string operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opShowText(Object args[], int /*numArgs*/)
+{
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in show");
+ return;
+ }
+ if (fontChanged) {
+ builder->updateFont(state);
+ fontChanged = gFalse;
+ }
+ doShowText(args[0].getString());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opMoveShowText(Object args[], int /*numArgs*/)
+{
+ double tx = 0;
+ double ty = 0;
+
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in move/show");
+ return;
+ }
+ if (fontChanged) {
+ builder->updateFont(state);
+ fontChanged = gFalse;
+ }
+ tx = state->getLineX();
+ ty = state->getLineY() - state->getLeading();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+ doShowText(args[0].getString());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opMoveSetShowText(Object args[], int /*numArgs*/)
+{
+ double tx = 0;
+ double ty = 0;
+
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in move/set/show");
+ return;
+ }
+ if (fontChanged) {
+ builder->updateFont(state);
+ fontChanged = gFalse;
+ }
+ state->setWordSpace(args[0].getNum());
+ state->setCharSpace(args[1].getNum());
+ tx = state->getLineX();
+ ty = state->getLineY() - state->getLeading();
+ state->textMoveTo(tx, ty);
+ builder->updateTextPosition(tx, ty);
+ doShowText(args[2].getString());
+}
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opShowSpaceText(Object args[], int /*numArgs*/)
+{
+ Array *a = nullptr;
+ Object obj;
+ int wMode = 0;
+
+ if (!state->getFont()) {
+ error(errSyntaxError, getPos(), "No font in show/space");
+ return;
+ }
+ if (fontChanged) {
+ builder->updateFont(state);
+ fontChanged = gFalse;
+ }
+ wMode = state->getFont()->getWMode();
+ a = args[0].getArray();
+ for (int i = 0; i < a->getLength(); ++i) {
+ _POPPLER_CALL_ARGS(obj, a->get, i);
+ if (obj.isNum()) {
+ // this uses the absolute value of the font size to match
+ // Acrobat's behavior
+ if (wMode) {
+ state->textShift(0, -obj.getNum() * 0.001 *
+ fabs(state->getFontSize()));
+ } else {
+ state->textShift(-obj.getNum() * 0.001 *
+ fabs(state->getFontSize()), 0);
+ }
+ builder->updateTextShift(state, obj.getNum());
+ } else if (obj.isString()) {
+ doShowText(obj.getString());
+ } else {
+ error(errSyntaxError, getPos(), "Element of show/space array must be number or string");
+ }
+ _POPPLER_FREE(obj);
+ }
+}
+
+#if POPPLER_CHECK_VERSION(0,64,0)
+void PdfParser::doShowText(const GooString *s) {
+#else
+void PdfParser::doShowText(GooString *s) {
+#endif
+ int wMode;
+ double riseX, riseY;
+ CharCode code;
+ Unicode _POPPLER_CONST_82 *u = nullptr;
+ double x, y, dx, dy, tdx, tdy;
+ double originX, originY, tOriginX, tOriginY;
+ double oldCTM[6], newCTM[6];
+ const double *mat;
+ Object charProc;
+ Dict *resDict;
+ Parser *oldParser;
+#if POPPLER_CHECK_VERSION(0,64,0)
+ const char *p;
+#else
+ char *p;
+#endif
+ int len, n, uLen;
+
+ auto font = state->getFont();
+ wMode = font->getWMode();
+
+ builder->beginString(state);
+
+ // handle a Type 3 char
+ if (font->getType() == fontType3 && false) {//out->interpretType3Chars()) {
+ mat = state->getCTM();
+ for (int i = 0; i < 6; ++i) {
+ oldCTM[i] = mat[i];
+ }
+ mat = state->getTextMat();
+ newCTM[0] = mat[0] * oldCTM[0] + mat[1] * oldCTM[2];
+ newCTM[1] = mat[0] * oldCTM[1] + mat[1] * oldCTM[3];
+ newCTM[2] = mat[2] * oldCTM[0] + mat[3] * oldCTM[2];
+ newCTM[3] = mat[2] * oldCTM[1] + mat[3] * oldCTM[3];
+ mat = font->getFontMatrix();
+ newCTM[0] = mat[0] * newCTM[0] + mat[1] * newCTM[2];
+ newCTM[1] = mat[0] * newCTM[1] + mat[1] * newCTM[3];
+ newCTM[2] = mat[2] * newCTM[0] + mat[3] * newCTM[2];
+ newCTM[3] = mat[2] * newCTM[1] + mat[3] * newCTM[3];
+ newCTM[0] *= state->getFontSize();
+ newCTM[1] *= state->getFontSize();
+ newCTM[2] *= state->getFontSize();
+ newCTM[3] *= state->getFontSize();
+ newCTM[0] *= state->getHorizScaling();
+ newCTM[2] *= state->getHorizScaling();
+ state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
+ double curX = state->getCurX();
+ double curY = state->getCurY();
+ double lineX = state->getLineX();
+ double lineY = state->getLineY();
+ oldParser = parser;
+ p = s->getCString();
+ len = s->getLength();
+ while (len > 0) {
+ n = font->getNextChar(p, len, &code,
+ &u, &uLen, /* TODO: This looks like a memory leak for u. */
+ &dx, &dy, &originX, &originY);
+ dx = dx * state->getFontSize() + state->getCharSpace();
+ if (n == 1 && *p == ' ') {
+ dx += state->getWordSpace();
+ }
+ dx *= state->getHorizScaling();
+ dy *= state->getFontSize();
+ state->textTransformDelta(dx, dy, &tdx, &tdy);
+ state->transform(curX + riseX, curY + riseY, &x, &y);
+ saveState();
+ state->setCTM(newCTM[0], newCTM[1], newCTM[2], newCTM[3], x, y);
+ //~ the CTM concat values here are wrong (but never used)
+ //out->updateCTM(state, 1, 0, 0, 1, 0, 0);
+ if (false){ /*!out->beginType3Char(state, curX + riseX, curY + riseY, tdx, tdy,
+ code, u, uLen)) {*/
+ _POPPLER_CALL_ARGS(charProc, _POPPLER_FONTPTR_TO_GFX8(font)->getCharProc, code);
+ if (resDict = _POPPLER_FONTPTR_TO_GFX8(font)->getResources()) {
+ pushResources(resDict);
+ }
+ if (charProc.isStream()) {
+ //parse(&charProc, gFalse); // TODO: parse into SVG font
+ } else {
+ error(errSyntaxError, getPos(), "Missing or bad Type3 CharProc entry");
+ }
+ //out->endType3Char(state);
+ if (resDict) {
+ popResources();
+ }
+ _POPPLER_FREE(charProc);
+ }
+ restoreState();
+ // GfxState::restore() does *not* restore the current position,
+ // so we deal with it here using (curX, curY) and (lineX, lineY)
+ curX += tdx;
+ curY += tdy;
+ state->moveTo(curX, curY);
+ state->textSetPos(lineX, lineY);
+ p += n;
+ len -= n;
+ }
+ parser = oldParser;
+
+ } else {
+ state->textTransformDelta(0, state->getRise(), &riseX, &riseY);
+ p = s->getCString();
+ len = s->getLength();
+ while (len > 0) {
+ n = font->getNextChar(p, len, &code,
+ &u, &uLen, /* TODO: This looks like a memory leak for u. */
+ &dx, &dy, &originX, &originY);
+
+ if (wMode) {
+ dx *= state->getFontSize();
+ dy = dy * state->getFontSize() + state->getCharSpace();
+ if (n == 1 && *p == ' ') {
+ dy += state->getWordSpace();
+ }
+ } else {
+ dx = dx * state->getFontSize() + state->getCharSpace();
+ if (n == 1 && *p == ' ') {
+ dx += state->getWordSpace();
+ }
+ dx *= state->getHorizScaling();
+ dy *= state->getFontSize();
+ }
+ state->textTransformDelta(dx, dy, &tdx, &tdy);
+ originX *= state->getFontSize();
+ originY *= state->getFontSize();
+ state->textTransformDelta(originX, originY, &tOriginX, &tOriginY);
+ builder->addChar(state, state->getCurX() + riseX, state->getCurY() + riseY,
+ dx, dy, tOriginX, tOriginY, code, n, u, uLen);
+ state->shift(tdx, tdy);
+ p += n;
+ len -= n;
+ }
+ }
+
+ builder->endString(state);
+}
+
+
+//------------------------------------------------------------------------
+// XObject operators
+//------------------------------------------------------------------------
+
+// TODO not good that numArgs is ignored but args[] is used:
+void PdfParser::opXObject(Object args[], int /*numArgs*/)
+{
+ Object obj1, obj2, obj3, refObj;
+
+#if POPPLER_CHECK_VERSION(0,64,0)
+ const char *name = args[0].getName();
+#else
+ char *name = args[0].getName();
+#endif
+ _POPPLER_CALL_ARGS(obj1, res->lookupXObject, name);
+ if (obj1.isNull()) {
+ return;
+ }
+ if (!obj1.isStream()) {
+ error(errSyntaxError, getPos(), "XObject '{0:s}' is wrong type", name);
+ _POPPLER_FREE(obj1);
+ return;
+ }
+ _POPPLER_CALL_ARGS(obj2, obj1.streamGetDict()->lookup, "Subtype");
+ if (obj2.isName(const_cast<char*>("Image"))) {
+ _POPPLER_CALL_ARGS(refObj, res->lookupXObjectNF, name);
+ doImage(&refObj, obj1.getStream(), gFalse);
+ _POPPLER_FREE(refObj);
+ } else if (obj2.isName(const_cast<char*>("Form"))) {
+ doForm(&obj1);
+ } else if (obj2.isName(const_cast<char*>("PS"))) {
+ _POPPLER_CALL_ARGS(obj3, obj1.streamGetDict()->lookup, "Level1");
+/* out->psXObject(obj1.getStream(),
+ obj3.isStream() ? obj3.getStream() : (Stream *)NULL);*/
+ } else if (obj2.isName()) {
+ error(errSyntaxError, getPos(), "Unknown XObject subtype '{0:s}'", obj2.getName());
+ } else {
+ error(errSyntaxError, getPos(), "XObject subtype is missing or wrong type");
+ }
+ _POPPLER_FREE(obj2);
+ _POPPLER_FREE(obj1);
+}
+
+void PdfParser::doImage(Object * /*ref*/, Stream *str, GBool inlineImg)
+{
+ Dict *dict;
+ int width, height;
+ int bits;
+ GBool interpolate;
+ StreamColorSpaceMode csMode;
+ GBool mask;
+ GBool invert;
+ Object maskObj, smaskObj;
+ GBool haveColorKeyMask, haveExplicitMask, haveSoftMask;
+ GBool maskInvert;
+ GBool maskInterpolate;
+ Object obj1, obj2;
+
+ // get info from the stream
+ bits = 0;
+ csMode = streamCSNone;
+ str->getImageParams(&bits, &csMode);
+
+ // get stream dict
+ dict = str->getDict();
+
+ // get size
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Width");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "W");
+ }
+ if (obj1.isInt()){
+ width = obj1.getInt();
+ }
+ else if (obj1.isReal()) {
+ width = (int)obj1.getReal();
+ }
+ else {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Height");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "H");
+ }
+ if (obj1.isInt()) {
+ height = obj1.getInt();
+ }
+ else if (obj1.isReal()){
+ height = static_cast<int>(obj1.getReal());
+ }
+ else {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+
+ // image interpolation
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Interpolate");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "I");
+ }
+ if (obj1.isBool())
+ interpolate = obj1.getBool();
+ else
+ interpolate = gFalse;
+ _POPPLER_FREE(obj1);
+ maskInterpolate = gFalse;
+
+ // image or mask?
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "ImageMask");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "IM");
+ }
+ mask = gFalse;
+ if (obj1.isBool()) {
+ mask = obj1.getBool();
+ }
+ else if (!obj1.isNull()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+
+ // bit depth
+ if (bits == 0) {
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "BitsPerComponent");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "BPC");
+ }
+ if (obj1.isInt()) {
+ bits = obj1.getInt();
+ } else if (mask) {
+ bits = 1;
+ } else {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ }
+
+ // display a mask
+ if (mask) {
+ // check for inverted mask
+ if (bits != 1) {
+ goto err1;
+ }
+ invert = gFalse;
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "D");
+ }
+ if (obj1.isArray()) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0);
+ if (obj2.isInt() && obj2.getInt() == 1) {
+ invert = gTrue;
+ }
+ _POPPLER_FREE(obj2);
+ } else if (!obj1.isNull()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+
+ // draw it
+ builder->addImageMask(state, str, width, height, invert, interpolate);
+
+ } else {
+ // get color space and color map
+ GfxColorSpace *colorSpace;
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "ColorSpace");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "CS");
+ }
+ if (!obj1.isNull()) {
+ colorSpace = lookupColorSpaceCopy(obj1);
+ } else if (csMode == streamCSDeviceGray) {
+ colorSpace = new GfxDeviceGrayColorSpace();
+ } else if (csMode == streamCSDeviceRGB) {
+ colorSpace = new GfxDeviceRGBColorSpace();
+ } else if (csMode == streamCSDeviceCMYK) {
+ colorSpace = new GfxDeviceCMYKColorSpace();
+ } else {
+ colorSpace = nullptr;
+ }
+ _POPPLER_FREE(obj1);
+ if (!colorSpace) {
+ goto err1;
+ }
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "D");
+ }
+ GfxImageColorMap *colorMap = new GfxImageColorMap(bits, &obj1, colorSpace);
+ _POPPLER_FREE(obj1);
+ if (!colorMap->isOk()) {
+ delete colorMap;
+ goto err1;
+ }
+
+ // get the mask
+ int maskColors[2*gfxColorMaxComps];
+ haveColorKeyMask = haveExplicitMask = haveSoftMask = gFalse;
+ Stream *maskStr = nullptr;
+ int maskWidth = 0;
+ int maskHeight = 0;
+ maskInvert = gFalse;
+ GfxImageColorMap *maskColorMap = nullptr;
+ _POPPLER_CALL_ARGS(maskObj, dict->lookup, "Mask");
+ _POPPLER_CALL_ARGS(smaskObj, dict->lookup, "SMask");
+ Dict* maskDict;
+ if (smaskObj.isStream()) {
+ // soft mask
+ if (inlineImg) {
+ goto err1;
+ }
+ maskStr = smaskObj.getStream();
+ maskDict = smaskObj.streamGetDict();
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskWidth = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskHeight = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BitsPerComponent");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "BPC");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ int maskBits = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I");
+ }
+ if (obj1.isBool())
+ maskInterpolate = obj1.getBool();
+ else
+ maskInterpolate = gFalse;
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ColorSpace");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "CS");
+ }
+ GfxColorSpace *maskColorSpace = lookupColorSpaceCopy(obj1);
+ _POPPLER_FREE(obj1);
+ if (!maskColorSpace || maskColorSpace->getMode() != csDeviceGray) {
+ goto err1;
+ }
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D");
+ }
+ maskColorMap = new GfxImageColorMap(maskBits, &obj1, maskColorSpace);
+ _POPPLER_FREE(obj1);
+ if (!maskColorMap->isOk()) {
+ delete maskColorMap;
+ goto err1;
+ }
+ //~ handle the Matte entry
+ haveSoftMask = gTrue;
+ } else if (maskObj.isArray()) {
+ // color key mask
+ int i;
+ for (i = 0; i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps; ++i) {
+ _POPPLER_CALL_ARGS(obj1, maskObj.arrayGet, i);
+ maskColors[i] = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ }
+ haveColorKeyMask = gTrue;
+ } else if (maskObj.isStream()) {
+ // explicit mask
+ if (inlineImg) {
+ goto err1;
+ }
+ maskStr = maskObj.getStream();
+ maskDict = maskObj.streamGetDict();
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Width");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "W");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskWidth = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Height");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "H");
+ }
+ if (!obj1.isInt()) {
+ goto err2;
+ }
+ maskHeight = obj1.getInt();
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "ImageMask");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "IM");
+ }
+ if (!obj1.isBool() || !obj1.getBool()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Interpolate");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "I");
+ }
+ if (obj1.isBool())
+ maskInterpolate = obj1.getBool();
+ else
+ maskInterpolate = gFalse;
+ _POPPLER_FREE(obj1);
+ maskInvert = gFalse;
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "Decode");
+ if (obj1.isNull()) {
+ _POPPLER_FREE(obj1);
+ _POPPLER_CALL_ARGS(obj1, maskDict->lookup, "D");
+ }
+ if (obj1.isArray()) {
+ _POPPLER_CALL_ARGS(obj2, obj1.arrayGet, 0);
+ if (obj2.isInt() && obj2.getInt() == 1) {
+ maskInvert = gTrue;
+ }
+ _POPPLER_FREE(obj2);
+ } else if (!obj1.isNull()) {
+ goto err2;
+ }
+ _POPPLER_FREE(obj1);
+ haveExplicitMask = gTrue;
+ }
+
+ // draw it
+ if (haveSoftMask) {
+ builder->addSoftMaskedImage(state, str, width, height, colorMap, interpolate,
+ maskStr, maskWidth, maskHeight, maskColorMap, maskInterpolate);
+ delete maskColorMap;
+ } else if (haveExplicitMask) {
+ builder->addMaskedImage(state, str, width, height, colorMap, interpolate,
+ maskStr, maskWidth, maskHeight, maskInvert, maskInterpolate);
+ } else {
+ builder->addImage(state, str, width, height, colorMap, interpolate,
+ haveColorKeyMask ? maskColors : static_cast<int *>(nullptr));
+ }
+ delete colorMap;
+
+ _POPPLER_FREE(maskObj);
+ _POPPLER_FREE(smaskObj);
+ }
+
+ return;
+
+ err2:
+ _POPPLER_FREE(obj1);
+ err1:
+ error(errSyntaxError, getPos(), "Bad image parameters");
+}
+
+void PdfParser::doForm(Object *str) {
+ Dict *dict;
+ GBool transpGroup, isolated, knockout;
+ GfxColorSpace *blendingColorSpace;
+ Object matrixObj, bboxObj;
+ double m[6], bbox[4];
+ Object resObj;
+ Dict *resDict;
+ Object obj1, obj2, obj3;
+ int i;
+
+ // check for excessive recursion
+ if (formDepth > 20) {
+ return;
+ }
+
+ // get stream dict
+ dict = str->streamGetDict();
+
+ // check form type
+ _POPPLER_CALL_ARGS(obj1, dict->lookup, "FormType");
+ if (!(obj1.isNull() || (obj1.isInt() && obj1.getInt() == 1))) {
+ error(errSyntaxError, getPos(), "Unknown form type");
+ }
+ _POPPLER_FREE(obj1);
+
+ // get bounding box
+ _POPPLER_CALL_ARGS(bboxObj, dict->lookup, "BBox");
+ if (!bboxObj.isArray()) {
+ _POPPLER_FREE(bboxObj);
+ error(errSyntaxError, getPos(), "Bad form bounding box");
+ return;
+ }
+ for (i = 0; i < 4; ++i) {
+ _POPPLER_CALL_ARGS(obj1, bboxObj.arrayGet, i);
+ bbox[i] = obj1.getNum();
+ _POPPLER_FREE(obj1);
+ }
+ _POPPLER_FREE(bboxObj);
+
+ // get matrix
+ _POPPLER_CALL_ARGS(matrixObj, dict->lookup, "Matrix");
+ if (matrixObj.isArray()) {
+ for (i = 0; i < 6; ++i) {
+ _POPPLER_CALL_ARGS(obj1, matrixObj.arrayGet, i);
+ m[i] = obj1.getNum();
+ _POPPLER_FREE(obj1);
+ }
+ } else {
+ m[0] = 1; m[1] = 0;
+ m[2] = 0; m[3] = 1;
+ m[4] = 0; m[5] = 0;
+ }
+ _POPPLER_FREE(matrixObj);
+
+ // get resources
+ _POPPLER_CALL_ARGS(resObj, dict->lookup, "Resources");
+ resDict = resObj.isDict() ? resObj.getDict() : (Dict *)nullptr;
+
+ // check for a transparency group
+ transpGroup = isolated = knockout = gFalse;
+ blendingColorSpace = nullptr;
+ if (_POPPLER_CALL_ARGS_DEREF(obj1, dict->lookup, "Group").isDict()) {
+ if (_POPPLER_CALL_ARGS_DEREF(obj2, obj1.dictLookup, "S").isName("Transparency")) {
+ transpGroup = gTrue;
+ if (!_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "CS").isNull()) {
+ blendingColorSpace = GfxColorSpace::parse(nullptr, &obj3, nullptr, state);
+ }
+ _POPPLER_FREE(obj3);
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "I").isBool()) {
+ isolated = obj3.getBool();
+ }
+ _POPPLER_FREE(obj3);
+ if (_POPPLER_CALL_ARGS_DEREF(obj3, obj1.dictLookup, "K").isBool()) {
+ knockout = obj3.getBool();
+ }
+ _POPPLER_FREE(obj3);
+ }
+ _POPPLER_FREE(obj2);
+ }
+ _POPPLER_FREE(obj1);
+
+ // draw it
+ ++formDepth;
+ doForm1(str, resDict, m, bbox,
+ transpGroup, gFalse, blendingColorSpace, isolated, knockout);
+ --formDepth;
+
+ if (blendingColorSpace) {
+ delete blendingColorSpace;
+ }
+ _POPPLER_FREE(resObj);
+}
+
+void PdfParser::doForm1(Object *str, Dict *resDict, double *matrix, double *bbox,
+ GBool transpGroup, GBool softMask,
+ GfxColorSpace *blendingColorSpace,
+ GBool isolated, GBool knockout,
+ GBool alpha, Function *transferFunc,
+ GfxColor *backdropColor) {
+ Parser *oldParser;
+ double oldBaseMatrix[6];
+ int i;
+
+ // push new resources on stack
+ pushResources(resDict);
+
+ // save current graphics state
+ saveState();
+
+ // kill any pre-existing path
+ state->clearPath();
+
+ if (softMask || transpGroup) {
+ builder->clearSoftMask(state);
+ builder->pushTransparencyGroup(state, bbox, blendingColorSpace,
+ isolated, knockout, softMask);
+ }
+
+ // save current parser
+ oldParser = parser;
+
+ // set form transformation matrix
+ state->concatCTM(matrix[0], matrix[1], matrix[2],
+ matrix[3], matrix[4], matrix[5]);
+ builder->setTransform(matrix[0], matrix[1], matrix[2],
+ matrix[3], matrix[4], matrix[5]);
+
+ // set form bounding box
+ state->moveTo(bbox[0], bbox[1]);
+ state->lineTo(bbox[2], bbox[1]);
+ state->lineTo(bbox[2], bbox[3]);
+ state->lineTo(bbox[0], bbox[3]);
+ state->closePath();
+ state->clip();
+ clipHistory->setClip(state->getPath());
+ builder->clip(state);
+ state->clearPath();
+
+ if (softMask || transpGroup) {
+ if (state->getBlendMode() != gfxBlendNormal) {
+ state->setBlendMode(gfxBlendNormal);
+ }
+ if (state->getFillOpacity() != 1) {
+ builder->setGroupOpacity(state->getFillOpacity());
+ state->setFillOpacity(1);
+ }
+ if (state->getStrokeOpacity() != 1) {
+ state->setStrokeOpacity(1);
+ }
+ }
+
+ // set new base matrix
+ for (i = 0; i < 6; ++i) {
+ oldBaseMatrix[i] = baseMatrix[i];
+ baseMatrix[i] = state->getCTM()[i];
+ }
+
+ // draw the form
+ parse(str, gFalse);
+
+ // restore base matrix
+ for (i = 0; i < 6; ++i) {
+ baseMatrix[i] = oldBaseMatrix[i];
+ }
+
+ // restore parser
+ parser = oldParser;
+
+ if (softMask || transpGroup) {
+ builder->popTransparencyGroup(state);
+ }
+
+ // restore graphics state
+ restoreState();
+
+ // pop resource stack
+ popResources();
+
+ if (softMask) {
+ builder->setSoftMask(state, bbox, alpha, transferFunc, backdropColor);
+ } else if (transpGroup) {
+ builder->paintTransparencyGroup(state, bbox);
+ }
+
+ return;
+}
+
+//------------------------------------------------------------------------
+// in-line image operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginImage(Object /*args*/[], int /*numArgs*/)
+{
+ // build dict/stream
+ Stream *str = buildImageStream();
+
+ // display the image
+ if (str) {
+ doImage(nullptr, str, gTrue);
+
+ // skip 'EI' tag
+ int c1 = str->getUndecodedStream()->getChar();
+ int c2 = str->getUndecodedStream()->getChar();
+ while (!(c1 == 'E' && c2 == 'I') && c2 != EOF) {
+ c1 = c2;
+ c2 = str->getUndecodedStream()->getChar();
+ }
+ delete str;
+ }
+}
+
+Stream *PdfParser::buildImageStream() {
+ Object dict;
+ Object obj;
+ Stream *str;
+
+ // build dictionary
+#if defined(POPPLER_NEW_OBJECT_API)
+ dict = Object(new Dict(xref));
+#else
+ dict.initDict(xref);
+#endif
+ _POPPLER_CALL(obj, parser->getObj);
+ while (!obj.isCmd(const_cast<char*>("ID")) && !obj.isEOF()) {
+ if (!obj.isName()) {
+ error(errSyntaxError, getPos(), "Inline image dictionary key must be a name object");
+ _POPPLER_FREE(obj);
+ } else {
+ Object obj2;
+ _POPPLER_CALL(obj2, parser->getObj);
+ if (obj2.isEOF() || obj2.isError()) {
+ _POPPLER_FREE(obj);
+ break;
+ }
+ _POPPLER_DICTADD(dict, obj.getName(), obj2);
+ _POPPLER_FREE(obj);
+ _POPPLER_FREE(obj2);
+ }
+ _POPPLER_CALL(obj, parser->getObj);
+ }
+ if (obj.isEOF()) {
+ error(errSyntaxError, getPos(), "End of file in inline image");
+ _POPPLER_FREE(obj);
+ _POPPLER_FREE(dict);
+ return nullptr;
+ }
+ _POPPLER_FREE(obj);
+
+ // make stream
+#if defined(POPPLER_NEW_OBJECT_API)
+ str = new EmbedStream(parser->getStream(), dict.copy(), gFalse, 0);
+ str = str->addFilters(dict.getDict());
+#else
+ str = new EmbedStream(parser->getStream(), &dict, gFalse, 0);
+ str = str->addFilters(&dict);
+#endif
+
+ return str;
+}
+
+void PdfParser::opImageData(Object /*args*/[], int /*numArgs*/)
+{
+ error(errInternal, getPos(), "Internal: got 'ID' operator");
+}
+
+void PdfParser::opEndImage(Object /*args*/[], int /*numArgs*/)
+{
+ error(errInternal, getPos(), "Internal: got 'EI' operator");
+}
+
+//------------------------------------------------------------------------
+// type 3 font operators
+//------------------------------------------------------------------------
+
+void PdfParser::opSetCharWidth(Object /*args*/[], int /*numArgs*/)
+{
+}
+
+void PdfParser::opSetCacheDevice(Object /*args*/[], int /*numArgs*/)
+{
+}
+
+//------------------------------------------------------------------------
+// compatibility operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginIgnoreUndef(Object /*args*/[], int /*numArgs*/)
+{
+ ++ignoreUndef;
+}
+
+void PdfParser::opEndIgnoreUndef(Object /*args*/[], int /*numArgs*/)
+{
+ if (ignoreUndef > 0)
+ --ignoreUndef;
+}
+
+//------------------------------------------------------------------------
+// marked content operators
+//------------------------------------------------------------------------
+
+void PdfParser::opBeginMarkedContent(Object args[], int numArgs) {
+ if (printCommands) {
+ printf(" marked content: %s ", args[0].getName());
+ if (numArgs == 2)
+ args[2].print(stdout);
+ printf("\n");
+ fflush(stdout);
+ }
+
+ if(numArgs == 2) {
+ //out->beginMarkedContent(args[0].getName(),args[1].getDict());
+ } else {
+ //out->beginMarkedContent(args[0].getName());
+ }
+}
+
+void PdfParser::opEndMarkedContent(Object /*args*/[], int /*numArgs*/)
+{
+ //out->endMarkedContent();
+}
+
+void PdfParser::opMarkPoint(Object args[], int numArgs) {
+ if (printCommands) {
+ printf(" mark point: %s ", args[0].getName());
+ if (numArgs == 2)
+ args[2].print(stdout);
+ printf("\n");
+ fflush(stdout);
+ }
+
+ if(numArgs == 2) {
+ //out->markPoint(args[0].getName(),args[1].getDict());
+ } else {
+ //out->markPoint(args[0].getName());
+ }
+
+}
+
+//------------------------------------------------------------------------
+// misc
+//------------------------------------------------------------------------
+
+void PdfParser::saveState() {
+ bool is_radial = false;
+
+ GfxPattern *pattern = state->getFillPattern();
+ if (pattern != nullptr)
+ if (pattern->getType() == 2 ) {
+ GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern);
+ GfxShading *shading = shading_pattern->getShading();
+ if (shading->getType() == 3)
+ is_radial = true;
+ }
+
+ builder->saveState();
+ if (is_radial)
+ state->save(); // nasty hack to prevent GfxRadialShading from getting corrupted during copy operation
+ else
+ state = state->save(); // see LP Bug 919176 comment 8
+ clipHistory = clipHistory->save();
+}
+
+void PdfParser::restoreState() {
+ clipHistory = clipHistory->restore();
+ state = state->restore();
+ builder->restoreState();
+}
+
+void PdfParser::pushResources(Dict *resDict) {
+ res = new GfxResources(xref, resDict, res);
+}
+
+void PdfParser::popResources() {
+ GfxResources *resPtr;
+
+ resPtr = res->getNext();
+ delete res;
+ res = resPtr;
+}
+
+void PdfParser::setDefaultApproximationPrecision() {
+ for (int i = 1; i <= pdfNumShadingTypes; ++i) {
+ setApproximationPrecision(i, defaultShadingColorDelta, defaultShadingMaxDepth);
+ }
+}
+
+void PdfParser::setApproximationPrecision(int shadingType, double colorDelta,
+ int maxDepth) {
+
+ if (shadingType > pdfNumShadingTypes || shadingType < 1) {
+ return;
+ }
+ colorDeltas[shadingType-1] = dblToCol(colorDelta);
+ maxDepths[shadingType-1] = maxDepth;
+}
+
+//------------------------------------------------------------------------
+// ClipHistoryEntry
+//------------------------------------------------------------------------
+
+ClipHistoryEntry::ClipHistoryEntry(GfxPath *clipPathA, GfxClipType clipTypeA) :
+ saved(nullptr),
+ clipPath((clipPathA) ? clipPathA->copy() : nullptr),
+ clipType(clipTypeA)
+{
+}
+
+ClipHistoryEntry::~ClipHistoryEntry()
+{
+ if (clipPath) {
+ delete clipPath;
+ clipPath = nullptr;
+ }
+}
+
+void ClipHistoryEntry::setClip(_POPPLER_CONST_83 GfxPath *clipPathA, GfxClipType clipTypeA) {
+ // Free previous clip path
+ if (clipPath) {
+ delete clipPath;
+ }
+ if (clipPathA) {
+ clipPath = clipPathA->copy();
+ clipType = clipTypeA;
+ } else {
+ clipPath = nullptr;
+ clipType = clipNormal;
+ }
+}
+
+ClipHistoryEntry *ClipHistoryEntry::save() {
+ ClipHistoryEntry *newEntry = new ClipHistoryEntry(this);
+ newEntry->saved = this;
+
+ return newEntry;
+}
+
+ClipHistoryEntry *ClipHistoryEntry::restore() {
+ ClipHistoryEntry *oldEntry;
+
+ if (saved) {
+ oldEntry = saved;
+ saved = nullptr;
+ delete this; // TODO really should avoid deleting from inside.
+ } else {
+ oldEntry = this;
+ }
+
+ return oldEntry;
+}
+
+ClipHistoryEntry::ClipHistoryEntry(ClipHistoryEntry *other) {
+ if (other->clipPath) {
+ this->clipPath = other->clipPath->copy();
+ this->clipType = other->clipType;
+ } else {
+ this->clipPath = nullptr;
+ this->clipType = clipNormal;
+ }
+ saved = nullptr;
+}
+
+#endif /* HAVE_POPPLER */
diff --git a/src/extension/internal/pdfinput/pdf-parser.h b/src/extension/internal/pdfinput/pdf-parser.h
new file mode 100644
index 0000000..b92c415
--- /dev/null
+++ b/src/extension/internal/pdfinput/pdf-parser.h
@@ -0,0 +1,356 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * PDF parsing using libpoppler.
+ *//*
+ * Authors:
+ * see git history
+ *
+ * Derived from Gfx.h from poppler (?) which derives from Xpdf, Copyright 1996-2003 Glyph & Cog, LLC, which is under GPL2+.
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef PDF_PARSER_H
+#define PDF_PARSER_H
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+#include "poppler-transition-api.h"
+
+#ifdef USE_GCC_PRAGMAS
+#pragma interface
+#endif
+
+namespace Inkscape {
+ namespace Extension {
+ namespace Internal {
+ class SvgBuilder;
+ }
+ }
+}
+
+// TODO clean up and remove using:
+using Inkscape::Extension::Internal::SvgBuilder;
+
+#include "glib/poppler-features.h"
+#include "Object.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+class GooString;
+class XRef;
+class Array;
+class Stream;
+class Parser;
+class Dict;
+class Function;
+class OutputDev;
+class GfxFontDict;
+class GfxFont;
+class GfxPattern;
+class GfxTilingPattern;
+class GfxShadingPattern;
+class GfxShading;
+class GfxFunctionShading;
+class GfxAxialShading;
+class GfxRadialShading;
+class GfxGouraudTriangleShading;
+class GfxPatchMeshShading;
+struct GfxPatch;
+class GfxState;
+struct GfxColor;
+class GfxColorSpace;
+class Gfx;
+class GfxResources;
+class PDFRectangle;
+class AnnotBorderStyle;
+
+class PdfParser;
+
+class ClipHistoryEntry;
+
+//------------------------------------------------------------------------
+
+#ifndef GFX_H
+enum GfxClipType {
+ clipNone,
+ clipNormal,
+ clipEO
+};
+
+enum TchkType {
+ tchkBool, // boolean
+ tchkInt, // integer
+ tchkNum, // number (integer or real)
+ tchkString, // string
+ tchkName, // name
+ tchkArray, // array
+ tchkProps, // properties (dictionary or name)
+ tchkSCN, // scn/SCN args (number of name)
+ tchkNone // used to avoid empty initializer lists
+};
+#endif /* GFX_H */
+
+#define maxOperatorArgs 33
+
+struct PdfOperator {
+ char name[4];
+ int numArgs;
+ TchkType tchk[maxOperatorArgs];
+ void (PdfParser::*func)(Object args[], int numArgs);
+};
+
+#undef maxOperatorArgs
+
+struct OpHistoryEntry {
+ const char *name; // operator's name
+ GfxState *state; // saved state, NULL if none
+ GBool executed; // whether the operator has been executed
+
+ OpHistoryEntry *next; // next entry on stack
+ unsigned depth; // total number of entries descending from this
+};
+
+//------------------------------------------------------------------------
+// PdfParser
+//------------------------------------------------------------------------
+
+//------------------------------------------------------------------------
+// constants
+//------------------------------------------------------------------------
+
+#define pdfFunctionShading 1
+#define pdfAxialShading 2
+#define pdfRadialShading 3
+#define pdfGouraudTriangleShading 4
+#define pdfPatchMeshShading 5
+#define pdfNumShadingTypes 5
+
+
+
+/**
+ * PDF parsing module using libpoppler's facilities.
+ */
+class PdfParser {
+public:
+
+ // Constructor for regular output.
+ PdfParser(XRef *xrefA, SvgBuilder *builderA, int pageNum, int rotate,
+ Dict *resDict,
+ _POPPLER_CONST PDFRectangle *box,
+ _POPPLER_CONST PDFRectangle *cropBox);
+
+ // Constructor for a sub-page object.
+ PdfParser(XRef *xrefA, Inkscape::Extension::Internal::SvgBuilder *builderA,
+ Dict *resDict,
+ _POPPLER_CONST PDFRectangle *box);
+
+ virtual ~PdfParser();
+
+ // Interpret a stream or array of streams.
+ void parse(Object *obj, GBool topLevel = gTrue);
+
+ // Save graphics state.
+ void saveState();
+
+ // Restore graphics state.
+ void restoreState();
+
+ // Get the current graphics state object.
+ GfxState *getState() { return state; }
+
+ // Set the precision of approximation for specific shading fills.
+ void setApproximationPrecision(int shadingType, double colorDelta, int maxDepth);
+
+private:
+
+ XRef *xref; // the xref table for this PDF file
+ SvgBuilder *builder; // SVG generator
+ GBool subPage; // is this a sub-page object?
+ GBool printCommands; // print the drawing commands (for debugging)
+ GfxResources *res; // resource stack
+
+ GfxState *state; // current graphics state
+ GBool fontChanged; // set if font or text matrix has changed
+ GfxClipType clip; // do a clip?
+ int ignoreUndef; // current BX/EX nesting level
+ double baseMatrix[6]; // default matrix for most recent
+ // page/form/pattern
+ int formDepth;
+
+ Parser *parser; // parser for page content stream(s)
+
+ static PdfOperator opTab[]; // table of operators
+
+ int colorDeltas[pdfNumShadingTypes];
+ // max deltas allowed in any color component
+ // for the approximation of shading fills
+ int maxDepths[pdfNumShadingTypes]; // max recursive depths
+
+ ClipHistoryEntry *clipHistory; // clip path stack
+ OpHistoryEntry *operatorHistory; // list containing the last N operators
+
+ //! Caches color spaces by name
+ std::map<std::string, std::unique_ptr<GfxColorSpace>> colorSpacesCache;
+
+ GfxColorSpace *lookupColorSpaceCopy(Object &);
+
+ void setDefaultApproximationPrecision(); // init color deltas
+ void pushOperator(const char *name);
+ OpHistoryEntry *popOperator();
+ const char *getPreviousOperator(unsigned int look_back=1); // returns the nth previous operator's name
+
+ void go(GBool topLevel);
+ void execOp(Object *cmd, Object args[], int numArgs);
+ PdfOperator *findOp(const char *name);
+ GBool checkArg(Object *arg, TchkType type);
+ int getPos();
+
+ // graphics state operators
+ void opSave(Object args[], int numArgs);
+ void opRestore(Object args[], int numArgs);
+ void opConcat(Object args[], int numArgs);
+ void opSetDash(Object args[], int numArgs);
+ void opSetFlat(Object args[], int numArgs);
+ void opSetLineJoin(Object args[], int numArgs);
+ void opSetLineCap(Object args[], int numArgs);
+ void opSetMiterLimit(Object args[], int numArgs);
+ void opSetLineWidth(Object args[], int numArgs);
+ void opSetExtGState(Object args[], int numArgs);
+ void doSoftMask(Object *str, GBool alpha,
+ GfxColorSpace *blendingColorSpace,
+ GBool isolated, GBool knockout,
+ Function *transferFunc, GfxColor *backdropColor);
+ void opSetRenderingIntent(Object args[], int numArgs);
+
+ // color operators
+ void opSetFillGray(Object args[], int numArgs);
+ void opSetStrokeGray(Object args[], int numArgs);
+ void opSetFillCMYKColor(Object args[], int numArgs);
+ void opSetStrokeCMYKColor(Object args[], int numArgs);
+ void opSetFillRGBColor(Object args[], int numArgs);
+ void opSetStrokeRGBColor(Object args[], int numArgs);
+ void opSetFillColorSpace(Object args[], int numArgs);
+ void opSetStrokeColorSpace(Object args[], int numArgs);
+ void opSetFillColor(Object args[], int numArgs);
+ void opSetStrokeColor(Object args[], int numArgs);
+ void opSetFillColorN(Object args[], int numArgs);
+ void opSetStrokeColorN(Object args[], int numArgs);
+
+ // path segment operators
+ void opMoveTo(Object args[], int numArgs);
+ void opLineTo(Object args[], int numArgs);
+ void opCurveTo(Object args[], int numArgs);
+ void opCurveTo1(Object args[], int numArgs);
+ void opCurveTo2(Object args[], int numArgs);
+ void opRectangle(Object args[], int numArgs);
+ void opClosePath(Object args[], int numArgs);
+
+ // path painting operators
+ void opEndPath(Object args[], int numArgs);
+ void opStroke(Object args[], int numArgs);
+ void opCloseStroke(Object args[], int numArgs);
+ void opFill(Object args[], int numArgs);
+ void opEOFill(Object args[], int numArgs);
+ void opFillStroke(Object args[], int numArgs);
+ void opCloseFillStroke(Object args[], int numArgs);
+ void opEOFillStroke(Object args[], int numArgs);
+ void opCloseEOFillStroke(Object args[], int numArgs);
+ void doFillAndStroke(GBool eoFill);
+ void doPatternFillFallback(GBool eoFill);
+ void doPatternStrokeFallback();
+ void doShadingPatternFillFallback(GfxShadingPattern *sPat,
+ GBool stroke, GBool eoFill);
+ void opShFill(Object args[], int numArgs);
+ void doFunctionShFill(GfxFunctionShading *shading);
+ void doFunctionShFill1(GfxFunctionShading *shading,
+ double x0, double y0,
+ double x1, double y1,
+ GfxColor *colors, int depth);
+ void doGouraudTriangleShFill(GfxGouraudTriangleShading *shading);
+ void gouraudFillTriangle(double x0, double y0, GfxColor *color0,
+ double x1, double y1, GfxColor *color1,
+ double x2, double y2, GfxColor *color2,
+ int nComps, int depth);
+ void doPatchMeshShFill(GfxPatchMeshShading *shading);
+ void fillPatch(_POPPLER_CONST GfxPatch *patch, int nComps, int depth);
+ void doEndPath();
+
+ // path clipping operators
+ void opClip(Object args[], int numArgs);
+ void opEOClip(Object args[], int numArgs);
+
+ // text object operators
+ void opBeginText(Object args[], int numArgs);
+ void opEndText(Object args[], int numArgs);
+
+ // text state operators
+ void opSetCharSpacing(Object args[], int numArgs);
+ void opSetFont(Object args[], int numArgs);
+ void opSetTextLeading(Object args[], int numArgs);
+ void opSetTextRender(Object args[], int numArgs);
+ void opSetTextRise(Object args[], int numArgs);
+ void opSetWordSpacing(Object args[], int numArgs);
+ void opSetHorizScaling(Object args[], int numArgs);
+
+ // text positioning operators
+ void opTextMove(Object args[], int numArgs);
+ void opTextMoveSet(Object args[], int numArgs);
+ void opSetTextMatrix(Object args[], int numArgs);
+ void opTextNextLine(Object args[], int numArgs);
+
+ // text string operators
+ void opShowText(Object args[], int numArgs);
+ void opMoveShowText(Object args[], int numArgs);
+ void opMoveSetShowText(Object args[], int numArgs);
+ void opShowSpaceText(Object args[], int numArgs);
+#if POPPLER_CHECK_VERSION(0,64,0)
+ void doShowText(const GooString *s);
+#else
+ void doShowText(GooString *s);
+#endif
+
+
+ // XObject operators
+ void opXObject(Object args[], int numArgs);
+ void doImage(Object *ref, Stream *str, GBool inlineImg);
+ void doForm(Object *str);
+ void doForm1(Object *str, Dict *resDict, double *matrix, double *bbox,
+ GBool transpGroup = gFalse, GBool softMask = gFalse,
+ GfxColorSpace *blendingColorSpace = nullptr,
+ GBool isolated = gFalse, GBool knockout = gFalse,
+ GBool alpha = gFalse, Function *transferFunc = nullptr,
+ GfxColor *backdropColor = nullptr);
+
+ // in-line image operators
+ void opBeginImage(Object args[], int numArgs);
+ Stream *buildImageStream();
+ void opImageData(Object args[], int numArgs);
+ void opEndImage(Object args[], int numArgs);
+
+ // type 3 font operators
+ void opSetCharWidth(Object args[], int numArgs);
+ void opSetCacheDevice(Object args[], int numArgs);
+
+ // compatibility operators
+ void opBeginIgnoreUndef(Object args[], int numArgs);
+ void opEndIgnoreUndef(Object args[], int numArgs);
+
+ // marked content operators
+ void opBeginMarkedContent(Object args[], int numArgs);
+ void opEndMarkedContent(Object args[], int numArgs);
+ void opMarkPoint(Object args[], int numArgs);
+
+ void pushResources(Dict *resDict);
+ void popResources();
+};
+
+#endif /* HAVE_POPPLER */
+
+#endif /* PDF_PARSER_H */
diff --git a/src/extension/internal/pdfinput/poppler-transition-api.h b/src/extension/internal/pdfinput/poppler-transition-api.h
new file mode 100644
index 0000000..dc9e47e
--- /dev/null
+++ b/src/extension/internal/pdfinput/poppler-transition-api.h
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * TODO short description
+ *//*
+ * Authors:
+ * see git history
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_POPPLER_TRANSITION_API_H
+#define SEEN_POPPLER_TRANSITION_API_H
+
+#include <glib/poppler-features.h>
+
+#if POPPLER_CHECK_VERSION(22, 4, 0)
+#define _POPPLER_FONTPTR_TO_GFX8(font_ptr) ((Gfx8BitFont *)font_ptr.get())
+#else
+#define _POPPLER_FONTPTR_TO_GFX8(font_ptr) ((Gfx8BitFont *)font_ptr)
+#endif
+
+#if POPPLER_CHECK_VERSION(22, 3, 0)
+#define _POPPLER_MAKE_SHARED_PDFDOC(uri) std::make_shared<PDFDoc>(std::make_unique<GooString>(uri))
+#else
+#define _POPPLER_MAKE_SHARED_PDFDOC(uri) std::make_shared<PDFDoc>(new GooString(uri), nullptr, nullptr, nullptr)
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 83, 0)
+#define _POPPLER_CONST_83 const
+#else
+#define _POPPLER_CONST_83
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 82, 0)
+#define _POPPLER_CONST_82 const
+#else
+#define _POPPLER_CONST_82
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 76, 0)
+#define _POPPLER_NEW_PARSER(xref, obj) Parser(xref, obj, gFalse)
+#else
+#define _POPPLER_NEW_PARSER(xref, obj) Parser(xref, new Lexer(xref, obj), gFalse)
+#endif
+
+#if POPPLER_CHECK_VERSION(0, 83, 0)
+#define _POPPLER_NEW_GLOBAL_PARAMS(args...) std::unique_ptr<GlobalParams>(new GlobalParams(args))
+#else
+#define _POPPLER_NEW_GLOBAL_PARAMS(args...) new GlobalParams(args)
+#endif
+
+
+#if POPPLER_CHECK_VERSION(0, 72, 0)
+#define getCString c_str
+#endif
+
+#if POPPLER_CHECK_VERSION(0,71,0)
+typedef bool GBool;
+#define gTrue true
+#define gFalse false
+#endif
+
+#if POPPLER_CHECK_VERSION(0,70,0)
+#define _POPPLER_CONST const
+#else
+#define _POPPLER_CONST
+#endif
+
+#if POPPLER_CHECK_VERSION(0,69,0)
+#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(key, std::move(obj))
+#elif POPPLER_CHECK_VERSION(0,58,0)
+#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(copyString(key), std::move(obj))
+#else
+#define _POPPLER_DICTADD(dict, key, obj) (dict).dictAdd(copyString(key), &obj)
+#endif
+
+#if POPPLER_CHECK_VERSION(0,58,0)
+#define POPPLER_NEW_OBJECT_API
+#define _POPPLER_FREE(obj)
+#define _POPPLER_CALL(ret, func) (ret = func())
+#define _POPPLER_CALL_ARGS(ret, func, ...) (ret = func(__VA_ARGS__))
+#define _POPPLER_CALL_ARGS_DEREF _POPPLER_CALL_ARGS
+#else
+#define _POPPLER_FREE(obj) (obj).free()
+#define _POPPLER_CALL(ret, func) (*func(&ret))
+#define _POPPLER_CALL_ARGS(ret, func, ...) (func(__VA_ARGS__, &ret))
+#define _POPPLER_CALL_ARGS_DEREF(...) (*_POPPLER_CALL_ARGS(__VA_ARGS__))
+#endif
+
+#if !POPPLER_CHECK_VERSION(0, 29, 0)
+#error "Requires poppler >= 0.29"
+#endif
+
+#endif
diff --git a/src/extension/internal/pdfinput/svg-builder.cpp b/src/extension/internal/pdfinput/svg-builder.cpp
new file mode 100644
index 0000000..9c49d37
--- /dev/null
+++ b/src/extension/internal/pdfinput/svg-builder.cpp
@@ -0,0 +1,1983 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Native PDF import using libpoppler.
+ *
+ * Authors:
+ * miklos erdelyi
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <string>
+
+#ifdef HAVE_POPPLER
+
+#include "svg-builder.h"
+#include "pdf-parser.h"
+
+#include "document.h"
+#include "object/sp-namedview.h"
+#include "png.h"
+
+#include "xml/document.h"
+#include "xml/node.h"
+#include "xml/repr.h"
+#include "svg/svg.h"
+#include "svg/path-string.h"
+#include "svg/css-ostringstream.h"
+#include "svg/svg-color.h"
+#include "color.h"
+#include "util/units.h"
+#include "display/nr-filter-utils.h"
+#include "libnrtype/font-instance.h"
+#include "object/sp-defs.h"
+
+#include "Function.h"
+#include "GfxState.h"
+#include "GfxFont.h"
+#include "Stream.h"
+#include "Page.h"
+#include "UnicodeMap.h"
+#include "GlobalParams.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+//#define IFTRACE(_code) _code
+#define IFTRACE(_code)
+
+#define TRACE(_args) IFTRACE(g_print _args)
+
+/**
+ * \struct SvgTransparencyGroup
+ * \brief Holds information about a PDF transparency group
+ */
+struct SvgTransparencyGroup {
+ double bbox[6]; // TODO should this be 4?
+ Inkscape::XML::Node *container;
+
+ bool isolated;
+ bool knockout;
+ bool for_softmask;
+
+ SvgTransparencyGroup *next;
+};
+
+/**
+ * \class SvgBuilder
+ *
+ */
+
+SvgBuilder::SvgBuilder(SPDocument *document, gchar *docname, XRef *xref)
+{
+ _is_top_level = true;
+ _doc = document;
+ _docname = docname;
+ _xref = xref;
+ _xml_doc = _doc->getReprDoc();
+ _container = _root = _doc->getReprRoot();
+ _init();
+
+ // Set default preference settings
+ _preferences = _xml_doc->createElement("svgbuilder:prefs");
+ _preferences->setAttribute("embedImages", "1");
+ _preferences->setAttribute("localFonts", "1");
+}
+
+SvgBuilder::SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root) {
+ _is_top_level = false;
+ _doc = parent->_doc;
+ _docname = parent->_docname;
+ _xref = parent->_xref;
+ _xml_doc = parent->_xml_doc;
+ _preferences = parent->_preferences;
+ _container = this->_root = root;
+ _init();
+}
+
+SvgBuilder::~SvgBuilder() = default;
+
+void SvgBuilder::_init() {
+ _font_style = nullptr;
+ _font_specification = nullptr;
+ _font_scaling = 1;
+ _need_font_update = true;
+ _in_text_object = false;
+ _invalidated_style = true;
+ _current_state = nullptr;
+ _width = 0;
+ _height = 0;
+
+ // Fill _availableFontNames (Bug LP #179589) (code cfr. FontLister)
+ std::vector<PangoFontFamily *> families;
+ font_factory::Default()->GetUIFamilies(families);
+ for (auto & familie : families) {
+ _availableFontNames.emplace_back(pango_font_family_get_name(familie));
+ }
+
+ _transp_group_stack = nullptr;
+ SvgGraphicsState initial_state;
+ initial_state.softmask = nullptr;
+ initial_state.group_depth = 0;
+ _state_stack.push_back(initial_state);
+ _node_stack.push_back(_container);
+
+ _ttm[0] = 1; _ttm[1] = 0; _ttm[2] = 0; _ttm[3] = 1; _ttm[4] = 0; _ttm[5] = 0;
+ _ttm_is_set = false;
+}
+
+/**
+ * We're creating a multi-page document, push page number.
+ */
+void SvgBuilder::pushPage() {
+ // Move page over by the last page width
+ if (_page && this->_width) {
+ int gap = 20;
+ _page_left += this->_width + gap;
+ // TODO: A more interesting page layout could be implemented here.
+ }
+ _page_num += 1;
+ _page_offset = true;
+
+ if (_page) {
+ Inkscape::GC::release(_page);
+ }
+ _page = _xml_doc->createElement("inkscape:page");
+ _page->setAttributeSvgDouble("x", _page_left);
+ _page->setAttributeSvgDouble("y", _page_top);
+ auto _nv = _doc->getNamedView()->getRepr();
+ _nv->appendChild(_page);
+}
+
+void SvgBuilder::setDocumentSize(double width, double height) {
+ this->_width = width;
+ this->_height = height;
+
+ if (_page_num < 2) {
+ _root->setAttributeSvgDouble("width", width);
+ _root->setAttributeSvgDouble("height", height);
+ }
+ if (_page) {
+ _page->setAttributeSvgDouble("width", width);
+ _page->setAttributeSvgDouble("height", height);
+ }
+}
+
+/**
+ * \brief Sets groupmode of the current container to 'layer' and sets its label if given
+ */
+void SvgBuilder::setAsLayer(char *layer_name) {
+ _container->setAttribute("inkscape:groupmode", "layer");
+ if (layer_name) {
+ _container->setAttribute("inkscape:label", layer_name);
+ }
+}
+
+/**
+ * \brief Sets the current container's opacity
+ */
+void SvgBuilder::setGroupOpacity(double opacity) {
+ _container->setAttributeSvgDouble("opacity", CLAMP(opacity, 0.0, 1.0));
+}
+
+void SvgBuilder::saveState() {
+ SvgGraphicsState new_state;
+ new_state.group_depth = 0;
+ new_state.softmask = _state_stack.back().softmask;
+ _state_stack.push_back(new_state);
+ pushGroup();
+}
+
+void SvgBuilder::restoreState() {
+ while( _state_stack.back().group_depth > 0 ) {
+ popGroup();
+ }
+ _state_stack.pop_back();
+}
+
+Inkscape::XML::Node *SvgBuilder::pushNode(const char *name) {
+ Inkscape::XML::Node *node = _xml_doc->createElement(name);
+ _node_stack.push_back(node);
+ _container = node;
+ return node;
+}
+
+Inkscape::XML::Node *SvgBuilder::popNode() {
+ Inkscape::XML::Node *node = nullptr;
+ if ( _node_stack.size() > 1 ) {
+ node = _node_stack.back();
+ _node_stack.pop_back();
+ _container = _node_stack.back(); // Re-set container
+ } else {
+ TRACE(("popNode() called when stack is empty\n"));
+ node = _root;
+ }
+ return node;
+}
+
+Inkscape::XML::Node *SvgBuilder::pushGroup() {
+ Inkscape::XML::Node *saved_container = _container;
+ Inkscape::XML::Node *node = pushNode("svg:g");
+ saved_container->appendChild(node);
+ Inkscape::GC::release(node);
+ _state_stack.back().group_depth++;
+ // Set as a layer if this is a top-level group
+ if ( _container->parent() == _root && _is_top_level ) {
+ static int layer_count = 1;
+ if (_page_num) {
+ gchar *layer_name = g_strdup_printf("Page %d", _page_num);
+ setAsLayer(layer_name);
+ g_free(layer_name);
+ } else if ( layer_count > 1 ) {
+ gchar *layer_name = g_strdup_printf("%s%d", _docname, layer_count);
+ setAsLayer(layer_name);
+ g_free(layer_name);
+ layer_count++;
+ } else {
+ setAsLayer(_docname);
+ layer_count++;
+ }
+ }
+ if (_container->parent()->attribute("inkscape:groupmode") != nullptr) {
+ _ttm[0] = _ttm[3] = 1.0; // clear ttm if parent is a layer
+ _ttm[1] = _ttm[2] = _ttm[4] = _ttm[5] = 0.0;
+ _ttm_is_set = false;
+ }
+ return _container;
+}
+
+Inkscape::XML::Node *SvgBuilder::popGroup() {
+ if (_container != _root) { // Pop if the current container isn't root
+ popNode();
+ _state_stack.back().group_depth--;
+ }
+
+ return _container;
+}
+
+Inkscape::XML::Node *SvgBuilder::getContainer() {
+ return _container;
+}
+
+static gchar *svgConvertRGBToText(double r, double g, double b) {
+ using Inkscape::Filters::clamp;
+ static gchar tmp[1023] = {0};
+ snprintf(tmp, 1023,
+ "#%02x%02x%02x",
+ clamp(SP_COLOR_F_TO_U(r)),
+ clamp(SP_COLOR_F_TO_U(g)),
+ clamp(SP_COLOR_F_TO_U(b)));
+ return (gchar *)&tmp;
+}
+
+static gchar *svgConvertGfxRGB(GfxRGB *color) {
+ double r = (double)color->r / 65535.0;
+ double g = (double)color->g / 65535.0;
+ double b = (double)color->b / 65535.0;
+ return svgConvertRGBToText(r, g, b);
+}
+
+static void svgSetTransform(Inkscape::XML::Node *node, Geom::Affine matrix) {
+ node->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(matrix));
+}
+
+/**
+ * \brief Generates a SVG path string from poppler's data structure
+ */
+static gchar *svgInterpretPath(_POPPLER_CONST_83 GfxPath *path) {
+ Inkscape::SVG::PathString pathString;
+ for (int i = 0 ; i < path->getNumSubpaths() ; ++i ) {
+ _POPPLER_CONST_83 GfxSubpath *subpath = path->getSubpath(i);
+ if (subpath->getNumPoints() > 0) {
+ pathString.moveTo(subpath->getX(0), subpath->getY(0));
+ int j = 1;
+ while (j < subpath->getNumPoints()) {
+ if (subpath->getCurve(j)) {
+ pathString.curveTo(subpath->getX(j), subpath->getY(j),
+ subpath->getX(j+1), subpath->getY(j+1),
+ subpath->getX(j+2), subpath->getY(j+2));
+
+ j += 3;
+ } else {
+ pathString.lineTo(subpath->getX(j), subpath->getY(j));
+ ++j;
+ }
+ }
+ if (subpath->isClosed()) {
+ pathString.closePath();
+ }
+ }
+ }
+
+ return g_strdup(pathString.c_str());
+}
+
+/**
+ * \brief Sets stroke style from poppler's GfxState data structure
+ * Uses the given SPCSSAttr for storing the style properties
+ */
+void SvgBuilder::_setStrokeStyle(SPCSSAttr *css, GfxState *state) {
+ // Stroke color/pattern
+ if ( state->getStrokeColorSpace()->getMode() == csPattern ) {
+ gchar *urltext = _createPattern(state->getStrokePattern(), state, true);
+ sp_repr_css_set_property(css, "stroke", urltext);
+ if (urltext) {
+ g_free(urltext);
+ }
+ } else {
+ GfxRGB stroke_color;
+ state->getStrokeRGB(&stroke_color);
+ sp_repr_css_set_property(css, "stroke", svgConvertGfxRGB(&stroke_color));
+ }
+
+ // Opacity
+ Inkscape::CSSOStringStream os_opacity;
+ os_opacity << state->getStrokeOpacity();
+ sp_repr_css_set_property(css, "stroke-opacity", os_opacity.str().c_str());
+
+ // Line width
+ Inkscape::CSSOStringStream os_width;
+ double lw = state->getLineWidth();
+ if (lw > 0.0) {
+ os_width << lw;
+ } else {
+ // emit a stroke which is 1px in toplevel user units
+ double pxw = Inkscape::Util::Quantity::convert(1.0, "pt", "px");
+ os_width << 1.0 / state->transformWidth(pxw);
+ }
+ sp_repr_css_set_property(css, "stroke-width", os_width.str().c_str());
+
+ // Line cap
+ switch (state->getLineCap()) {
+ case 0:
+ sp_repr_css_set_property(css, "stroke-linecap", "butt");
+ break;
+ case 1:
+ sp_repr_css_set_property(css, "stroke-linecap", "round");
+ break;
+ case 2:
+ sp_repr_css_set_property(css, "stroke-linecap", "square");
+ break;
+ }
+
+ // Line join
+ switch (state->getLineJoin()) {
+ case 0:
+ sp_repr_css_set_property(css, "stroke-linejoin", "miter");
+ break;
+ case 1:
+ sp_repr_css_set_property(css, "stroke-linejoin", "round");
+ break;
+ case 2:
+ sp_repr_css_set_property(css, "stroke-linejoin", "bevel");
+ break;
+ }
+
+ // Miterlimit
+ Inkscape::CSSOStringStream os_ml;
+ os_ml << state->getMiterLimit();
+ sp_repr_css_set_property(css, "stroke-miterlimit", os_ml.str().c_str());
+
+ // Line dash
+ int dash_length;
+ double dash_start;
+#if POPPLER_CHECK_VERSION(22, 9, 0)
+ const double *dash_pattern;
+ const std::vector<double> &dash = state->getLineDash(&dash_start);
+ dash_pattern = dash.data();
+ dash_length = dash.size();
+#else
+ double *dash_pattern;
+ state->getLineDash(&dash_pattern, &dash_length, &dash_start);
+#endif
+ if ( dash_length > 0 ) {
+ Inkscape::CSSOStringStream os_array;
+ for ( int i = 0 ; i < dash_length ; i++ ) {
+ os_array << dash_pattern[i];
+ if (i < (dash_length - 1)) {
+ os_array << ",";
+ }
+ }
+ sp_repr_css_set_property(css, "stroke-dasharray", os_array.str().c_str());
+
+ Inkscape::CSSOStringStream os_offset;
+ os_offset << dash_start;
+ sp_repr_css_set_property(css, "stroke-dashoffset", os_offset.str().c_str());
+ } else {
+ sp_repr_css_set_property(css, "stroke-dasharray", "none");
+ sp_repr_css_set_property(css, "stroke-dashoffset", nullptr);
+ }
+}
+
+/**
+ * \brief Sets fill style from poppler's GfxState data structure
+ * Uses the given SPCSSAttr for storing the style properties.
+ */
+void SvgBuilder::_setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd) {
+
+ // Fill color/pattern
+ if ( state->getFillColorSpace()->getMode() == csPattern ) {
+ gchar *urltext = _createPattern(state->getFillPattern(), state);
+ sp_repr_css_set_property(css, "fill", urltext);
+ if (urltext) {
+ g_free(urltext);
+ }
+ } else {
+ GfxRGB fill_color;
+ state->getFillRGB(&fill_color);
+ sp_repr_css_set_property(css, "fill", svgConvertGfxRGB(&fill_color));
+ }
+
+ // Opacity
+ Inkscape::CSSOStringStream os_opacity;
+ os_opacity << state->getFillOpacity();
+ sp_repr_css_set_property(css, "fill-opacity", os_opacity.str().c_str());
+
+ // Fill rule
+ sp_repr_css_set_property(css, "fill-rule", even_odd ? "evenodd" : "nonzero");
+}
+
+/**
+ * \brief Sets blend style properties from poppler's GfxState data structure
+ * \update a SPCSSAttr with all mix-blend-mode set
+ */
+void SvgBuilder::_setBlendMode(Inkscape::XML::Node *node, GfxState *state)
+{
+ SPCSSAttr *css = sp_repr_css_attr(node, "style");
+ GfxBlendMode blendmode = state->getBlendMode();
+ if (blendmode) {
+ sp_repr_css_set_property(css, "mix-blend-mode", enum_blend_mode[blendmode].key);
+ }
+ Glib::ustring value;
+ sp_repr_css_write_string(css, value);
+ node->setAttributeOrRemoveIfEmpty("style", value);
+ sp_repr_css_attr_unref(css);
+}
+/**
+ * \brief Sets style properties from poppler's GfxState data structure
+ * \return SPCSSAttr with all the relevant properties set
+ */
+SPCSSAttr *SvgBuilder::_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd) {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ if (fill) {
+ _setFillStyle(css, state, even_odd);
+ } else {
+ sp_repr_css_set_property(css, "fill", "none");
+ }
+
+ if (stroke) {
+ _setStrokeStyle(css, state);
+ } else {
+ sp_repr_css_set_property(css, "stroke", "none");
+ }
+
+ return css;
+}
+
+/**
+ * \brief Emits the current path in poppler's GfxState data structure
+ * Can be used to do filling and stroking at once.
+ *
+ * \param fill whether the path should be filled
+ * \param stroke whether the path should be stroked
+ * \param even_odd whether the even-odd rule should be used when filling the path
+ */
+void SvgBuilder::addPath(GfxState *state, bool fill, bool stroke, bool even_odd) {
+ Inkscape::XML::Node *path = _xml_doc->createElement("svg:path");
+ gchar *pathtext = svgInterpretPath(state->getPath());
+ path->setAttribute("d", pathtext);
+ g_free(pathtext);
+
+ // Set style
+ SPCSSAttr *css = _setStyle(state, fill, stroke, even_odd);
+ sp_repr_css_change(path, css, "style");
+ sp_repr_css_attr_unref(css);
+ _setBlendMode(path, state);
+ _container->appendChild(path);
+ Inkscape::GC::release(path);
+}
+
+/**
+ * \brief Emits the current path in poppler's GfxState data structure
+ * The path is set to be filled with the given shading.
+ */
+void SvgBuilder::addShadedFill(GfxShading *shading, double *matrix, GfxPath *path,
+ bool even_odd) {
+
+ Inkscape::XML::Node *path_node = _xml_doc->createElement("svg:path");
+ gchar *pathtext = svgInterpretPath(path);
+ path_node->setAttribute("d", pathtext);
+ g_free(pathtext);
+
+ // Set style
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ gchar *id = _createGradient(shading, matrix, true);
+ if (id) {
+ gchar *urltext = g_strdup_printf ("url(#%s)", id);
+ sp_repr_css_set_property(css, "fill", urltext);
+ g_free(urltext);
+ g_free(id);
+ } else {
+ sp_repr_css_attr_unref(css);
+ Inkscape::GC::release(path_node);
+ return;
+ }
+ if (even_odd) {
+ sp_repr_css_set_property(css, "fill-rule", "evenodd");
+ }
+ sp_repr_css_set_property(css, "stroke", "none");
+ sp_repr_css_change(path_node, css, "style");
+ sp_repr_css_attr_unref(css);
+
+ _container->appendChild(path_node);
+ Inkscape::GC::release(path_node);
+
+ // Remove the clipping path emitted before the 'sh' operator
+ int up_walk = 0;
+ Inkscape::XML::Node *node = _container->parent();
+ while( node && node->childCount() == 1 && up_walk < 3 ) {
+ gchar const *clip_path_url = node->attribute("clip-path");
+ if (clip_path_url) {
+ // Obtain clipping path's id from the URL
+ gchar clip_path_id[32];
+ strncpy(clip_path_id, clip_path_url + 5, strlen(clip_path_url) - 6);
+ clip_path_id[sizeof (clip_path_id) - 1] = '\0';
+ SPObject *clip_obj = _doc->getObjectById(clip_path_id);
+ if (clip_obj) {
+ clip_obj->deleteObject();
+ node->removeAttribute("clip-path");
+ TRACE(("removed clipping path: %s\n", clip_path_id));
+ }
+ break;
+ }
+ node = node->parent();
+ up_walk++;
+ }
+}
+
+/**
+ * \brief Clips to the current path set in GfxState
+ * \param state poppler's data structure
+ * \param even_odd whether the even-odd rule should be applied
+ */
+void SvgBuilder::clip(GfxState *state, bool even_odd) {
+ pushGroup();
+ setClipPath(state, even_odd);
+}
+
+void SvgBuilder::setClipPath(GfxState *state, bool even_odd) {
+ // Create the clipPath repr
+ Inkscape::XML::Node *clip_path = _xml_doc->createElement("svg:clipPath");
+ clip_path->setAttribute("clipPathUnits", "userSpaceOnUse");
+ // Create the path
+ Inkscape::XML::Node *path = _xml_doc->createElement("svg:path");
+ gchar *pathtext = svgInterpretPath(state->getPath());
+ path->setAttribute("d", pathtext);
+ g_free(pathtext);
+ if (even_odd) {
+ path->setAttribute("clip-rule", "evenodd");
+ }
+ clip_path->appendChild(path);
+ Inkscape::GC::release(path);
+ // Append clipPath to defs and get id
+ _doc->getDefs()->getRepr()->appendChild(clip_path);
+ gchar *urltext = g_strdup_printf ("url(#%s)", clip_path->attribute("id"));
+ Inkscape::GC::release(clip_path);
+ _container->setAttribute("clip-path", urltext);
+ g_free(urltext);
+}
+
+/**
+ * \brief Fills the given array with the current container's transform, if set
+ * \param transform array of doubles to be filled
+ * \return true on success; false on invalid transformation
+ */
+bool SvgBuilder::getTransform(double *transform) {
+ Geom::Affine svd;
+ gchar const *tr = _container->attribute("transform");
+ bool valid = sp_svg_transform_read(tr, &svd);
+ if (valid) {
+ for ( int i = 0 ; i < 6 ; i++ ) {
+ transform[i] = svd[i];
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+/**
+ * \brief Sets the transformation matrix of the current container
+ */
+void SvgBuilder::setTransform(double c0, double c1, double c2, double c3,
+ double c4, double c5) {
+
+ auto matrix = Geom::Affine(c0, c1, c2, c3, c4, c5);
+
+ // Add page transformation only once (see scaledCTM in pdf-parser.cpp)
+ if ( _container->parent() == _root && _is_top_level && _page_offset) {
+ matrix = matrix * Geom::Translate(_page_left, _page_top);
+ _page_offset = false;
+ }
+
+ // do not remember the group which is a layer
+ if ((_container->attribute("inkscape:groupmode") == nullptr) && !_ttm_is_set) {
+ _ttm[0] = c0;
+ _ttm[1] = c1;
+ _ttm[2] = c2;
+ _ttm[3] = c3;
+ _ttm[4] = c4;
+ _ttm[5] = c5;
+ _ttm_is_set = true;
+ }
+
+ // Avoid transforming a group with an already set clip-path
+ if ( _container->attribute("clip-path") != nullptr ) {
+ pushGroup();
+ }
+ TRACE(("setTransform: %f %f %f %f %f %f\n", c0, c1, c2, c3, c4, c5));
+ svgSetTransform(_container, matrix);
+}
+
+void SvgBuilder::setTransform(double const *transform) {
+ setTransform(transform[0], transform[1], transform[2], transform[3],
+ transform[4], transform[5]);
+}
+
+/**
+ * \brief Checks whether the given pattern type can be represented in SVG
+ * Used by PdfParser to decide when to do fallback operations.
+ */
+bool SvgBuilder::isPatternTypeSupported(GfxPattern *pattern) {
+ if ( pattern != nullptr ) {
+ if ( pattern->getType() == 2 ) { // shading pattern
+ GfxShading *shading = (static_cast<GfxShadingPattern *>(pattern))->getShading();
+ int shadingType = shading->getType();
+ if ( shadingType == 2 || // axial shading
+ shadingType == 3 ) { // radial shading
+ return true;
+ }
+ return false;
+ } else if ( pattern->getType() == 1 ) { // tiling pattern
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * \brief Creates a pattern from poppler's data structure
+ * Handles linear and radial gradients. Creates a new PdfParser and uses it to
+ * build a tiling pattern.
+ * \return a url pointing to the created pattern
+ */
+gchar *SvgBuilder::_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke) {
+ gchar *id = nullptr;
+ if ( pattern != nullptr ) {
+ if ( pattern->getType() == 2 ) { // Shading pattern
+ GfxShadingPattern *shading_pattern = static_cast<GfxShadingPattern *>(pattern);
+ const double *ptm;
+ double m[6] = {1, 0, 0, 1, 0, 0};
+ double det;
+
+ // construct a (pattern space) -> (current space) transform matrix
+
+ ptm = shading_pattern->getMatrix();
+ det = _ttm[0] * _ttm[3] - _ttm[1] * _ttm[2];
+ if (det) {
+ double ittm[6]; // invert ttm
+ ittm[0] = _ttm[3] / det;
+ ittm[1] = -_ttm[1] / det;
+ ittm[2] = -_ttm[2] / det;
+ ittm[3] = _ttm[0] / det;
+ ittm[4] = (_ttm[2] * _ttm[5] - _ttm[3] * _ttm[4]) / det;
+ ittm[5] = (_ttm[1] * _ttm[4] - _ttm[0] * _ttm[5]) / det;
+ m[0] = ptm[0] * ittm[0] + ptm[1] * ittm[2];
+ m[1] = ptm[0] * ittm[1] + ptm[1] * ittm[3];
+ m[2] = ptm[2] * ittm[0] + ptm[3] * ittm[2];
+ m[3] = ptm[2] * ittm[1] + ptm[3] * ittm[3];
+ m[4] = ptm[4] * ittm[0] + ptm[5] * ittm[2] + ittm[4];
+ m[5] = ptm[4] * ittm[1] + ptm[5] * ittm[3] + ittm[5];
+ }
+ id = _createGradient(shading_pattern->getShading(),
+ m,
+ !is_stroke);
+ } else if ( pattern->getType() == 1 ) { // Tiling pattern
+ id = _createTilingPattern(static_cast<GfxTilingPattern*>(pattern), state, is_stroke);
+ }
+ } else {
+ return nullptr;
+ }
+ gchar *urltext = g_strdup_printf ("url(#%s)", id);
+ g_free(id);
+ return urltext;
+}
+
+/**
+ * \brief Creates a tiling pattern from poppler's data structure
+ * Creates a sub-page PdfParser and uses it to parse the pattern's content stream.
+ * \return id of the created pattern
+ */
+gchar *SvgBuilder::_createTilingPattern(GfxTilingPattern *tiling_pattern,
+ GfxState *state, bool is_stroke) {
+
+ Inkscape::XML::Node *pattern_node = _xml_doc->createElement("svg:pattern");
+ // Set pattern transform matrix
+ const double *p2u = tiling_pattern->getMatrix();
+ double m[6] = {1, 0, 0, 1, 0, 0};
+ double det;
+ det = _ttm[0] * _ttm[3] - _ttm[1] * _ttm[2]; // see LP Bug 1168908
+ if (det) {
+ double ittm[6]; // invert ttm
+ ittm[0] = _ttm[3] / det;
+ ittm[1] = -_ttm[1] / det;
+ ittm[2] = -_ttm[2] / det;
+ ittm[3] = _ttm[0] / det;
+ ittm[4] = (_ttm[2] * _ttm[5] - _ttm[3] * _ttm[4]) / det;
+ ittm[5] = (_ttm[1] * _ttm[4] - _ttm[0] * _ttm[5]) / det;
+ m[0] = p2u[0] * ittm[0] + p2u[1] * ittm[2];
+ m[1] = p2u[0] * ittm[1] + p2u[1] * ittm[3];
+ m[2] = p2u[2] * ittm[0] + p2u[3] * ittm[2];
+ m[3] = p2u[2] * ittm[1] + p2u[3] * ittm[3];
+ m[4] = p2u[4] * ittm[0] + p2u[5] * ittm[2] + ittm[4];
+ m[5] = p2u[4] * ittm[1] + p2u[5] * ittm[3] + ittm[5];
+ }
+ Geom::Affine pat_matrix(m[0], m[1], m[2], m[3], m[4], m[5]);
+ pattern_node->setAttributeOrRemoveIfEmpty("patternTransform", sp_svg_transform_write(pat_matrix));
+ pattern_node->setAttribute("patternUnits", "userSpaceOnUse");
+ // Set pattern tiling
+ // FIXME: don't ignore XStep and YStep
+ const double *bbox = tiling_pattern->getBBox();
+ pattern_node->setAttributeSvgDouble("x", 0.0);
+ pattern_node->setAttributeSvgDouble("y", 0.0);
+ pattern_node->setAttributeSvgDouble("width", bbox[2] - bbox[0]);
+ pattern_node->setAttributeSvgDouble("height", bbox[3] - bbox[1]);
+
+ // Convert BBox for PdfParser
+ PDFRectangle box;
+ box.x1 = bbox[0];
+ box.y1 = bbox[1];
+ box.x2 = bbox[2];
+ box.y2 = bbox[3];
+ // Create new SvgBuilder and sub-page PdfParser
+ SvgBuilder *pattern_builder = new SvgBuilder(this, pattern_node);
+ PdfParser *pdf_parser = new PdfParser(_xref, pattern_builder, tiling_pattern->getResDict(),
+ &box);
+ // Get pattern color space
+ GfxPatternColorSpace *pat_cs = (GfxPatternColorSpace *)( is_stroke ? state->getStrokeColorSpace()
+ : state->getFillColorSpace() );
+ // Set fill/stroke colors if this is an uncolored tiling pattern
+ GfxColorSpace *cs = nullptr;
+ if ( tiling_pattern->getPaintType() == 2 && ( cs = pat_cs->getUnder() ) ) {
+ GfxState *pattern_state = pdf_parser->getState();
+ pattern_state->setFillColorSpace(cs->copy());
+ pattern_state->setFillColor(state->getFillColor());
+ pattern_state->setStrokeColorSpace(cs->copy());
+ pattern_state->setStrokeColor(state->getFillColor());
+ }
+
+ // Generate the SVG pattern
+ pdf_parser->parse(tiling_pattern->getContentStream());
+
+ // Cleanup
+ delete pdf_parser;
+ delete pattern_builder;
+
+ // Append the pattern to defs
+ _doc->getDefs()->getRepr()->appendChild(pattern_node);
+ gchar *id = g_strdup(pattern_node->attribute("id"));
+ Inkscape::GC::release(pattern_node);
+
+ return id;
+}
+
+/**
+ * \brief Creates a linear or radial gradient from poppler's data structure
+ * \param shading poppler's data structure for the shading
+ * \param matrix gradient transformation, can be null
+ * \param for_shading true if we're creating this for a shading operator; false otherwise
+ * \return id of the created object
+ */
+gchar *SvgBuilder::_createGradient(GfxShading *shading, double *matrix, bool for_shading) {
+ Inkscape::XML::Node *gradient;
+ _POPPLER_CONST Function *func;
+ int num_funcs;
+ bool extend0, extend1;
+
+ if ( shading->getType() == 2 ) { // Axial shading
+ gradient = _xml_doc->createElement("svg:linearGradient");
+ GfxAxialShading *axial_shading = static_cast<GfxAxialShading*>(shading);
+ double x1, y1, x2, y2;
+ axial_shading->getCoords(&x1, &y1, &x2, &y2);
+ gradient->setAttributeSvgDouble("x1", x1);
+ gradient->setAttributeSvgDouble("y1", y1);
+ gradient->setAttributeSvgDouble("x2", x2);
+ gradient->setAttributeSvgDouble("y2", y2);
+ extend0 = axial_shading->getExtend0();
+ extend1 = axial_shading->getExtend1();
+ num_funcs = axial_shading->getNFuncs();
+ func = axial_shading->getFunc(0);
+ } else if (shading->getType() == 3) { // Radial shading
+ gradient = _xml_doc->createElement("svg:radialGradient");
+ GfxRadialShading *radial_shading = static_cast<GfxRadialShading*>(shading);
+ double x1, y1, r1, x2, y2, r2;
+ radial_shading->getCoords(&x1, &y1, &r1, &x2, &y2, &r2);
+ // FIXME: the inner circle's radius is ignored here
+ gradient->setAttributeSvgDouble("fx", x1);
+ gradient->setAttributeSvgDouble("fy", y1);
+ gradient->setAttributeSvgDouble("cx", x2);
+ gradient->setAttributeSvgDouble("cy", y2);
+ gradient->setAttributeSvgDouble("r", r2);
+ extend0 = radial_shading->getExtend0();
+ extend1 = radial_shading->getExtend1();
+ num_funcs = radial_shading->getNFuncs();
+ func = radial_shading->getFunc(0);
+ } else { // Unsupported shading type
+ return nullptr;
+ }
+ gradient->setAttribute("gradientUnits", "userSpaceOnUse");
+ // If needed, flip the gradient transform around the y axis
+ if (matrix) {
+ Geom::Affine pat_matrix(matrix[0], matrix[1], matrix[2], matrix[3],
+ matrix[4], matrix[5]);
+ if ( !for_shading && _is_top_level ) {
+ Geom::Affine flip(1.0, 0.0, 0.0, -1.0, 0.0, Inkscape::Util::Quantity::convert(_height, "px", "pt"));
+ pat_matrix *= flip;
+ }
+ gradient->setAttributeOrRemoveIfEmpty("gradientTransform", sp_svg_transform_write(pat_matrix));
+ }
+
+ if ( extend0 && extend1 ) {
+ gradient->setAttribute("spreadMethod", "pad");
+ }
+
+ if ( num_funcs > 1 || !_addGradientStops(gradient, shading, func) ) {
+ Inkscape::GC::release(gradient);
+ return nullptr;
+ }
+
+ Inkscape::XML::Node *defs = _doc->getDefs()->getRepr();
+ defs->appendChild(gradient);
+ gchar *id = g_strdup(gradient->attribute("id"));
+ Inkscape::GC::release(gradient);
+
+ return id;
+}
+
+#define EPSILON 0.0001
+/**
+ * \brief Adds a stop with the given properties to the gradient's representation
+ */
+void SvgBuilder::_addStopToGradient(Inkscape::XML::Node *gradient, double offset,
+ GfxRGB *color, double opacity) {
+ Inkscape::XML::Node *stop = _xml_doc->createElement("svg:stop");
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ Inkscape::CSSOStringStream os_opacity;
+ gchar *color_text = nullptr;
+ if ( _transp_group_stack != nullptr && _transp_group_stack->for_softmask ) {
+ double gray = (double)color->r / 65535.0;
+ gray = CLAMP(gray, 0.0, 1.0);
+ os_opacity << gray;
+ color_text = (char*) "#ffffff";
+ } else {
+ os_opacity << opacity;
+ color_text = svgConvertGfxRGB(color);
+ }
+ sp_repr_css_set_property(css, "stop-opacity", os_opacity.str().c_str());
+ sp_repr_css_set_property(css, "stop-color", color_text);
+
+ sp_repr_css_change(stop, css, "style");
+ sp_repr_css_attr_unref(css);
+ stop->setAttributeCssDouble("offset", offset);
+
+ gradient->appendChild(stop);
+ Inkscape::GC::release(stop);
+}
+
+static bool svgGetShadingColorRGB(GfxShading *shading, double offset, GfxRGB *result) {
+ GfxColorSpace *color_space = shading->getColorSpace();
+ GfxColor temp;
+ if ( shading->getType() == 2 ) { // Axial shading
+ (static_cast<GfxAxialShading*>(shading))->getColor(offset, &temp);
+ } else if ( shading->getType() == 3 ) { // Radial shading
+ (static_cast<GfxRadialShading*>(shading))->getColor(offset, &temp);
+ } else {
+ return false;
+ }
+ // Convert it to RGB
+ color_space->getRGB(&temp, result);
+
+ return true;
+}
+
+#define INT_EPSILON 8
+bool SvgBuilder::_addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading,
+ _POPPLER_CONST Function *func) {
+ int type = func->getType();
+ if ( type == 0 || type == 2 ) { // Sampled or exponential function
+ GfxRGB stop1, stop2;
+ if ( !svgGetShadingColorRGB(shading, 0.0, &stop1) ||
+ !svgGetShadingColorRGB(shading, 1.0, &stop2) ) {
+ return false;
+ } else {
+ _addStopToGradient(gradient, 0.0, &stop1, 1.0);
+ _addStopToGradient(gradient, 1.0, &stop2, 1.0);
+ }
+ } else if ( type == 3 ) { // Stitching
+ auto stitchingFunc = static_cast<_POPPLER_CONST StitchingFunction*>(func);
+ const double *bounds = stitchingFunc->getBounds();
+ const double *encode = stitchingFunc->getEncode();
+ int num_funcs = stitchingFunc->getNumFuncs();
+
+ // Add stops from all the stitched functions
+ GfxRGB prev_color, color;
+ svgGetShadingColorRGB(shading, bounds[0], &prev_color);
+ _addStopToGradient(gradient, bounds[0], &prev_color, 1.0);
+ for ( int i = 0 ; i < num_funcs ; i++ ) {
+ svgGetShadingColorRGB(shading, bounds[i + 1], &color);
+ // Add stops
+ if (stitchingFunc->getFunc(i)->getType() == 2) { // process exponential fxn
+ double expE = (static_cast<_POPPLER_CONST ExponentialFunction*>(stitchingFunc->getFunc(i)))->getE();
+ if (expE > 1.0) {
+ expE = (bounds[i + 1] - bounds[i])/expE; // approximate exponential as a single straight line at x=1
+ if (encode[2*i] == 0) { // normal sequence
+ _addStopToGradient(gradient, bounds[i + 1] - expE, &prev_color, 1.0);
+ } else { // reflected sequence
+ _addStopToGradient(gradient, bounds[i] + expE, &color, 1.0);
+ }
+ }
+ }
+ _addStopToGradient(gradient, bounds[i + 1], &color, 1.0);
+ prev_color = color;
+ }
+ } else { // Unsupported function type
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * \brief Sets _invalidated_style to true to indicate that styles have to be updated
+ * Used for text output when glyphs are buffered till a font change
+ */
+void SvgBuilder::updateStyle(GfxState *state) {
+ if (_in_text_object) {
+ _invalidated_style = true;
+ _current_state = state;
+ }
+}
+
+/*
+ MatchingChars
+ Count for how many characters s1 matches sp taking into account
+ that a space in sp may be removed or replaced by some other tokens
+ specified in the code. (Bug LP #179589)
+*/
+static size_t MatchingChars(std::string s1, std::string sp)
+{
+ size_t is = 0;
+ size_t ip = 0;
+
+ while(is < s1.length() && ip < sp.length()) {
+ if (s1[is] == sp[ip]) {
+ is++; ip++;
+ } else if (sp[ip] == ' ') {
+ ip++;
+ if (s1[is] == '_') { // Valid matches to spaces in sp.
+ is++;
+ }
+ } else {
+ break;
+ }
+ }
+ return ip;
+}
+
+/*
+ SvgBuilder::_BestMatchingFont
+ Scan the available fonts to find the font name that best matches PDFname.
+ (Bug LP #179589)
+*/
+std::string SvgBuilder::_BestMatchingFont(std::string PDFname)
+{
+ double bestMatch = 0;
+ std::string bestFontname = "Arial";
+
+ for (auto fontname : _availableFontNames) {
+ // At least the first word of the font name should match.
+ size_t minMatch = fontname.find(" ");
+ if (minMatch == std::string::npos) {
+ minMatch = fontname.length();
+ }
+
+ size_t Match = MatchingChars(PDFname, fontname);
+ if (Match >= minMatch) {
+ double relMatch = (float)Match / (fontname.length() + PDFname.length());
+ if (relMatch > bestMatch) {
+ bestMatch = relMatch;
+ bestFontname = fontname;
+ }
+ }
+ }
+
+ if (bestMatch == 0)
+ return PDFname;
+ else
+ return bestFontname;
+}
+
+/**
+ * This array holds info about translating font weight names to more or less CSS equivalents
+ */
+static char *font_weight_translator[][2] = {
+ // clang-format off
+ {(char*) "bold", (char*) "bold"},
+ {(char*) "light", (char*) "300"},
+ {(char*) "black", (char*) "900"},
+ {(char*) "heavy", (char*) "900"},
+ {(char*) "ultrabold", (char*) "800"},
+ {(char*) "extrabold", (char*) "800"},
+ {(char*) "demibold", (char*) "600"},
+ {(char*) "semibold", (char*) "600"},
+ {(char*) "medium", (char*) "500"},
+ {(char*) "book", (char*) "normal"},
+ {(char*) "regular", (char*) "normal"},
+ {(char*) "roman", (char*) "normal"},
+ {(char*) "normal", (char*) "normal"},
+ {(char*) "ultralight", (char*) "200"},
+ {(char*) "extralight", (char*) "200"},
+ {(char*) "thin", (char*) "100"}
+ // clang-format on
+};
+
+/**
+ * \brief Updates _font_style according to the font set in parameter state
+ */
+void SvgBuilder::updateFont(GfxState *state) {
+
+ TRACE(("updateFont()\n"));
+ _need_font_update = false;
+ updateTextMatrix(state); // Ensure that we have a text matrix built
+
+ _font_style = sp_repr_css_attr_new();
+ auto font = state->getFont();
+ // Store original name
+ if (font->getName()) {
+ _font_specification = font->getName()->getCString();
+ } else {
+ _font_specification = "Arial";
+ }
+
+ // Prune the font name to get the correct font family name
+ // In a PDF font names can look like this: IONIPB+MetaPlusBold-Italic
+ char *font_family = nullptr;
+ char *font_style = nullptr;
+ char *font_style_lowercase = nullptr;
+ const char *plus_sign = strstr(_font_specification, "+");
+ if (plus_sign) {
+ font_family = g_strdup(plus_sign + 1);
+ _font_specification = plus_sign + 1;
+ } else {
+ font_family = g_strdup(_font_specification);
+ }
+ char *style_delim = nullptr;
+ if ( ( style_delim = g_strrstr(font_family, "-") ) ||
+ ( style_delim = g_strrstr(font_family, ",") ) ) {
+ font_style = style_delim + 1;
+ font_style_lowercase = g_ascii_strdown(font_style, -1);
+ style_delim[0] = 0;
+ }
+
+ // Font family
+ if (font->getFamily()) { // if font family is explicitly given use it.
+ sp_repr_css_set_property(_font_style, "font-family", font->getFamily()->getCString());
+ } else {
+ int attr_value = _preferences->getAttributeInt("localFonts", 1);
+ if (attr_value != 0) {
+ // Find the font that best matches the stripped down (orig)name (Bug LP #179589).
+ sp_repr_css_set_property(_font_style, "font-family", _BestMatchingFont(font_family).c_str());
+ } else {
+ sp_repr_css_set_property(_font_style, "font-family", font_family);
+ }
+ }
+
+ // Font style
+ if (font->isItalic()) {
+ sp_repr_css_set_property(_font_style, "font-style", "italic");
+ } else if (font_style) {
+ if ( strstr(font_style_lowercase, "italic") ||
+ strstr(font_style_lowercase, "slanted") ) {
+ sp_repr_css_set_property(_font_style, "font-style", "italic");
+ } else if (strstr(font_style_lowercase, "oblique")) {
+ sp_repr_css_set_property(_font_style, "font-style", "oblique");
+ }
+ }
+
+ // Font variant -- default 'normal' value
+ sp_repr_css_set_property(_font_style, "font-variant", "normal");
+
+ // Font weight
+ GfxFont::Weight font_weight = font->getWeight();
+ char *css_font_weight = nullptr;
+ if ( font_weight != GfxFont::WeightNotDefined ) {
+ if ( font_weight == GfxFont::W400 ) {
+ css_font_weight = (char*) "normal";
+ } else if ( font_weight == GfxFont::W700 ) {
+ css_font_weight = (char*) "bold";
+ } else {
+ gchar weight_num[4] = "100";
+ weight_num[0] = (gchar)( '1' + (font_weight - GfxFont::W100) );
+ sp_repr_css_set_property(_font_style, "font-weight", (gchar *)&weight_num);
+ }
+ } else if (font_style) {
+ // Apply the font weight translations
+ int num_translations = sizeof(font_weight_translator) / ( 2 * sizeof(char *) );
+ for ( int i = 0 ; i < num_translations ; i++ ) {
+ if (strstr(font_style_lowercase, font_weight_translator[i][0])) {
+ css_font_weight = font_weight_translator[i][1];
+ }
+ }
+ } else {
+ css_font_weight = (char*) "normal";
+ }
+ if (css_font_weight) {
+ sp_repr_css_set_property(_font_style, "font-weight", css_font_weight);
+ }
+ g_free(font_family);
+ if (font_style_lowercase) {
+ g_free(font_style_lowercase);
+ }
+
+ // Font stretch
+ GfxFont::Stretch font_stretch = font->getStretch();
+ gchar *stretch_value = nullptr;
+ switch (font_stretch) {
+ case GfxFont::UltraCondensed:
+ stretch_value = (char*) "ultra-condensed";
+ break;
+ case GfxFont::ExtraCondensed:
+ stretch_value = (char*) "extra-condensed";
+ break;
+ case GfxFont::Condensed:
+ stretch_value = (char*) "condensed";
+ break;
+ case GfxFont::SemiCondensed:
+ stretch_value = (char*) "semi-condensed";
+ break;
+ case GfxFont::Normal:
+ stretch_value = (char*) "normal";
+ break;
+ case GfxFont::SemiExpanded:
+ stretch_value = (char*) "semi-expanded";
+ break;
+ case GfxFont::Expanded:
+ stretch_value = (char*) "expanded";
+ break;
+ case GfxFont::ExtraExpanded:
+ stretch_value = (char*) "extra-expanded";
+ break;
+ case GfxFont::UltraExpanded:
+ stretch_value = (char*) "ultra-expanded";
+ break;
+ default:
+ break;
+ }
+ if ( stretch_value != nullptr ) {
+ sp_repr_css_set_property(_font_style, "font-stretch", stretch_value);
+ }
+
+ // Font size
+ Inkscape::CSSOStringStream os_font_size;
+ double css_font_size = _font_scaling * state->getFontSize();
+ if ( font->getType() == fontType3 ) {
+ const double *font_matrix = font->getFontMatrix();
+ if ( font_matrix[0] != 0.0 ) {
+ css_font_size *= font_matrix[3] / font_matrix[0];
+ }
+ }
+ os_font_size << css_font_size;
+ sp_repr_css_set_property(_font_style, "font-size", os_font_size.str().c_str());
+
+ // Writing mode
+ if ( font->getWMode() == 0 ) {
+ sp_repr_css_set_property(_font_style, "writing-mode", "lr");
+ } else {
+ sp_repr_css_set_property(_font_style, "writing-mode", "tb");
+ }
+
+ _invalidated_style = true;
+}
+
+/**
+ * \brief Shifts the current text position by the given amount (specified in text space)
+ */
+void SvgBuilder::updateTextShift(GfxState *state, double shift) {
+ double shift_value = -shift * 0.001 * fabs(state->getFontSize());
+ if (state->getFont()->getWMode()) {
+ _text_position[1] += shift_value;
+ } else {
+ _text_position[0] += shift_value;
+ }
+}
+
+/**
+ * \brief Updates current text position
+ */
+void SvgBuilder::updateTextPosition(double tx, double ty) {
+ Geom::Point new_position(tx, ty);
+ _text_position = new_position;
+}
+
+/**
+ * \brief Flushes the buffered characters
+ */
+void SvgBuilder::updateTextMatrix(GfxState *state) {
+ _flushText();
+ // Update text matrix
+ const double *text_matrix = state->getTextMat();
+ double w_scale = sqrt( text_matrix[0] * text_matrix[0] + text_matrix[2] * text_matrix[2] );
+ double h_scale = sqrt( text_matrix[1] * text_matrix[1] + text_matrix[3] * text_matrix[3] );
+ double max_scale;
+ if ( w_scale > h_scale ) {
+ max_scale = w_scale;
+ } else {
+ max_scale = h_scale;
+ }
+ // Calculate new text matrix
+ Geom::Affine new_text_matrix(text_matrix[0] * state->getHorizScaling(),
+ text_matrix[1] * state->getHorizScaling(),
+ -text_matrix[2], -text_matrix[3],
+ 0.0, 0.0);
+
+ if ( fabs( max_scale - 1.0 ) > EPSILON ) {
+ // Cancel out scaling by font size in text matrix
+ for ( int i = 0 ; i < 4 ; i++ ) {
+ new_text_matrix[i] /= max_scale;
+ }
+ }
+ _text_matrix = new_text_matrix;
+ _font_scaling = max_scale;
+}
+
+/**
+ * \brief Writes the buffered characters to the SVG document
+ */
+void SvgBuilder::_flushText() {
+ // Ignore empty strings
+ if ( _glyphs.empty()) {
+ _glyphs.clear();
+ return;
+ }
+ std::vector<SvgGlyph>::iterator i = _glyphs.begin();
+ const SvgGlyph& first_glyph = (*i);
+ int render_mode = first_glyph.render_mode;
+ // Ignore invisible characters
+ if ( render_mode == 3 ) {
+ _glyphs.clear();
+ return;
+ }
+
+ Inkscape::XML::Node *text_node = _xml_doc->createElement("svg:text");
+
+ // we preserve spaces in the text objects we create, this applies to any descendant
+ text_node->setAttribute("xml:space", "preserve");
+
+ // Set text matrix
+ Geom::Affine text_transform(_text_matrix);
+ text_transform[4] = first_glyph.position[0];
+ text_transform[5] = first_glyph.position[1];
+ text_node->setAttributeOrRemoveIfEmpty("transform", sp_svg_transform_write(text_transform));
+
+ bool new_tspan = true;
+ bool same_coords[2] = {true, true};
+ Geom::Point last_delta_pos;
+ unsigned int glyphs_in_a_row = 0;
+ Inkscape::XML::Node *tspan_node = nullptr;
+ Glib::ustring x_coords;
+ Glib::ustring y_coords;
+ Glib::ustring text_buffer;
+
+ // Output all buffered glyphs
+ while (true) {
+ const SvgGlyph& glyph = (*i);
+ auto prev_iterator = (i == _glyphs.begin()) ? _glyphs.end() : (i-1);
+ // Check if we need to make a new tspan
+ if (glyph.style_changed) {
+ new_tspan = true;
+ } else if ( i != _glyphs.begin() ) {
+ const SvgGlyph& prev_glyph = (*prev_iterator);
+ if ( !( ( glyph.dy == 0.0 && prev_glyph.dy == 0.0 &&
+ glyph.text_position[1] == prev_glyph.text_position[1] ) ||
+ ( glyph.dx == 0.0 && prev_glyph.dx == 0.0 &&
+ glyph.text_position[0] == prev_glyph.text_position[0] ) ) ) {
+ new_tspan = true;
+ }
+ }
+
+ // Create tspan node if needed
+ if ( new_tspan || i == _glyphs.end() ) {
+ if (tspan_node) {
+ // Set the x and y coordinate arrays
+ if ( same_coords[0] ) {
+ tspan_node->setAttributeSvgDouble("x", last_delta_pos[0]);
+ } else {
+ tspan_node->setAttributeOrRemoveIfEmpty("x", x_coords);
+ }
+ if ( same_coords[1] ) {
+ tspan_node->setAttributeSvgDouble("y", last_delta_pos[1]);
+ } else {
+ tspan_node->setAttributeOrRemoveIfEmpty("y", y_coords);
+ }
+ TRACE(("tspan content: %s\n", text_buffer.c_str()));
+ if ( glyphs_in_a_row > 1 ) {
+ tspan_node->setAttribute("sodipodi:role", "line");
+ }
+ // Add text content node to tspan
+ Inkscape::XML::Node *text_content = _xml_doc->createTextNode(text_buffer.c_str());
+ tspan_node->appendChild(text_content);
+ Inkscape::GC::release(text_content);
+ text_node->appendChild(tspan_node);
+ // Clear temporary buffers
+ x_coords.clear();
+ y_coords.clear();
+ text_buffer.clear();
+ Inkscape::GC::release(tspan_node);
+ glyphs_in_a_row = 0;
+ }
+ if ( i == _glyphs.end() ) {
+ sp_repr_css_attr_unref((*prev_iterator).style);
+ break;
+ } else {
+ tspan_node = _xml_doc->createElement("svg:tspan");
+
+ ///////
+ // Create a font specification string and save the attribute in the style
+ PangoFontDescription *descr = pango_font_description_from_string(glyph.font_specification);
+ Glib::ustring properFontSpec = font_factory::Default()->ConstructFontSpecification(descr);
+ pango_font_description_free(descr);
+ sp_repr_css_set_property(glyph.style, "-inkscape-font-specification", properFontSpec.c_str());
+
+ // Set style and unref SPCSSAttr if it won't be needed anymore
+ // assume all <tspan> nodes in a <text> node share the same style
+ sp_repr_css_change(text_node, glyph.style, "style");
+ if ( glyph.style_changed && i != _glyphs.begin() ) { // Free previous style
+ sp_repr_css_attr_unref((*prev_iterator).style);
+ }
+ }
+ new_tspan = false;
+ }
+ if ( glyphs_in_a_row > 0 && i != _glyphs.begin() ) {
+ x_coords.append(" ");
+ y_coords.append(" ");
+ // Check if we have the same coordinates
+ const SvgGlyph& prev_glyph = (*prev_iterator);
+ for ( int p = 0 ; p < 2 ; p++ ) {
+ if ( glyph.text_position[p] != prev_glyph.text_position[p] ) {
+ same_coords[p] = false;
+ }
+ }
+ }
+ // Append the coordinates to their respective strings
+ Geom::Point delta_pos( glyph.text_position - first_glyph.text_position );
+ delta_pos[1] += glyph.rise;
+ delta_pos[1] *= -1.0; // flip it
+ delta_pos *= _font_scaling;
+ Inkscape::CSSOStringStream os_x;
+ os_x << delta_pos[0];
+ x_coords.append(os_x.str());
+ Inkscape::CSSOStringStream os_y;
+ os_y << delta_pos[1];
+ y_coords.append(os_y.str());
+ last_delta_pos = delta_pos;
+
+ // Append the character to the text buffer
+ if ( !glyph.code.empty() ) {
+ text_buffer.append(1, glyph.code[0]);
+ }
+
+ glyphs_in_a_row++;
+ ++i;
+ }
+ _container->appendChild(text_node);
+ Inkscape::GC::release(text_node);
+
+ _glyphs.clear();
+}
+
+void SvgBuilder::beginString(GfxState *state) {
+ if (_need_font_update) {
+ updateFont(state);
+ }
+ IFTRACE(double *m = state->getTextMat());
+ TRACE(("tm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
+ IFTRACE(m = state->getCTM());
+ TRACE(("ctm: %f %f %f %f %f %f\n",m[0], m[1],m[2], m[3], m[4], m[5]));
+}
+
+/**
+ * \brief Adds the specified character to the text buffer
+ * Takes care of converting it to UTF-8 and generates a new style repr if style
+ * has changed since the last call.
+ */
+void SvgBuilder::addChar(GfxState *state, double x, double y,
+ double dx, double dy,
+ double originX, double originY,
+ CharCode /*code*/, int /*nBytes*/, Unicode const *u, int uLen) {
+
+ // Skip control characters, found in LaTeX generated PDFs
+ // https://gitlab.com/inkscape/inkscape/-/issues/1369
+ if (uLen > 0 && u[0] < 0x80 && g_ascii_iscntrl(u[0]) && !g_ascii_isspace(u[0])) {
+ g_warning("Skipping ASCII control character %u", u[0]);
+ _text_position += Geom::Point(dx, dy);
+ return;
+ }
+
+ bool is_space = ( uLen == 1 && u[0] == 32 );
+ // Skip beginning space
+ if ( is_space && _glyphs.empty()) {
+ Geom::Point delta(dx, dy);
+ _text_position += delta;
+ return;
+ }
+ // Allow only one space in a row
+ if ( is_space && (_glyphs[_glyphs.size() - 1].code.size() == 1) &&
+ (_glyphs[_glyphs.size() - 1].code[0] == 32) ) {
+ Geom::Point delta(dx, dy);
+ _text_position += delta;
+ return;
+ }
+
+ SvgGlyph new_glyph;
+ new_glyph.is_space = is_space;
+ new_glyph.position = Geom::Point( x - originX, y - originY );
+ new_glyph.text_position = _text_position;
+ new_glyph.dx = dx;
+ new_glyph.dy = dy;
+ Geom::Point delta(dx, dy);
+ _text_position += delta;
+
+ // Convert the character to UTF-8 since that's our SVG document's encoding
+ {
+ gunichar2 uu[8] = {0};
+
+ for (int i = 0; i < uLen; i++) {
+ uu[i] = u[i];
+ }
+
+ gchar *tmp = g_utf16_to_utf8(uu, uLen, nullptr, nullptr, nullptr);
+ if ( tmp && *tmp ) {
+ new_glyph.code = tmp;
+ } else {
+ new_glyph.code.clear();
+ }
+ g_free(tmp);
+ }
+
+ // Copy current style if it has changed since the previous glyph
+ if (_invalidated_style || _glyphs.empty()) {
+ new_glyph.style_changed = true;
+ int render_mode = state->getRender();
+ // Set style
+ bool has_fill = !( render_mode & 1 );
+ bool has_stroke = ( render_mode & 3 ) == 1 || ( render_mode & 3 ) == 2;
+ new_glyph.style = _setStyle(state, has_fill, has_stroke);
+ // Find a way to handle blend modes on text
+ /* GfxBlendMode blendmode = state->getBlendMode();
+ if (blendmode) {
+ sp_repr_css_set_property(new_glyph.style, "mix-blend-mode", enum_blend_mode[blendmode].key);
+ } */
+ new_glyph.render_mode = render_mode;
+ sp_repr_css_merge(new_glyph.style, _font_style); // Merge with font style
+ _invalidated_style = false;
+ } else {
+ new_glyph.style_changed = false;
+ // Point to previous glyph's style information
+ const SvgGlyph& prev_glyph = _glyphs.back();
+ new_glyph.style = prev_glyph.style;
+ /* GfxBlendMode blendmode = state->getBlendMode();
+ if (blendmode) {
+ sp_repr_css_set_property(new_glyph.style, "mix-blend-mode", enum_blend_mode[blendmode].key);
+ } */
+ new_glyph.render_mode = prev_glyph.render_mode;
+ }
+ new_glyph.font_specification = _font_specification;
+ new_glyph.rise = state->getRise();
+
+ _glyphs.push_back(new_glyph);
+}
+
+void SvgBuilder::endString(GfxState * /*state*/) {
+}
+
+void SvgBuilder::beginTextObject(GfxState *state) {
+ _in_text_object = true;
+ _invalidated_style = true; // Force copying of current state
+ _current_state = state;
+}
+
+void SvgBuilder::endTextObject(GfxState * /*state*/) {
+ _flushText();
+ // TODO: clip if render_mode >= 4
+ _in_text_object = false;
+}
+
+/**
+ * Helper functions for supporting direct PNG output into a base64 encoded stream
+ */
+void png_write_vector(png_structp png_ptr, png_bytep data, png_size_t length)
+{
+ auto *v_ptr = reinterpret_cast<std::vector<guchar> *>(png_get_io_ptr(png_ptr)); // Get pointer to stream
+ for ( unsigned i = 0 ; i < length ; i++ ) {
+ v_ptr->push_back(data[i]);
+ }
+}
+
+/**
+ * \brief Creates an <image> element containing the given ImageStream as a PNG
+ *
+ */
+Inkscape::XML::Node *SvgBuilder::_createImage(Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ int *mask_colors, bool alpha_only,
+ bool invert_alpha) {
+
+ // Create PNG write struct
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ if ( png_ptr == nullptr ) {
+ return nullptr;
+ }
+ // Create PNG info struct
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+ if ( info_ptr == nullptr ) {
+ png_destroy_write_struct(&png_ptr, nullptr);
+ return nullptr;
+ }
+ // Set error handler
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ return nullptr;
+ }
+ // Decide whether we should embed this image
+ int attr_value = _preferences->getAttributeInt("embedImages", 1);
+ bool embed_image = ( attr_value != 0 );
+ // Set read/write functions
+ std::vector<guchar> png_buffer;
+ FILE *fp = nullptr;
+ gchar *file_name = nullptr;
+ if (embed_image) {
+ png_set_write_fn(png_ptr, &png_buffer, png_write_vector, nullptr);
+ } else {
+ static int counter = 0;
+ file_name = g_strdup_printf("%s_img%d.png", _docname, counter++);
+ fp = fopen(file_name, "wb");
+ if ( fp == nullptr ) {
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ g_free(file_name);
+ return nullptr;
+ }
+ png_init_io(png_ptr, fp);
+ }
+
+ // Set header data
+ if ( !invert_alpha && !alpha_only ) {
+ png_set_invert_alpha(png_ptr);
+ }
+ png_color_8 sig_bit;
+ if (alpha_only) {
+ png_set_IHDR(png_ptr, info_ptr,
+ width,
+ height,
+ 8, /* bit_depth */
+ PNG_COLOR_TYPE_GRAY,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+ sig_bit.red = 0;
+ sig_bit.green = 0;
+ sig_bit.blue = 0;
+ sig_bit.gray = 8;
+ sig_bit.alpha = 0;
+ } else {
+ png_set_IHDR(png_ptr, info_ptr,
+ width,
+ height,
+ 8, /* bit_depth */
+ PNG_COLOR_TYPE_RGB_ALPHA,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_BASE,
+ PNG_FILTER_TYPE_BASE);
+ sig_bit.red = 8;
+ sig_bit.green = 8;
+ sig_bit.blue = 8;
+ sig_bit.alpha = 8;
+ }
+ png_set_sBIT(png_ptr, info_ptr, &sig_bit);
+ png_set_bgr(png_ptr);
+ // Write the file header
+ png_write_info(png_ptr, info_ptr);
+
+ // Convert pixels
+ ImageStream *image_stream;
+ if (alpha_only) {
+ if (color_map) {
+ image_stream = new ImageStream(str, width, color_map->getNumPixelComps(),
+ color_map->getBits());
+ } else {
+ image_stream = new ImageStream(str, width, 1, 1);
+ }
+ image_stream->reset();
+
+ // Convert grayscale values
+ unsigned char *buffer = new unsigned char[width];
+ int invert_bit = invert_alpha ? 1 : 0;
+ for ( int y = 0 ; y < height ; y++ ) {
+ unsigned char *row = image_stream->getLine();
+ if (color_map) {
+ color_map->getGrayLine(row, buffer, width);
+ } else {
+ unsigned char *buf_ptr = buffer;
+ for ( int x = 0 ; x < width ; x++ ) {
+ if ( row[x] ^ invert_bit ) {
+ *buf_ptr++ = 0;
+ } else {
+ *buf_ptr++ = 255;
+ }
+ }
+ }
+ png_write_row(png_ptr, (png_bytep)buffer);
+ }
+ delete [] buffer;
+ } else if (color_map) {
+ image_stream = new ImageStream(str, width,
+ color_map->getNumPixelComps(),
+ color_map->getBits());
+ image_stream->reset();
+
+ // Convert RGB values
+ unsigned int *buffer = new unsigned int[width];
+ if (mask_colors) {
+ for ( int y = 0 ; y < height ; y++ ) {
+ unsigned char *row = image_stream->getLine();
+ color_map->getRGBLine(row, buffer, width);
+
+ unsigned int *dest = buffer;
+ for ( int x = 0 ; x < width ; x++ ) {
+ // Check each color component against the mask
+ for ( int i = 0; i < color_map->getNumPixelComps() ; i++) {
+ if ( row[i] < mask_colors[2*i] * 255 ||
+ row[i] > mask_colors[2*i + 1] * 255 ) {
+ *dest = *dest | 0xff000000;
+ break;
+ }
+ }
+ // Advance to the next pixel
+ row += color_map->getNumPixelComps();
+ dest++;
+ }
+ // Write it to the PNG
+ png_write_row(png_ptr, (png_bytep)buffer);
+ }
+ } else {
+ for ( int i = 0 ; i < height ; i++ ) {
+ unsigned char *row = image_stream->getLine();
+ memset((void*)buffer, 0xff, sizeof(int) * width);
+ color_map->getRGBLine(row, buffer, width);
+ png_write_row(png_ptr, (png_bytep)buffer);
+ }
+ }
+ delete [] buffer;
+
+ } else { // A colormap must be provided, so quit
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ if (!embed_image) {
+ fclose(fp);
+ g_free(file_name);
+ }
+ return nullptr;
+ }
+ delete image_stream;
+ str->close();
+ // Close PNG
+ png_write_end(png_ptr, info_ptr);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+
+ // Create repr
+ Inkscape::XML::Node *image_node = _xml_doc->createElement("svg:image");
+ image_node->setAttributeSvgDouble("width", 1);
+ image_node->setAttributeSvgDouble("height", 1);
+ if( !interpolate ) {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ // This should be changed after CSS4 Images widely supported.
+ sp_repr_css_set_property(css, "image-rendering", "optimizeSpeed");
+ sp_repr_css_change(image_node, css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+
+ // PS/PDF images are placed via a transformation matrix, no preserveAspectRatio used
+ image_node->setAttribute("preserveAspectRatio", "none");
+
+ // Set transformation
+ svgSetTransform(image_node, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
+
+ // Create href
+ if (embed_image) {
+ // Append format specification to the URI
+ auto *base64String = g_base64_encode(png_buffer.data(), png_buffer.size());
+ auto png_data = std::string("data:image/png;base64,") + base64String;
+ g_free(base64String);
+ image_node->setAttributeOrRemoveIfEmpty("xlink:href", png_data);
+ } else {
+ fclose(fp);
+ image_node->setAttribute("xlink:href", file_name);
+ g_free(file_name);
+ }
+
+ return image_node;
+}
+
+/**
+ * \brief Creates a <mask> with the specified width and height and adds to <defs>
+ * If we're not the top-level SvgBuilder, creates a <defs> too and adds the mask to it.
+ * \return the created XML node
+ */
+Inkscape::XML::Node *SvgBuilder::_createMask(double width, double height) {
+ Inkscape::XML::Node *mask_node = _xml_doc->createElement("svg:mask");
+ mask_node->setAttribute("maskUnits", "userSpaceOnUse");
+ mask_node->setAttributeSvgDouble("x", 0.0);
+ mask_node->setAttributeSvgDouble("y", 0.0);
+ mask_node->setAttributeSvgDouble("width", width);
+ mask_node->setAttributeSvgDouble("height", height);
+ // Append mask to defs
+ if (_is_top_level) {
+ _doc->getDefs()->getRepr()->appendChild(mask_node);
+ Inkscape::GC::release(mask_node);
+ return _doc->getDefs()->getRepr()->lastChild();
+ } else { // Work around for renderer bug when mask isn't defined in pattern
+ static int mask_count = 0;
+ Inkscape::XML::Node *defs = _root->firstChild();
+ if ( !( defs && !strcmp(defs->name(), "svg:defs") ) ) {
+ // Create <defs> node
+ defs = _xml_doc->createElement("svg:defs");
+ _root->addChild(defs, nullptr);
+ Inkscape::GC::release(defs);
+ defs = _root->firstChild();
+ }
+ gchar *mask_id = g_strdup_printf("_mask%d", mask_count++);
+ mask_node->setAttribute("id", mask_id);
+ g_free(mask_id);
+ defs->appendChild(mask_node);
+ Inkscape::GC::release(mask_node);
+ return defs->lastChild();
+ }
+}
+
+void SvgBuilder::addImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
+ bool interpolate, int *mask_colors)
+{
+
+ Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, mask_colors);
+ if (image_node) {
+ _setBlendMode(image_node, state);
+ _container->appendChild(image_node);
+ Inkscape::GC::release(image_node);
+ }
+}
+
+void SvgBuilder::addImageMask(GfxState *state, Stream *str, int width, int height,
+ bool invert, bool interpolate) {
+
+ // Create a rectangle
+ Inkscape::XML::Node *rect = _xml_doc->createElement("svg:rect");
+ rect->setAttributeSvgDouble("x", 0.0);
+ rect->setAttributeSvgDouble("y", 0.0);
+ rect->setAttributeSvgDouble("width", 1.0);
+ rect->setAttributeSvgDouble("height", 1.0);
+ svgSetTransform(rect, Geom::Affine(1.0, 0.0, 0.0, -1.0, 0.0, 1.0));
+ // Get current fill style and set it on the rectangle
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ _setFillStyle(css, state, false);
+ sp_repr_css_change(rect, css, "style");
+ sp_repr_css_attr_unref(css);
+ _setBlendMode(rect, state);
+
+ // Scaling 1x1 surfaces might not work so skip setting a mask with this size
+ if ( width > 1 || height > 1 ) {
+ Inkscape::XML::Node *mask_image_node =
+ _createImage(str, width, height, nullptr, interpolate, nullptr, true, invert);
+ if (mask_image_node) {
+ // Create the mask
+ Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
+ // Remove unnecessary transformation from the mask image
+ mask_image_node->removeAttribute("transform");
+ mask_node->appendChild(mask_image_node);
+ Inkscape::GC::release(mask_image_node);
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
+ rect->setAttribute("mask", mask_url);
+ g_free(mask_url);
+ }
+ }
+
+ // Add the rectangle to the container
+ _container->appendChild(rect);
+ Inkscape::GC::release(rect);
+}
+
+void SvgBuilder::addMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
+ bool interpolate, Stream *mask_str, int mask_width, int mask_height, bool invert_mask,
+ bool mask_interpolate)
+{
+
+ Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height,
+ nullptr, mask_interpolate, nullptr, true, invert_mask);
+ Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr);
+ if ( mask_image_node && image_node ) {
+ // Create mask for the image
+ Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
+ // Remove unnecessary transformation from the mask image
+ mask_image_node->removeAttribute("transform");
+ mask_node->appendChild(mask_image_node);
+ // Scale the mask to the size of the image
+ Geom::Affine mask_transform((double)width, 0.0, 0.0, (double)height, 0.0, 0.0);
+ mask_node->setAttributeOrRemoveIfEmpty("maskTransform", sp_svg_transform_write(mask_transform));
+ // Set mask and add image
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
+ image_node->setAttribute("mask", mask_url);
+ g_free(mask_url);
+ _container->appendChild(image_node);
+ }
+ if (mask_image_node) {
+ Inkscape::GC::release(mask_image_node);
+ }
+ if (image_node) {
+ _setBlendMode(image_node, state);
+ Inkscape::GC::release(image_node);
+ }
+}
+
+void SvgBuilder::addSoftMaskedImage(GfxState *state, Stream *str, int width, int height, GfxImageColorMap *color_map,
+ bool interpolate, Stream *mask_str, int mask_width, int mask_height,
+ GfxImageColorMap *mask_color_map, bool mask_interpolate)
+{
+
+ Inkscape::XML::Node *mask_image_node = _createImage(mask_str, mask_width, mask_height,
+ mask_color_map, mask_interpolate, nullptr, true);
+ Inkscape::XML::Node *image_node = _createImage(str, width, height, color_map, interpolate, nullptr);
+ if ( mask_image_node && image_node ) {
+ // Create mask for the image
+ Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
+ // Remove unnecessary transformation from the mask image
+ mask_image_node->removeAttribute("transform");
+ mask_node->appendChild(mask_image_node);
+ // Set mask and add image
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
+ image_node->setAttribute("mask", mask_url);
+ g_free(mask_url);
+ _container->appendChild(image_node);
+ }
+ if (mask_image_node) {
+ Inkscape::GC::release(mask_image_node);
+ }
+ if (image_node) {
+ _setBlendMode(image_node, state);
+ Inkscape::GC::release(image_node);
+ }
+}
+
+/**
+ * \brief Starts building a new transparency group
+ */
+void SvgBuilder::pushTransparencyGroup(GfxState * /*state*/, double *bbox,
+ GfxColorSpace * /*blending_color_space*/,
+ bool isolated, bool knockout,
+ bool for_softmask) {
+
+ // Push node stack
+ pushNode("svg:g");
+
+ // Setup new transparency group
+ SvgTransparencyGroup *transpGroup = new SvgTransparencyGroup;
+ for (size_t i = 0; i < 4; i++) {
+ transpGroup->bbox[i] = bbox[i];
+ }
+ transpGroup->isolated = isolated;
+ transpGroup->knockout = knockout;
+ transpGroup->for_softmask = for_softmask;
+ transpGroup->container = _container;
+
+ // Push onto the stack
+ transpGroup->next = _transp_group_stack;
+ _transp_group_stack = transpGroup;
+}
+
+void SvgBuilder::popTransparencyGroup(GfxState * /*state*/) {
+ // Restore node stack
+ popNode();
+}
+
+/**
+ * \brief Places the current transparency group into the current container
+ */
+void SvgBuilder::paintTransparencyGroup(GfxState * /*state*/, double * /*bbox*/) {
+ SvgTransparencyGroup *transpGroup = _transp_group_stack;
+ _container->appendChild(transpGroup->container);
+ Inkscape::GC::release(transpGroup->container);
+ // Pop the stack
+ _transp_group_stack = transpGroup->next;
+ delete transpGroup;
+}
+
+/**
+ * \brief Creates a mask using the current transparency group as its content
+ */
+void SvgBuilder::setSoftMask(GfxState * /*state*/, double * /*bbox*/, bool /*alpha*/,
+ Function * /*transfer_func*/, GfxColor * /*backdrop_color*/) {
+
+ // Create mask
+ Inkscape::XML::Node *mask_node = _createMask(1.0, 1.0);
+ // Add the softmask content to it
+ SvgTransparencyGroup *transpGroup = _transp_group_stack;
+ mask_node->appendChild(transpGroup->container);
+ Inkscape::GC::release(transpGroup->container);
+ // Apply the mask
+ _state_stack.back().softmask = mask_node;
+ pushGroup();
+ gchar *mask_url = g_strdup_printf("url(#%s)", mask_node->attribute("id"));
+ _container->setAttribute("mask", mask_url);
+ g_free(mask_url);
+ // Pop the stack
+ _transp_group_stack = transpGroup->next;
+ delete transpGroup;
+}
+
+void SvgBuilder::clearSoftMask(GfxState * /*state*/) {
+ if (_state_stack.back().softmask) {
+ _state_stack.back().softmask = nullptr;
+ popGroup();
+ }
+}
+
+} } } /* namespace Inkscape, Extension, Internal */
+
+#endif /* HAVE_POPPLER */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/pdfinput/svg-builder.h b/src/extension/internal/pdfinput/svg-builder.h
new file mode 100644
index 0000000..e91febd
--- /dev/null
+++ b/src/extension/internal/pdfinput/svg-builder.h
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H
+#define SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H
+
+/*
+ * Authors:
+ * miklos erdelyi
+ *
+ * Copyright (C) 2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef HAVE_POPPLER
+#include "poppler-transition-api.h"
+
+class SPDocument;
+namespace Inkscape {
+ namespace XML {
+ struct Document;
+ class Node;
+ }
+}
+
+#include <2geom/point.h>
+#include <2geom/affine.h>
+#include <glibmm/ustring.h>
+
+#include "CharTypes.h"
+class Function;
+class GfxState;
+struct GfxColor;
+class GfxColorSpace;
+struct GfxRGB;
+class GfxPath;
+class GfxPattern;
+class GfxTilingPattern;
+class GfxShading;
+class GfxFont;
+class GfxImageColorMap;
+class Stream;
+class XRef;
+
+class SPCSSAttr;
+
+#include <vector>
+#include <glib.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+struct SvgTransparencyGroup;
+
+/**
+ * Holds information about the current softmask and group depth for use of libpoppler.
+ * Could be later used to store other graphics state parameters so that we could
+ * emit only the differences in style settings from the parent state.
+ */
+struct SvgGraphicsState {
+ Inkscape::XML::Node *softmask; // Points to current softmask node
+ int group_depth; // Depth of nesting groups at this level
+};
+
+/**
+ * Holds information about glyphs added by PdfParser which haven't been added
+ * to the document yet.
+ */
+struct SvgGlyph {
+ Geom::Point position; // Absolute glyph coords
+ Geom::Point text_position; // Absolute glyph coords in text space
+ double dx; // X advance value
+ double dy; // Y advance value
+ double rise; // Text rise parameter
+ Glib::ustring code; // UTF-8 coded character
+ bool is_space;
+
+ bool style_changed; // Set to true if style has to be reset
+ SPCSSAttr *style;
+ int render_mode; // Text render mode
+ const char *font_specification; // Pointer to current font specification
+};
+
+/**
+ * Builds the inner SVG representation using libpoppler from the calls of PdfParser.
+ */
+class SvgBuilder {
+public:
+ SvgBuilder(SPDocument *document, gchar *docname, XRef *xref);
+ SvgBuilder(SvgBuilder *parent, Inkscape::XML::Node *root);
+ virtual ~SvgBuilder();
+
+ // Property setting
+ void setDocumentSize(double width, double height); // Document size in px
+ void setAsLayer(char *layer_name=nullptr);
+ void setGroupOpacity(double opacity);
+ Inkscape::XML::Node *getPreferences() {
+ return _preferences;
+ }
+ void pushPage();
+
+ // Handling the node stack
+ Inkscape::XML::Node *pushGroup();
+ Inkscape::XML::Node *popGroup();
+ Inkscape::XML::Node *getContainer(); // Returns current group node
+
+ // Path adding
+ void addPath(GfxState *state, bool fill, bool stroke, bool even_odd=false);
+ void addShadedFill(GfxShading *shading, double *matrix, GfxPath *path, bool even_odd=false);
+
+ // Image handling
+ void addImage(GfxState *state, Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate, int *mask_colors);
+ void addImageMask(GfxState *state, Stream *str, int width, int height,
+ bool invert, bool interpolate);
+ void addMaskedImage(GfxState *state, Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ Stream *mask_str, int mask_width, int mask_height,
+ bool invert_mask, bool mask_interpolate);
+ void addSoftMaskedImage(GfxState *state, Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ Stream *mask_str, int mask_width, int mask_height,
+ GfxImageColorMap *mask_color_map, bool mask_interpolate);
+
+ // Transparency group and soft mask handling
+ void pushTransparencyGroup(GfxState *state, double *bbox,
+ GfxColorSpace *blending_color_space,
+ bool isolated, bool knockout,
+ bool for_softmask);
+ void popTransparencyGroup(GfxState *state);
+ void paintTransparencyGroup(GfxState *state, double *bbox);
+ void setSoftMask(GfxState *state, double *bbox, bool alpha,
+ Function *transfer_func, GfxColor *backdrop_color);
+ void clearSoftMask(GfxState *state);
+
+ // Text handling
+ void beginString(GfxState *state);
+ void endString(GfxState *state);
+ void addChar(GfxState *state, double x, double y,
+ double dx, double dy,
+ double originX, double originY,
+ CharCode code, int nBytes, Unicode const *u, int uLen);
+ void beginTextObject(GfxState *state);
+ void endTextObject(GfxState *state);
+
+ bool isPatternTypeSupported(GfxPattern *pattern);
+
+ // State manipulation
+ void saveState();
+ void restoreState();
+ void updateStyle(GfxState *state);
+ void updateFont(GfxState *state);
+ void updateTextPosition(double tx, double ty);
+ void updateTextShift(GfxState *state, double shift);
+ void updateTextMatrix(GfxState *state);
+
+ // Clipping
+ void clip(GfxState *state, bool even_odd=false);
+ void setClipPath(GfxState *state, bool even_odd=false);
+
+ // Transforming
+ void setTransform(double c0, double c1, double c2, double c3, double c4,
+ double c5);
+ void setTransform(double const *transform);
+ bool getTransform(double *transform);
+
+private:
+ void _init();
+
+ // Pattern creation
+ gchar *_createPattern(GfxPattern *pattern, GfxState *state, bool is_stroke=false);
+ gchar *_createGradient(GfxShading *shading, double *matrix, bool for_shading=false);
+ void _addStopToGradient(Inkscape::XML::Node *gradient, double offset,
+ GfxRGB *color, double opacity);
+ bool _addGradientStops(Inkscape::XML::Node *gradient, GfxShading *shading,
+ _POPPLER_CONST Function *func);
+ gchar *_createTilingPattern(GfxTilingPattern *tiling_pattern, GfxState *state,
+ bool is_stroke=false);
+ // Image/mask creation
+ Inkscape::XML::Node *_createImage(Stream *str, int width, int height,
+ GfxImageColorMap *color_map, bool interpolate,
+ int *mask_colors, bool alpha_only=false,
+ bool invert_alpha=false);
+ Inkscape::XML::Node *_createMask(double width, double height);
+ // Style setting
+ SPCSSAttr *_setStyle(GfxState *state, bool fill, bool stroke, bool even_odd=false);
+ void _setStrokeStyle(SPCSSAttr *css, GfxState *state);
+ void _setFillStyle(SPCSSAttr *css, GfxState *state, bool even_odd);
+ void _setBlendMode(Inkscape::XML::Node *node, GfxState *state);
+ void _flushText(); // Write buffered text into doc
+
+ std::string _BestMatchingFont(std::string PDFname);
+
+ // Handling of node stack
+ Inkscape::XML::Node *pushNode(const char* name);
+ Inkscape::XML::Node *popNode();
+ std::vector<Inkscape::XML::Node *> _node_stack;
+ std::vector<int> _group_depth; // Depth of nesting groups
+ SvgTransparencyGroup *_transp_group_stack; // Transparency group stack
+ std::vector<SvgGraphicsState> _state_stack;
+
+ SPCSSAttr *_font_style; // Current font style
+ const char *_font_specification;
+ double _font_scaling;
+ bool _need_font_update;
+ Geom::Affine _text_matrix;
+ Geom::Point _text_position;
+ std::vector<SvgGlyph> _glyphs; // Added characters
+ bool _in_text_object; // Whether we are inside a text object
+ bool _invalidated_style;
+ GfxState *_current_state;
+ std::vector<std::string> _availableFontNames; // Full names, used for matching font names (Bug LP #179589).
+
+ bool _is_top_level; // Whether this SvgBuilder is the top-level one
+ SPDocument *_doc;
+ gchar *_docname; // Basename of the URI from which this document is created
+ XRef *_xref; // Cross-reference table from the PDF doc we're converting from
+ Inkscape::XML::Document *_xml_doc;
+ Inkscape::XML::Node *_root; // Root node from the point of view of this SvgBuilder
+ Inkscape::XML::Node *_container; // Current container (group/pattern/mask)
+ Inkscape::XML::Node *_preferences; // Preferences container node
+ double _width; // Document size in px
+ double _height; // Document size in px
+ double _ttm[6]; ///< temporary transform matrix
+ bool _ttm_is_set;
+
+ Inkscape::XML::Node *_nv = nullptr; // XML NameView xml
+ Inkscape::XML::Node *_page = nullptr; // XML Page definition
+ int _page_num = 0; // Are we on a page
+ double _page_left = 0 ; // Move to the left for more pages
+ double _page_top = 0 ; // Move to the top (maybe)
+ bool _page_offset = false;
+};
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // HAVE_POPPLER
+
+#endif // SEEN_EXTENSION_INTERNAL_PDFINPUT_SVGBUILDER_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/png-output.cpp b/src/extension/internal/png-output.cpp
new file mode 100644
index 0000000..68841ef
--- /dev/null
+++ b/src/extension/internal/png-output.cpp
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * An internal raster export which passes the generated PNG output
+ * to an external file. In the future this module could host more of
+ * the PNG generation code that isn't needed for other raster export options.
+ *
+ * Authors:
+ * Martin Owens <doctormo@geek-2.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "png-output.h"
+
+#include <cstdlib>
+#include <iostream>
+#include <string>
+#include <glibmm.h>
+#include <giomm.h>
+
+#include "clear-n_.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+void PngOutput::export_raster(Inkscape::Extension::Output * /*module*/,
+ const SPDocument * /*doc*/, std::string const &png_file, gchar const *filename)
+{
+ // We want to move the png file to the new location
+ Glib::RefPtr<Gio::File> input_fn = Gio::File::create_for_path(png_file);
+ Glib::RefPtr<Gio::File> output_fn = Gio::File::create_for_path(filename);
+ try {
+ // This file must be copied because the permissions must be created
+ // based on it's target location and not the temp directory.
+ input_fn->copy(output_fn, Gio::FILE_COPY_OVERWRITE | Gio::FILE_COPY_TARGET_DEFAULT_PERMS);
+ }
+ catch (const Gio::Error& e) {
+ std::cerr << "Moving resource " << png_file
+ << " to " << filename
+ << " failed: " << e.what().raw() << std::endl;
+ }
+}
+
+void PngOutput::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("Portable Network Graphic") "</name>\n"
+ "<id>" SP_MODULE_KEY_RASTER_PNG "</id>\n"
+ "<param name='png_interlacing' type='bool' gui-text='" N_("Interlacing") "'>false</param>"
+ "<param name='png_bitdepth' type='optiongroup' appearance='combo' gui-text='" N_("Bit Depth") "'>"
+ "<option value='99'>" N_("RGBA 8") "</option>" // First because it's the default option
+ "<option value='100'>" N_("RGBA 16") "</option>"
+ "<option value='67'>" N_("GrayAlpha 8") "</option>"
+ "<option value='68'>" N_("GrayAlpha 16") "</option>"
+ "<option value='35'>" N_("RGB 8") "</option>"
+ "<option value='36'>" N_("RGB 16") "</option>"
+ "<option value='0'>" N_("Gray 1") "</option>"
+ "<option value='1'>" N_("Gray 2") "</option>"
+ "<option value='2'>" N_("Gray 4") "</option>"
+ "<option value='3'>" N_("Gray 8") "</option>"
+ "<option value='4'>" N_("Gray 16") "</option>"
+ "</param>"
+ "<param name='png_compression' type='optiongroup' appearance='combo' gui-text='" N_("Compression") "'>"
+ "<option value='0'>" N_("0 - No Compression") "</option>"
+ "<option value='1'>" N_("1 - Best Speed") "</option>"
+ "<option value='2'>2</option>"
+ "<option value='3'>3</option>"
+ "<option value='4'>4</option>"
+ "<option value='5'>5</option>"
+ "<option value='6'>" N_("6 - Default Compression") "</option>" // First because it's default (and broken)
+ "<option value='7'>7</option>"
+ "<option value='8'>8</option>"
+ "<option value='9'>" N_("9 - Best Compression") "</option>"
+ "</param>"
+ "<param name='png_phys' gui-text='" N_("pHYs DPI") "' type='float' min='0.0' max='100000.0'>0.0</param>"
+ "<param name='png_antialias' gui-text='" N_("Antialias") "' type='int' min='0' max='3'>2</param>"
+ "<output raster=\"true\">\n"
+ "<extension>.png</extension>\n"
+ "<mimetype>image/png</mimetype>\n"
+ "<filetypename>" N_("Portable Network Graphic (*.png)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Default raster graphic export") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>",
+ new PngOutput());
+ // clang-format on
+}
+
+} // 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/png-output.h b/src/extension/internal/png-output.h
new file mode 100644
index 0000000..39e466d
--- /dev/null
+++ b/src/extension/internal/png-output.h
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * An internal raster export which passes the generated PNG output
+ * to an external file. In the future this module could host more of
+ * the PNG generation code that isn't needed for other raster export options.
+ *
+ * Authors:
+ * Martin Owens <doctormo@geek-2.com>
+ *
+ * Copyright (C) 2021 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef EXTENSION_INTERNAL_PNG_OUTPUT_H
+#define EXTENSION_INTERNAL_PNG_OUTPUT_H
+
+#include <glib.h>
+
+#include "extension/extension.h"
+#include "extension/implementation/implementation.h"
+#include "extension/output.h"
+#include "extension/system.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class PngOutput : public Inkscape::Extension::Implementation::Implementation
+{
+public:
+ PngOutput(){};
+
+ bool check(Inkscape::Extension::Extension *module) override { return true; };
+
+ void export_raster(Inkscape::Extension::Output *module,
+ const SPDocument *doc, std::string const &png_file, gchar const *filename) override;
+
+ static void init();
+
+private:
+};
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+#endif /* EXTENSION_INTERNAL_PNG_OUTPUT_H */
diff --git a/src/extension/internal/polyfill/README.md b/src/extension/internal/polyfill/README.md
new file mode 100644
index 0000000..2677a50
--- /dev/null
+++ b/src/extension/internal/polyfill/README.md
@@ -0,0 +1,19 @@
+# JavaScript polyfills
+
+This directory contains JavaScript "Polyfills" to support rendering of SVG 2
+features that are not well supported by browsers, but appeared in the 2016
+[specification](https://www.w3.org/TR/2016/CR-SVG2-20160915/pservers.html#MeshGradients)
+
+The included files are:
+ - `mesh.js` mesh gradients supporting bicubic meshes and mesh on strokes.
+ - `mesh_compressed.include` mesh.js minified and wrapped as a C++11 raw string literal.
+ - `hatch.js` hatch paint server supporting linear and absolute paths hatches
+ (relative paths are not fully supported)
+ - `hatch_tests` folder with tests used for `hatch.js` rendering
+
+## Details
+The coding standard used is [semistandard](https://github.com/Flet/semistandard),
+a more permissive (allows endrow semicolons) over the famous, open-source
+[standardjs](https://standardjs.com/).
+
+The minifier used for the compressed version is [JavaScript minifier](https://javascript-minifier.com/).
diff --git a/src/extension/internal/polyfill/hatch.js b/src/extension/internal/polyfill/hatch.js
new file mode 100644
index 0000000..c805425
--- /dev/null
+++ b/src/extension/internal/polyfill/hatch.js
@@ -0,0 +1,401 @@
+// SPDX-License-Identifier: CC0
+/** @file
+ * Use patterns to render a hatch paint server via this polyfill
+ *//*
+ * Authors:
+ * - Valentin Ionita (2019)
+ * License: CC0 / Public Domain
+ */
+
+(function () {
+ // Name spaces -----------------------------------
+ const svgNS = 'http://www.w3.org/2000/svg';
+ const xlinkNS = 'http://www.w3.org/1999/xlink';
+ const unitObjectBoundingBox = 'objectBoundingBox';
+ const unitUserSpace = 'userSpaceOnUse';
+
+ // Set multiple attributes to an element
+ const setAttributes = (el, attrs) => {
+ for (let key in attrs) {
+ el.setAttribute(key, attrs[key]);
+ }
+ };
+
+ // Copy attributes from the hatch with 'id' to the current element
+ const setReference = (el, id) => {
+ const attr = [
+ 'x', 'y', 'pitch', 'rotate',
+ 'hatchUnits', 'hatchContentUnits', 'transform'
+ ];
+ const template = document.getElementById(id.slice(1));
+
+ if (template && template.nodeName === 'hatch') {
+ attr.forEach(a => {
+ let t = template.getAttribute(a);
+ if (el.getAttribute(a) === null && t !== null) {
+ el.setAttribute(a, t);
+ }
+ });
+
+ if (el.children.length === 0) {
+ Array.from(template.children).forEach(c => {
+ el.appendChild(c.cloneNode(true));
+ });
+ }
+ }
+ };
+
+ // Order pain-order of hatchpaths relative to their pitch
+ const orderHatchPaths = (paths) => {
+ const nodeArray = [];
+ paths.forEach(p => nodeArray.push(p));
+
+ return nodeArray.sort((a, b) =>
+ // (pitch - a.offset) - (pitch - b.offset)
+ Number(b.getAttribute('offset')) - Number(a.getAttribute('offset'))
+ );
+ };
+
+ // Generate x-axis coordinates for the pattern paths
+ const generatePositions = (width, diagonal, initial, distance) => {
+ const offset = (diagonal - width) / 2;
+ const leftDistance = initial + offset;
+ const rightDistance = width + offset + distance;
+ const units = Math.round(leftDistance / distance) + 1;
+ let array = [];
+
+ for (let i = initial - units * distance; i < rightDistance; i += distance) {
+ array.push(i);
+ }
+
+ return array;
+ };
+
+ // Turn a path array into a tokenized version of it
+ const parsePath = (data) => {
+ let array = [];
+ let i = 0;
+ let len = data.length;
+ let last = 0;
+
+ /*
+ * Last state (last) index map
+ * 0 => ()
+ * 1 => (x y)
+ * 2 => (x)
+ * 3 => (y)
+ * 4 => (x1 y1 x2 y2 x y)
+ * 5 => (x2 y2 x y)
+ * 6 => (_ _ _ _ _ x y)
+ * 7 => (_)
+ */
+
+ while (i < len) {
+ switch (data[i].toUpperCase()) {
+ case 'Z':
+ array.push(data[i]);
+ i += 1;
+ last = 0;
+ break;
+ case 'M':
+ case 'L':
+ case 'T':
+ array.push(data[i], new Point(Number(data[i + 1]), Number(data[i + 2])));
+ i += 3;
+ last = 1;
+ break;
+ case 'H':
+ array.push(data[i], new Point(Number(data[i + 1]), null));
+ i += 2;
+ last = 2;
+ break;
+ case 'V':
+ array.push(data[i], new Point(null, Number(data[i + 1])));
+ i += 2;
+ last = 3;
+ break;
+ case 'C':
+ array.push(
+ data[i], new Point(Number(data[i + 1]), Number(data[i + 2])),
+ new Point(Number(data[i + 3]), Number(data[i + 4])),
+ new Point(Number(data[i + 5]), Number(data[i + 6]))
+ );
+ i += 7;
+ last = 4;
+ break;
+ case 'S':
+ case 'Q':
+ array.push(
+ data[i], new Point(Number(data[i + 1]), Number(data[i + 2])),
+ new Point(Number(data[i + 3]), Number(data[i + 4]))
+ );
+ i += 5;
+ last = 5;
+ break;
+ case 'A':
+ array.push(
+ data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4],
+ data[i + 5], new Point(Number(data[i + 6]), Number(data[i + 7]))
+ );
+ i += 8;
+ last = 6;
+ break;
+ case 'B':
+ array.push(data[i], data[i + 1]);
+ i += 2;
+ last = 7;
+ break;
+ default:
+ switch (last) {
+ case 1:
+ array.push(new Point(Number(data[i]), Number(data[i + 1])));
+ i += 2;
+ break;
+ case 2:
+ array.push(new Point(Number(data[i]), null));
+ i += 1;
+ break;
+ case 3:
+ array.push(new Point(null, Number(data[i])));
+ i += 1;
+ break;
+ case 4:
+ array.push(
+ new Point(Number(data[i]), Number(data[i + 1])),
+ new Point(Number(data[i + 2]), Number(data[i + 3])),
+ new Point(Number(data[i + 4]), Number(data[i + 5]))
+ );
+ i += 6;
+ break;
+ case 5:
+ array.push(
+ new Point(Number(data[i]), Number(data[i + 1])),
+ new Point(Number(data[i + 2]), Number(data[i + 3]))
+ );
+ i += 4;
+ break;
+ case 6:
+ array.push(
+ data[i], data[i + 1], data[i + 2], data[i + 3], data[i + 4],
+ new Point(Number(data[i + 5]), Number(data[i + 6]))
+ );
+ i += 7;
+ break;
+ default:
+ array.push(data[i]);
+ i += 1;
+ }
+ }
+ }
+
+ return array;
+ };
+
+ const getYDistance = (hatchpath) => {
+ const path = document.createElementNS(svgNS, 'path');
+ let d = hatchpath.getAttribute('d');
+
+ if (d[0].toUpperCase() !== 'M') {
+ d = `M 0,0 ${d}`;
+ }
+
+ path.setAttribute('d', d);
+
+ return path.getPointAtLength(path.getTotalLength()).y -
+ path.getPointAtLength(0).y;
+ };
+
+ // Point class --------------------------------------
+ class Point {
+ constructor (x, y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ toString () {
+ return `${this.x} ${this.y}`;
+ }
+
+ isPoint () {
+ return true;
+ }
+
+ clone () {
+ return new Point(this.x, this.y);
+ }
+
+ add (v) {
+ return new Point(this.x + v.x, this.y + v.y);
+ }
+
+ distSquared (v) {
+ let x = this.x - v.x;
+ let y = this.y - v.y;
+ return (x * x + y * y);
+ }
+ }
+
+ // Start of document processing ---------------------
+ const shapes = document.querySelectorAll('rect,circle,ellipse,path,text');
+
+ shapes.forEach((shape, i) => {
+ // Get id. If no id, create one.
+ let shapeId = shape.getAttribute('id');
+ if (!shapeId) {
+ shapeId = 'hatch_shape_' + i;
+ shape.setAttribute('id', shapeId);
+ }
+
+ const fill = shape.getAttribute('fill') || shape.style.fill;
+ const fillURL = fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);
+
+ if (fillURL && fillURL[1]) {
+ const hatch = document.getElementById(fillURL[1]);
+
+ if (hatch && hatch.nodeName === 'hatch') {
+ const href = hatch.getAttributeNS(xlinkNS, 'href');
+
+ if (href !== null && href !== '') {
+ setReference(hatch, href);
+ }
+
+ // Degenerate hatch, with no hatchpath children
+ if (hatch.children.length === 0) {
+ return;
+ }
+
+ const bbox = shape.getBBox();
+ const hatchDiag = Math.ceil(Math.sqrt(
+ bbox.width * bbox.width + bbox.height * bbox.height
+ ));
+
+ // Hatch variables
+ const units = hatch.getAttribute('hatchUnits') || unitObjectBoundingBox;
+ const contentUnits = hatch.getAttribute('hatchContentUnits') || unitUserSpace;
+ const rotate = Number(hatch.getAttribute('rotate')) || 0;
+ const transform = hatch.getAttribute('transform') ||
+ hatch.getAttribute('hatchTransform') || '';
+ const hatchpaths = orderHatchPaths(hatch.querySelectorAll('hatchpath,hatchPath'));
+ const x = units === unitObjectBoundingBox
+ ? (Number(hatch.getAttribute('x')) * bbox.width) || 0
+ : Number(hatch.getAttribute('x')) || 0;
+ const y = units === unitObjectBoundingBox
+ ? (Number(hatch.getAttribute('y')) * bbox.width) || 0
+ : Number(hatch.getAttribute('y')) || 0;
+ let pitch = units === unitObjectBoundingBox
+ ? (Number(hatch.getAttribute('pitch')) * bbox.width) || 0
+ : Number(hatch.getAttribute('pitch')) || 0;
+
+ if (contentUnits === unitObjectBoundingBox && bbox.height) {
+ pitch /= bbox.height;
+ }
+
+ // A negative value is an error.
+ // A value of zero disables rendering of the element
+ if (pitch <= 0) {
+ console.error('Non-positive pitch');
+ return;
+ }
+
+ // Pattern variables
+ const pattern = document.createElementNS(svgNS, 'pattern');
+ const patternId = `${fillURL[1]}_pattern`;
+ let patternWidth = bbox.width - bbox.width % pitch;
+ let patternHeight = 0;
+
+ const xPositions = generatePositions(patternWidth, hatchDiag, x, pitch);
+
+ hatchpaths.forEach(hatchpath => {
+ let offset = Number(hatchpath.getAttribute('offset')) || 0;
+ offset = offset > pitch ? (offset % pitch) : offset;
+ const currentXPositions = xPositions.map(p => p + offset);
+
+ const path = document.createElementNS(svgNS, 'path');
+ let d = '';
+
+ for (let j = 0; j < hatchpath.attributes.length; ++j) {
+ const attr = hatchpath.attributes.item(j);
+ if (attr.name !== 'd') {
+ path.setAttribute(attr.name, attr.value);
+ }
+ }
+
+ if (hatchpath.getAttribute('d') === null) {
+ d += currentXPositions.reduce(
+ (acc, xPos) => `${acc}M ${xPos} ${y} V ${hatchDiag} `, ''
+ );
+ patternHeight = hatchDiag;
+ } else {
+ const hatchData = hatchpath.getAttribute('d');
+ const data = parsePath(
+ hatchData.match(/([+-]?(\d+(\.\d+)?))|[MmZzLlHhVvCcSsQqTtAaBb]/g)
+ );
+ const len = data.length;
+ const startsWithM = data[0] === 'M';
+ const relative = data[0].toLowerCase() === data[0];
+ const point = new Point(0, 0);
+ let yOffset = getYDistance(hatchpath);
+
+ if (data[len - 1].y !== undefined && yOffset < data[len - 1].y) {
+ yOffset = data[len - 1].y;
+ }
+
+ // The offset must be positive
+ if (yOffset <= 0) {
+ console.error('y offset is non-positive');
+ return;
+ }
+ patternHeight = bbox.height - bbox.height % yOffset;
+
+ const currentYPositions = generatePositions(
+ patternHeight, hatchDiag, y, yOffset
+ );
+
+ currentXPositions.forEach(xPos => {
+ point.x = xPos;
+
+ if (!startsWithM && !relative) {
+ d += `M ${xPos} 0`;
+ }
+
+ currentYPositions.forEach(yPos => {
+ point.y = yPos;
+
+ if (relative) {
+ // Path is relative, set the first point in each path render
+ d += `M ${xPos} ${yPos} ${hatchData}`;
+ } else {
+ // Path is absolute, translate every point
+ d += data.map(e => e.isPoint && e.isPoint() ? e.add(point) : e)
+ .map(e => e.isPoint && e.isPoint() ? e.toString() : e)
+ .reduce((acc, e) => `${acc} ${e}`, '');
+ }
+ });
+ });
+
+ // The hatchpaths are infinite, so they have no fill
+ path.style.fill = 'none';
+ }
+
+ path.setAttribute('d', d);
+ pattern.appendChild(path);
+ });
+
+ setAttributes(pattern, {
+ 'id': patternId,
+ 'patternUnits': unitUserSpace,
+ 'patternContentUnits': contentUnits,
+ 'width': patternWidth,
+ 'height': patternHeight,
+ 'x': bbox.x,
+ 'y': bbox.y,
+ 'patternTransform': `rotate(${rotate} ${0} ${0}) ${transform}`
+ });
+ hatch.parentElement.insertBefore(pattern, hatch);
+
+ shape.style.fill = `url(#${patternId})`;
+ shape.setAttribute('fill', `url(#${patternId})`);
+ }
+ }
+ });
+})();
diff --git a/src/extension/internal/polyfill/hatch_compressed.include b/src/extension/internal/polyfill/hatch_compressed.include
new file mode 100644
index 0000000..cdd893f
--- /dev/null
+++ b/src/extension/internal/polyfill/hatch_compressed.include
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: CC0
+R"=====(
+!function(){const t="http://www.w3.org/2000/svg",e=(t,e,r,n)=>{const u=(e-t)/2,i=r+u,s=t+u+n;let h=[];for(let t=r-(Math.round(i/n)+1)*n;t<s;t+=n)h.push(t);return h};class r{constructor(t,e){this.x=t,this.y=e}toString(){return`${this.x} ${this.y}`}isPoint(){return!0}clone(){return new r(this.x,this.y)}add(t){return new r(this.x+t.x,this.y+t.y)}distSquared(t){let e=this.x-t.x,r=this.y-t.y;return e*e+r*r}}document.querySelectorAll("rect,circle,ellipse,path,text").forEach((n,u)=>{let i=n.getAttribute("id");i||(i="hatch_shape_"+u,n.setAttribute("id",i));const s=(n.getAttribute("fill")||n.style.fill).match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);if(s&&s[1]){const u=document.getElementById(s[1]);if(u&&"hatch"===u.nodeName){const i=u.getAttributeNS("http://www.w3.org/1999/xlink","href");if(null!==i&&""!==i&&((t,e)=>{const r=["x","y","pitch","rotate","hatchUnits","hatchContentUnits","transform"],n=document.getElementById(e.slice(1));n&&"hatch"===n.nodeName&&(r.forEach(e=>{let r=n.getAttribute(e);null===t.getAttribute(e)&&null!==r&&t.setAttribute(e,r)}),0===t.children.length&&Array.from(n.children).forEach(e=>{t.appendChild(e.cloneNode(!0))}))})(u,i),0===u.children.length)return;const h=n.getBBox(),o=Math.ceil(Math.sqrt(h.width*h.width+h.height*h.height)),a=u.getAttribute("hatchUnits")||"objectBoundingBox",c=u.getAttribute("hatchContentUnits")||"userSpaceOnUse",b=Number(u.getAttribute("rotate"))||0,l=u.getAttribute("transform")||u.getAttribute("hatchTransform")||"",m=(t=>{const e=[];return t.forEach(t=>e.push(t)),e.sort((t,e)=>Number(e.getAttribute("offset"))-Number(t.getAttribute("offset")))})(u.querySelectorAll("hatchpath,hatchPath")),d="objectBoundingBox"===a?Number(u.getAttribute("x"))*h.width||0:Number(u.getAttribute("x"))||0,g="objectBoundingBox"===a?Number(u.getAttribute("y"))*h.width||0:Number(u.getAttribute("y"))||0;let p="objectBoundingBox"===a?Number(u.getAttribute("pitch"))*h.width||0:Number(u.getAttribute("pitch"))||0;if("objectBoundingBox"===c&&h.height&&(p/=h.height),p<=0)return void console.error("Non-positive pitch");const N=document.createElementNS(t,"pattern"),f=`${s[1]}_pattern`;let w=h.width-h.width%p,A=0;const y=e(w,o,d,p);m.forEach(n=>{let u=Number(n.getAttribute("offset"))||0;u=u>p?u%p:u;const i=y.map(t=>t+u),s=document.createElementNS(t,"path");let a="";for(let t=0;t<n.attributes.length;++t){const e=n.attributes.item(t);"d"!==e.name&&s.setAttribute(e.name,e.value)}if(null===n.getAttribute("d"))a+=i.reduce((t,e)=>`${t}M ${e} ${g} V ${o} `,""),A=o;else{const u=n.getAttribute("d"),c=(t=>{let e=[],n=0,u=t.length,i=0;for(;n<u;)switch(t[n].toUpperCase()){case"Z":e.push(t[n]),n+=1,i=0;break;case"M":case"L":case"T":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2]))),n+=3,i=1;break;case"H":e.push(t[n],new r(Number(t[n+1]),null)),n+=2,i=2;break;case"V":e.push(t[n],new r(null,Number(t[n+1]))),n+=2,i=3;break;case"C":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2])),new r(Number(t[n+3]),Number(t[n+4])),new r(Number(t[n+5]),Number(t[n+6]))),n+=7,i=4;break;case"S":case"Q":e.push(t[n],new r(Number(t[n+1]),Number(t[n+2])),new r(Number(t[n+3]),Number(t[n+4]))),n+=5,i=5;break;case"A":e.push(t[n],t[n+1],t[n+2],t[n+3],t[n+4],t[n+5],new r(Number(t[n+6]),Number(t[n+7]))),n+=8,i=6;break;case"B":e.push(t[n],t[n+1]),n+=2,i=7;break;default:switch(i){case 1:e.push(new r(Number(t[n]),Number(t[n+1]))),n+=2;break;case 2:e.push(new r(Number(t[n]),null)),n+=1;break;case 3:e.push(new r(null,Number(t[n]))),n+=1;break;case 4:e.push(new r(Number(t[n]),Number(t[n+1])),new r(Number(t[n+2]),Number(t[n+3])),new r(Number(t[n+4]),Number(t[n+5]))),n+=6;break;case 5:e.push(new r(Number(t[n]),Number(t[n+1])),new r(Number(t[n+2]),Number(t[n+3]))),n+=4;break;case 6:e.push(t[n],t[n+1],t[n+2],t[n+3],t[n+4],new r(Number(t[n+5]),Number(t[n+6]))),n+=7;break;default:e.push(t[n]),n+=1}}return e})(u.match(/([+-]?(\d+(\.\d+)?))|[MmZzLlHhVvCcSsQqTtAaBb]/g)),b=c.length,l="M"===c[0],m=c[0].toLowerCase()===c[0],d=new r(0,0);let p=(e=>{const r=document.createElementNS(t,"path");let n=e.getAttribute("d");return"M"!==n[0].toUpperCase()&&(n=`M 0,0 ${n}`),r.setAttribute("d",n),r.getPointAtLength(r.getTotalLength()).y-r.getPointAtLength(0).y})(n);if(void 0!==c[b-1].y&&p<c[b-1].y&&(p=c[b-1].y),p<=0)return void console.error("y offset is non-positive");A=h.height-h.height%p;const N=e(A,o,g,p);i.forEach(t=>{d.x=t,l||m||(a+=`M ${t} 0`),N.forEach(e=>{d.y=e,a+=m?`M ${t} ${e} ${u}`:c.map(t=>t.isPoint&&t.isPoint()?t.add(d):t).map(t=>t.isPoint&&t.isPoint()?t.toString():t).reduce((t,e)=>`${t} ${e}`,"")})}),s.style.fill="none"}s.setAttribute("d",a),N.appendChild(s)}),((t,e)=>{for(let r in e)t.setAttribute(r,e[r])})(N,{id:f,patternUnits:"userSpaceOnUse",patternContentUnits:c,width:w,height:A,x:h.x,y:h.y,patternTransform:`rotate(${b} 0 0) ${l}`}),u.parentElement.insertBefore(N,u),n.style.fill=`url(#${f})`,n.setAttribute("fill",`url(#${f})`)}}})}();
+)====="
diff --git a/src/extension/internal/polyfill/hatch_tests/hatch.svg b/src/extension/internal/polyfill/hatch_tests/hatch.svg
new file mode 100644
index 0000000..7e2f8de
--- /dev/null
+++ b/src/extension/internal/polyfill/hatch_tests/hatch.svg
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="360">
+ <defs>
+ <hatch
+ pitch="15"
+ hatchUnits="userSpaceOnUse"
+ id="simple6">
+ <hatchPath
+ d="m 0,0 5,10 5,-5"
+ offset="5"
+ stroke="#a080ff" />
+ </hatch>
+ <hatch
+ id="transform2"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ rotate="30">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20" />
+ </hatch>
+ <hatch
+ id="transform4"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ rotate="45">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20" />
+ </hatch>
+ <hatch
+ id="transform8"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ x="-5"
+ y="-10"
+ rotate="30">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20" />
+ </hatch>
+ <hatch
+ id="transform9"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ rotate="30"
+ x="-5"
+ y="-10"
+ hatchTransform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-8.4757068,43.273395)">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20" />
+ </hatch>
+ </defs>
+ <rect fill="url(#simple6)" stroke="black" stroke-width="2" x="25" y="25" width="150" height="150"/>
+ <rect fill="url(#transform2) #ff0000;" stroke="black" stroke-width="1" width="115" height="115" x="25" y="200" />
+
+ <script type="text/javascript" xlink:href="../hatch.js"></script>
+</svg>
diff --git a/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg b/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg
new file mode 100644
index 0000000..9c45296
--- /dev/null
+++ b/src/extension/internal/polyfill/hatch_tests/hatch01_with_js.svg
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ inkscape:version="1.0alpha2 (5c5a8378bf, 2019-06-27, custom)"
+ sodipodi:docname="hatch01_with_js.svg"
+ id="svg24"
+ version="1.1"
+ height="400"
+ width="400">
+ <metadata
+ id="metadata28">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ inkscape:current-layer="svg24"
+ inkscape:window-maximized="0"
+ inkscape:window-y="23"
+ inkscape:window-x="26"
+ inkscape:cy="200"
+ inkscape:cx="200"
+ inkscape:zoom="0.6025"
+ showgrid="false"
+ id="namedview26"
+ inkscape:window-height="480"
+ inkscape:window-width="686"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0"
+ guidetolerance="10"
+ gridtolerance="10"
+ objecttolerance="10"
+ borderopacity="1"
+ bordercolor="#666666"
+ pagecolor="#ffffff" />
+ <defs
+ id="defs14">
+ <hatch
+ rotate="135"
+ pitch="5"
+ hatchContentUnits="userSpaceOnUse"
+ hatchUnits="userSpaceOnUse"
+ id="hatch1">
+ <hatchpath
+ id="hatchpath2"
+ stroke-width="2"
+ stroke="#a080ff" />
+ </hatch>
+ <hatch
+ rotate="135"
+ pitch="0.05"
+ hatchContentUnits="userSpaceOnUse"
+ hatchUnits="objectBoundingBox"
+ id="hatch2">
+ <hatchpath
+ id="hatchpath5"
+ stroke-width="2"
+ stroke="#a080ff" />
+ </hatch>
+ <hatch
+ rotate="135"
+ pitch="5"
+ hatchContentUnits="objectBoundingBox"
+ hatchUnits="userSpaceOnUse"
+ id="hatch3">
+ <hatchpath
+ id="hatchpath8"
+ stroke-width="0.02"
+ stroke="#a080ff" />
+ </hatch>
+ <hatch
+ rotate="135"
+ pitch="0.05"
+ hatchContentUnits="objectBoundingBox"
+ hatchUnits="objectBoundingBox"
+ id="hatch4">
+ <hatchpath
+ id="hatchpath11"
+ stroke-width="0.02"
+ stroke="#a080ff" />
+ </hatch>
+ </defs>
+ <rect
+ id="rect16"
+ height="100"
+ width="100"
+ y="50"
+ x="50"
+ stroke-width="2"
+ stroke="black"
+ fill="url(#hatch1)" />
+ <rect
+ id="rect18"
+ height="100"
+ width="100"
+ y="50"
+ x="250"
+ stroke-width="2"
+ stroke="black"
+ fill="url(#hatch2)" />
+ <rect
+ id="rect20"
+ height="100"
+ width="100"
+ y="250"
+ x="50"
+ stroke-width="2"
+ stroke="black"
+ fill="url(#hatch3)" />
+ <rect
+ id="rect22"
+ height="100"
+ width="100"
+ y="250"
+ x="250"
+ stroke-width="2"
+ stroke="black"
+ fill="url(#hatch4)" />
+ <script type="text/javascript" xlink:href="../hatch.js"></script>
+</svg>
diff --git a/src/extension/internal/polyfill/hatch_tests/hatch_test.svg b/src/extension/internal/polyfill/hatch_tests/hatch_test.svg
new file mode 100644
index 0000000..fd45a8d
--- /dev/null
+++ b/src/extension/internal/polyfill/hatch_tests/hatch_test.svg
@@ -0,0 +1,11730 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="2600"
+ height="1700"
+ id="svg3780"
+ version="1.1"
+ inkscape:version="1.0alpha2 (2d4d49aaa0, 2019-06-18, custom)"
+ sodipodi:docname="hatch_test (copy).svg">
+ <defs
+ id="defs3782">
+ <hatch
+ id="simple1"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ stroke-width="2"
+ id="hatchPath2" />
+ </hatch>
+ <hatch
+ id="simple2"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="15"
+ id="hatchPath5" />
+ </hatch>
+ <hatch
+ id="simple3"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="M 0,0 5,10"
+ id="hatchPath8" />
+ </hatch>
+ <hatch
+ id="simple4"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="L 0,0 5,10"
+ id="hatchPath11" />
+ </hatch>
+ <hatch
+ id="simple5"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="M 0,0 5,10 10,5"
+ id="hatchPath14" />
+ </hatch>
+ <hatch
+ id="simple6"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="m 0,0 5,10 5,-5"
+ id="hatchPath17" />
+ </hatch>
+ <hatch
+ id="simple7"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="M 0,0 5,10 M 5,20"
+ id="hatchPath20" />
+ </hatch>
+ <hatch
+ id="transform1"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="L 0,0 5,10 0,20"
+ id="hatchPath23" />
+ </hatch>
+ <hatch
+ id="transform2"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ rotate="30">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20"
+ id="hatchPath26" />
+ </hatch>
+ <hatch
+ id="transform4"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ rotate="45">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20"
+ id="hatchPath29" />
+ </hatch>
+ <hatch
+ id="transform7"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ x="-5"
+ y="-10">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20"
+ id="hatchPath32" />
+ </hatch>
+ <hatch
+ id="transform8"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ x="-5"
+ y="-10"
+ rotate="30">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20"
+ id="hatchPath35" />
+ </hatch>
+ <hatch
+ id="transform9"
+ hatchUnits="userSpaceOnUse"
+ pitch="15"
+ rotate="30"
+ x="-5"
+ y="-10"
+ hatchTransform="matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-8.4757068,43.273395)">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,20"
+ id="hatchPath38" />
+ </hatch>
+ <hatch
+ id="multiple1"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ id="hatchPath41" />
+ <hatchPath
+ stroke="#32ff3f"
+ offset="10"
+ id="hatchPath43" />
+ </hatch>
+ <hatch
+ id="multiple2"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ id="hatchPath46" />
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10"
+ id="hatchPath48" />
+ </hatch>
+ <hatch
+ id="multiple3"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="L 0,0 5,17"
+ id="hatchPath51" />
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10"
+ id="hatchPath53" />
+ </hatch>
+ <hatch
+ id="stroke1"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ stroke-width="5"
+ stroke-dasharray="10 4 2 4"
+ id="hatchPath56" />
+ </hatch>
+ <hatch
+ id="overflow1"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ d="L 0,0 5,5 -5,15, 0,20"
+ id="hatchPath59" />
+ </hatch>
+ <hatch
+ id="overflow2"
+ style="overflow:hidden"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="0"
+ d="L 0,0 5,5 -5,15, 0,20"
+ id="hatchPath62" />
+ </hatch>
+ <hatch
+ id="overflow3"
+ style="overflow:visible"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="0"
+ d="L 0,0 5,5 -5,15, 0,20"
+ id="hatchPath65" />
+ </hatch>
+ <hatch
+ id="overflow4"
+ style="overflow:visible"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#32ff3f"
+ offset="5"
+ id="hatchPath68" />
+ <hatchPath
+ stroke="#ff0000"
+ offset="20"
+ id="hatchPath70" />
+ </hatch>
+ <hatch
+ id="ref1"
+ hatchUnits="userSpaceOnUse"
+ pitch="15">
+ <hatchPath
+ stroke="#a080ff"
+ offset="5"
+ id="hatchPath73" />
+ </hatch>
+ <hatch
+ id="ref2"
+ xlink:href="#ref1" />
+ <hatch
+ id="ref3"
+ xlink:href="#ref1"
+ pitch="45" />
+ <hatch
+ id="degenerate1"
+ pitch="45" />
+ <hatch
+ id="degenerate2"
+ xlink:href="#nonexisting"
+ pitch="45" />
+ <hatch
+ id="degenerate3"
+ pitch="30">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 0,15"
+ id="hatchPath80" />
+ </hatch>
+ <hatch
+ id="degenerate4"
+ pitch="30">
+ <hatchPath
+ stroke="#a080ff"
+ offset="10"
+ d="L 0,0 5,10 -5,15"
+ id="hatchPath83" />
+ </hatch>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient11910">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop11912" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop11914" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient10590">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop10592" />
+ <stop
+ style="stop-color:#c9c9c9;stop-opacity:0;"
+ offset="1"
+ id="stop10594" />
+ </linearGradient>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4338">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4340"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4342">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4344"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4346">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4348"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4350">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4352"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4354">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4356"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4358">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4360"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4362">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath4366">
+ <rect
+ style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4368"
+ width="115"
+ height="115"
+ x="175"
+ y="42.362183" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7203">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7205"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7207">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7209"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7211">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7213"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7215">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7217"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7219">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7221"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7223">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7225"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7227">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7229"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7231">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7233"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7235">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7237"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7239">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7241"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7243">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7245"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7247">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7249"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7251">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7253"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7255">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7257"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7259">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7261"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7263">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7265"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7267">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7269"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7271">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7273"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7275">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7277"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7279">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7281"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7283">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7285"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7287">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7289"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7291">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7293"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7295">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7297"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7299">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7301"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7303">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7305"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7307">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7309"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7311">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7313"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7315">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7317"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7319">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7321"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7323">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7325"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7327">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7329"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7331">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7333"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7335">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7337"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7339">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7341"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7343">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7345"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7347">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7349"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7351">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7353"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7355">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7357"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7359">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7361"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7363">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7365"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7367">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7369"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7371">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7373"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7375">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7377"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7379">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7381"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7383">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7385"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7387">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7389"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7391">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7393"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7395">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7397"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7399">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7401"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7403">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7405"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7407">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7409"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7411">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7413"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7415">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7417"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7419">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7421"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7423">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7425"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7427">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7429"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7431">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7433"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7435">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7437"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7439">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7441"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7443">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7445"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7447">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7449"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7451">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7453"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7455">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7457"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7459">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7461"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7463">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7465"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7467">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7469"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7471">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7473"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7475">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7477"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7479">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7481"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7483">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7485"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7487">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7489"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7491">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7493"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7495">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7497"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7499">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7501"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7503">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7505"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7507">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7509"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7511">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7513"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7515">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7517"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7519">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7521"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7523">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7525"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7527">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7529"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7531">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7533"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7535">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7537"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7539">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7541"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7543">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7545"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7547">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7549"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7551">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7553"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7555">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7557"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7559">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7561"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7563">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7565"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7567">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7569"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7571">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7573"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7575">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7577"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7579">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7581"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7583">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7585"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7587">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7589"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7591">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7593"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7595">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7597"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7599">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7601"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7603">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7605"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7607">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7609"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7611">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7613"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7615">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7617"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7619">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7621"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7623">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7625"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7627">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7629"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7631">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7633"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7635">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7637"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7639">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7641"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7643">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7645"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7647">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7649"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7651">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7653"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7655">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7657"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7659">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7661"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7663">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7665"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7667">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7669"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7671">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7673"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7675">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7677"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7679">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7681"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7683">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7685"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7687">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7689"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7691">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7693"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7695">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7697"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7699">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7701"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7703">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7705"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7707">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7709"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7711">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7713"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7715">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7717"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7719">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7721"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7723">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7725"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7727">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7729"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7731">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7733"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7735">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7737"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7739">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7741"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7743">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7745"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7747">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7749"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7751">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7753"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7755">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7757"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7759">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7761"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7763">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7765"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7767">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7769"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7771">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7773"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7775">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7777"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7779">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7781"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7783">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7785"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7787">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7789"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7791">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7793"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7795">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7797"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7799">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7801"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7803">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7805"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7807">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7809"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7811">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7813"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7815">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7817"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7819">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7821"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7823">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7825"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7827">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7829"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7831">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7833"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7835">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7837"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7839">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7841"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7843">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7845"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7847">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7849"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7851">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7853"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7855">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7857"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7859">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7861"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7863">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7865"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7867">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7869"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7871">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7873"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7875">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7877"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7879">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7881"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7883">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7885"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7887">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7889"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7891">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7893"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7895">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7897"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7899">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7901"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7903">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7905"
+ width="115"
+ height="115"
+ x="170.5"
+ y="327.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7948">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7950"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7952">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7954"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7956">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7958"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7960">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7962"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7964">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7966"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7968">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7970"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7972">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7974"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath7976">
+ <rect
+ y="452.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect7978"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8370">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8372"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8374">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8376"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8378">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8380"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8382">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8384"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8386">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8388"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8390">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8392"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8394">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8396"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8398">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8400"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8402">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8404"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8406">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8408"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8410">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8412"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8414">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8416"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8418">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8420"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8422">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8424"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8426">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8428"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8430">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8432"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8434">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8436"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8438">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8440"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8442">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8444"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8446">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8448"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8450">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8452"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8454">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8456"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8458">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8460"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8462">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8464"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8466">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8468"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8470">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8472"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8474">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8476"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8478">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8480"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8482">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8484"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8486">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8488"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8490">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8492"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8494">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8496"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8498">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8500"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8502">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8504"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8506">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8508"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8510">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8512"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8514">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8516"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8518">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8520"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8522">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8524"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8526">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8528"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8530">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8532"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8534">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8536"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8538">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8540"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8542">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8544"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8546">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8548"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8550">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8552"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8554">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8556"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8558">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8560"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8562">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8564"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8566">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8568"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8570">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8572"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8574">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8576"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8578">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8580"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8582">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8584"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8586">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8588"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8590">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8592"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8594">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8596"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8598">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8600"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8602">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8604"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8606">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8608"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8610">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8612"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8614">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8616"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8618">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8620"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8622">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8624"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8626">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8628"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8630">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8632"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8634">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8636"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8638">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8640"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8642">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8644"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8646">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8648"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8650">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8652"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8654">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8656"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8658">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8660"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8662">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8664"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8666">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8668"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8670">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8672"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8674">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8676"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8678">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8680"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8682">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8684"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8686">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8688"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8690">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8692"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8694">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8696"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8698">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8700"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8702">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8704"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8706">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8708"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8710">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8712"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8714">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8716"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8718">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8720"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8722">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8724"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8726">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8728"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8730">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8732"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8734">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8736"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8738">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8740"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8742">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8744"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8746">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8748"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8750">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8752"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8754">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8756"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8758">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8760"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8762">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8764"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8766">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8768"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8770">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8772"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8774">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8776"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8778">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8780"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8782">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8784"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8786">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8788"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8790">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8792"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8794">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8796"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8798">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8800"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8802">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8804"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8806">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8808"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8810">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8812"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8814">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8816"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8818">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8820"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8822">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8824"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8826">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8828"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8830">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8832"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8834">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8836"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8838">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8840"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8842">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8844"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8846">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8848"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8850">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8852"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8854">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8856"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8858">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8860"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8862">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8864"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8866">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8868"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8870">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8872"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8874">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8876"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8878">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8880"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8882">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8884"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8886">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8888"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8890">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8892"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8894">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8896"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8898">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8900"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8902">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8904"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8906">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8908"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8910">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8912"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8914">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8916"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8918">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8920"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8922">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8924"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8926">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8928"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8930">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8932"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8934">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8936"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8938">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8940"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8942">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8944"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8946">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8948"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8950">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8952"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8954">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8956"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8958">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8960"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8962">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8964"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8966">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8968"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8970">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8972"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8974">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8976"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8978">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8980"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8982">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8984"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8986">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8988"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8990">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8992"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8994">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect8996"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath8998">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9000"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9002">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9004"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9006">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9008"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9010">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9012"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9014">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9016"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9018">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9020"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9022">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9024"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9026">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9028"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9030">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9032"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9034">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9036"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9038">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9040"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9042">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9044"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9046">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9048"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9050">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9052"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9054">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9056"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9058">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9060"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9062">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9064"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9066">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9068"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9070">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9072"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9074">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9076"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9078">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9080"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9082">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9084"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9086">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9088"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9090">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9092"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9094">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9096"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9098">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9100"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9102">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9104"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9106">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9108"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9110">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9112"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9114">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9116"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9118">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9120"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9122">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9124"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9126">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9128"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9130">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9132"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9134">
+ <rect
+ style="fill:#000000;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9136"
+ width="115"
+ height="115"
+ x="170.5"
+ y="577.86218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9545">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9547"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9549">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9551"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9553">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9555"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9557">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9559"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9561">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9563"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9565">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9567"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9569">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9571"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath9573">
+ <rect
+ y="62.862183"
+ x="355.5"
+ height="115"
+ width="115"
+ id="rect9575"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10402">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10404"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10406">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10408"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10410">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10412"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10414">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10416"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10418">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10420"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10422">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10424"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10426">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10428"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10430">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10432"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10434">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10436"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10438">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10440"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10442">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10444"
+ width="115"
+ height="115"
+ x="520"
+ y="267.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10476">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10478"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10480">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10482"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10484">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10486"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10488">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10490"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10492">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10494"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10496">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10498"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10500">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10502"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10504">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10506"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10508">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10510"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10512">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10514"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10516">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10518"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10520">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10522"
+ width="115"
+ height="115"
+ x="520"
+ y="462.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10554">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10556"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10558">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10560"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10562">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10564"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10566">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10568"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10570">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10572"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10574">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10576"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10578">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10580"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10582">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10584"
+ width="115"
+ height="115"
+ x="505"
+ y="577.36218" />
+ </clipPath>
+ <pattern
+ patternUnits="userSpaceOnUse"
+ width="71"
+ height="71"
+ patternTransform="translate(-160.5,-138.125)"
+ id="pattern10608">
+ <rect
+ y="0.48718262"
+ x="0.5"
+ height="70"
+ width="70"
+ id="rect10606"
+ style="opacity:0.94899998;fill:#000000;fill-opacity:1;stroke:#ffff00;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:0.94117647;stroke-dasharray:none;stroke-dashoffset:0" />
+ </pattern>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10625">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10627"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10629">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10631"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10633">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10635"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10637">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10639"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10641">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10643"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10645">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10647"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10649">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10651"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10653">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10655"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10657">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10659"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10661">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10663"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10665">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10667"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10669">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10671"
+ width="115"
+ height="115"
+ x="530"
+ y="702.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10757">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10759"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10761">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10763"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10765">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10767"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10769">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10771"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10773">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10775"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10777">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10779"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10781">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10783"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10785">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10787"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10789">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10791"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10793">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10795"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10797">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10799"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10801">
+ <rect
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10803"
+ width="115"
+ height="115"
+ x="535"
+ y="762.36218" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10877">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10879"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10881">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10883"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10885">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10887"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10889">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10891"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10893">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10895"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10897">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10899"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10901">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10903"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10905">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10907"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10909">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10911"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10913">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10915"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10917">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10919"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10921">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10923"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10925">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10927"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10929">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10931"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath10933">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect10935"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11035">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11037"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11039">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11041"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11043">
+ <rect
+ y="212.36218"
+ x="915"
+ height="115"
+ width="115"
+ id="rect11045"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11047">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11049"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11051">
+ <rect
+ y="212.36218"
+ x="930"
+ height="115"
+ width="115"
+ id="rect11053"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11055">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11057"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11059">
+ <rect
+ y="212.36218"
+ x="945"
+ height="115"
+ width="115"
+ id="rect11061"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11063">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11065"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11067">
+ <rect
+ y="212.36218"
+ x="960"
+ height="115"
+ width="115"
+ id="rect11069"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11071">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11073"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11075">
+ <rect
+ y="212.36218"
+ x="975"
+ height="115"
+ width="115"
+ id="rect11077"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11079">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11081"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11083">
+ <rect
+ y="212.36218"
+ x="990"
+ height="115"
+ width="115"
+ id="rect11085"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11087">
+ <rect
+ y="342.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11089"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11091">
+ <rect
+ y="212.36218"
+ x="1005"
+ height="115"
+ width="115"
+ id="rect11093"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11155">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11157"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11159">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11161"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11163">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11165"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11167">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11169"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11171">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11173"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11175">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11177"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11179">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11181"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11183">
+ <rect
+ y="217.36218"
+ x="1485"
+ height="115"
+ width="115"
+ id="rect11185"
+ style="fill:#000000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11488">
+ <rect
+ y="168.9841"
+ x="-17.026188"
+ height="95.052338"
+ width="95.052338"
+ id="rect11490"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11492">
+ <rect
+ y="177.75092"
+ x="-8.259387"
+ height="95.052338"
+ width="95.052338"
+ id="rect11494"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11496">
+ <rect
+ y="186.51772"
+ x="0.50741416"
+ height="95.052338"
+ width="95.052338"
+ id="rect11498"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11500">
+ <rect
+ y="195.28452"
+ x="9.2742147"
+ height="95.052338"
+ width="95.052338"
+ id="rect11502"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11504">
+ <rect
+ y="204.05132"
+ x="18.041016"
+ height="95.052338"
+ width="95.052338"
+ id="rect11506"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11508">
+ <rect
+ y="212.81812"
+ x="26.807816"
+ height="95.052338"
+ width="95.052338"
+ id="rect11510"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11512">
+ <rect
+ y="221.58492"
+ x="35.574615"
+ height="95.052338"
+ width="95.052338"
+ id="rect11514"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11516">
+ <rect
+ y="221.58492"
+ x="35.574615"
+ height="95.052338"
+ width="95.052338"
+ id="rect11518"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11520">
+ <rect
+ y="221.58492"
+ x="35.574615"
+ height="95.052338"
+ width="95.052338"
+ id="rect11522"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11524">
+ <rect
+ y="221.58492"
+ x="35.574615"
+ height="95.052338"
+ width="95.052338"
+ id="rect11526"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <clipPath
+ clipPathUnits="userSpaceOnUse"
+ id="clipPath11528">
+ <rect
+ y="221.58492"
+ x="35.574615"
+ height="95.052338"
+ width="95.052338"
+ id="rect11530"
+ style="fill:#000000;stroke:#000000;stroke-width:1.06241;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ transform="matrix(0.85550008,-0.51780267,0.85550008,0.51780267,0,0)" />
+ </clipPath>
+ <linearGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient11910"
+ id="linearGradient11916"
+ x1="179.5"
+ y1="-217.63782"
+ x2="480.5"
+ y2="-217.63782"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="0.3245239"
+ inkscape:cx="1145.4883"
+ inkscape:cy="1129.6405"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:snap-bbox="true"
+ inkscape:window-width="1920"
+ inkscape:window-height="1014"
+ inkscape:window-x="0"
+ inkscape:window-y="27"
+ inkscape:window-maximized="1"
+ inkscape:object-paths="true"
+ inkscape:object-nodes="true">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3794" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata3785">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="27.142857"
+ y="42.362183"
+ id="text3788"><tspan
+ sodipodi:role="line"
+ id="tspan3790"
+ x="27.142857"
+ y="42.362183"
+ style="font-size:24px;line-height:1.25;font-family:sans-serif">Simple hatches</tspan></text>
+ <g
+ id="g4372"
+ transform="translate(384.5,5.5000026)">
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4366)"
+ inkscape:connector-curvature="0"
+ id="path3798"
+ d="M 180,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4362)"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 195,42.362183 V 192.36218"
+ id="path4310"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4358)"
+ inkscape:connector-curvature="0"
+ id="path4314"
+ d="M 210,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4354)"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 225,42.362183 V 192.36218"
+ id="path4318"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4350)"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 240,42.362183 V 192.36218"
+ id="path4322"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4346)"
+ inkscape:connector-curvature="0"
+ id="path4326"
+ d="M 255,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4342)"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 270,42.362183 V 192.36218"
+ id="path4330"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4338)"
+ inkscape:connector-curvature="0"
+ id="path4334"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="62.362183"
+ x="181"
+ height="115"
+ width="115"
+ id="rect4370"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <rect
+ style="fill:url(#simple1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4383"
+ width="115"
+ height="115"
+ x="435.5"
+ y="67.862183" />
+ <g
+ transform="translate(384.5,135.5)"
+ id="g4385">
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 180,42.362183 V 192.36218"
+ id="path4387"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4366)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4389"
+ d="M 195,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4362)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 210,42.362183 V 192.36218"
+ id="path4391"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4358)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4393"
+ d="M 225,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4354)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4395"
+ d="M 240,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4350)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 255,42.362183 V 192.36218"
+ id="path4397"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4346)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path4399"
+ d="M 270,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4342)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 285,42.362183 V 192.36218"
+ id="path4401"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4338)"
+ transform="translate(6,20)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4403"
+ width="115"
+ height="115"
+ x="181"
+ y="62.362183" />
+ </g>
+ <rect
+ y="197.86218"
+ x="435.5"
+ height="115"
+ width="115"
+ id="rect4405"
+ style="fill:url(#simple2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <g
+ id="g4695"
+ transform="translate(384.5,135.5)">
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4366)"
+ inkscape:connector-curvature="0"
+ id="path4697"
+ d="M 180,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4362)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 195,42.362183 V 192.36218"
+ id="path4699"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4358)"
+ inkscape:connector-curvature="0"
+ id="path4701"
+ d="M 210,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4354)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 225,42.362183 V 192.36218"
+ id="path4703"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4350)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 240,42.362183 V 192.36218"
+ id="path4705"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4346)"
+ inkscape:connector-curvature="0"
+ id="path4707"
+ d="M 255,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4342)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 270,42.362183 V 192.36218"
+ id="path4709"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4338)"
+ inkscape:connector-curvature="0"
+ id="path4711"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="62.362183"
+ x="181"
+ height="115"
+ width="115"
+ id="rect4713"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,327.36218 5,10"
+ id="path5443"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7903)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,337.36218 5,10"
+ id="path5445"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7899)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,347.36218 5,10"
+ id="path5447"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7895)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,357.36218 5,10"
+ id="path5449"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7891)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,367.36218 5,10"
+ id="path5451"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7887)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5453"
+ d="m 175,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7883)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5455"
+ d="m 175,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7879)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5457"
+ d="m 175,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7875)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5459"
+ d="m 175,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7871)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5461"
+ d="m 175,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7867)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,377.36218 5,10"
+ id="path5463"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7863)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5465"
+ d="m 175,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7859)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5467"
+ d="m 175,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7855)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5469"
+ d="m 175,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7851)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5471"
+ d="m 175,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7847)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5473"
+ d="m 175,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7843)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,387.36218 5,10"
+ id="path5475"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7839)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,397.36218 5,10"
+ id="path5477"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7835)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,407.36218 5,10"
+ id="path5479"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7831)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,417.36218 5,10"
+ id="path5481"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7827)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,427.36218 5,10"
+ id="path5483"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7823)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5485"
+ d="m 175,437.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7819)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5487"
+ d="m 190,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7815)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5489"
+ d="m 190,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7811)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5491"
+ d="m 190,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7807)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5493"
+ d="m 190,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7803)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5495"
+ d="m 190,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7799)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,327.36218 5,10"
+ id="path5497"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7795)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,337.36218 5,10"
+ id="path5499"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7791)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,347.36218 5,10"
+ id="path5501"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7787)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,357.36218 5,10"
+ id="path5503"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7783)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,367.36218 5,10"
+ id="path5505"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7779)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5507"
+ d="m 190,377.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7775)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,387.36218 5,10"
+ id="path5509"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7771)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,397.36218 5,10"
+ id="path5511"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7767)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,407.36218 5,10"
+ id="path5513"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7763)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,417.36218 5,10"
+ id="path5515"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7759)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,427.36218 5,10"
+ id="path5517"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7755)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5519"
+ d="m 190,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7751)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5521"
+ d="m 190,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7747)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5523"
+ d="m 190,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7743)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5525"
+ d="m 190,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7739)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5527"
+ d="m 190,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7735)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,437.36218 5,10"
+ id="path5529"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7731)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5531"
+ d="m 205,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7727)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5533"
+ d="m 205,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7723)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5535"
+ d="m 205,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7719)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5537"
+ d="m 205,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7715)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5539"
+ d="m 205,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7711)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,327.36218 5,10"
+ id="path5541"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7707)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,337.36218 5,10"
+ id="path5543"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7703)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,347.36218 5,10"
+ id="path5545"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7699)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,357.36218 5,10"
+ id="path5547"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7695)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,367.36218 5,10"
+ id="path5549"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7691)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5551"
+ d="m 205,377.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7687)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,387.36218 5,10"
+ id="path5553"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7683)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,397.36218 5,10"
+ id="path5555"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7679)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,407.36218 5,10"
+ id="path5557"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7675)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,417.36218 5,10"
+ id="path5559"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7671)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,427.36218 5,10"
+ id="path5561"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7667)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5563"
+ d="m 205,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7663)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5565"
+ d="m 205,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7659)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5567"
+ d="m 205,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7655)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5569"
+ d="m 205,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7651)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5571"
+ d="m 205,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7647)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,437.36218 5,10"
+ id="path5573"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7643)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,327.36218 5,10"
+ id="path5575"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7639)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,337.36218 5,10"
+ id="path5577"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7635)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,347.36218 5,10"
+ id="path5579"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7631)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,357.36218 5,10"
+ id="path5581"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7627)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,367.36218 5,10"
+ id="path5583"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7623)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5585"
+ d="m 220,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7619)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5587"
+ d="m 220,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7615)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5589"
+ d="m 220,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7611)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5591"
+ d="m 220,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7607)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5593"
+ d="m 220,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7603)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,377.36218 5,10"
+ id="path5595"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7599)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5597"
+ d="m 220,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7595)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5599"
+ d="m 220,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7591)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5601"
+ d="m 220,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7587)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5603"
+ d="m 220,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7583)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5605"
+ d="m 220,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7579)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,387.36218 5,10"
+ id="path5607"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7575)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,397.36218 5,10"
+ id="path5609"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7571)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,407.36218 5,10"
+ id="path5611"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7567)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,417.36218 5,10"
+ id="path5613"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7563)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,427.36218 5,10"
+ id="path5615"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7559)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5617"
+ d="m 220,437.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7555)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5619"
+ d="m 235,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7551)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5621"
+ d="m 235,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7547)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5623"
+ d="m 235,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7543)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5625"
+ d="m 235,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7539)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5627"
+ d="m 235,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7535)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,327.36218 5,10"
+ id="path5629"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7531)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,337.36218 5,10"
+ id="path5631"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7527)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,347.36218 5,10"
+ id="path5633"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7523)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,357.36218 5,10"
+ id="path5635"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7519)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,367.36218 5,10"
+ id="path5637"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7515)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5639"
+ d="m 235,377.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7511)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,387.36218 5,10"
+ id="path5641"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7507)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,397.36218 5,10"
+ id="path5643"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7503)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,407.36218 5,10"
+ id="path5645"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7499)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,417.36218 5,10"
+ id="path5647"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7495)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,427.36218 5,10"
+ id="path5649"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7491)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5651"
+ d="m 235,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7487)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5653"
+ d="m 235,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7483)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5655"
+ d="m 235,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7479)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5657"
+ d="m 235,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7475)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5659"
+ d="m 235,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7471)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,437.36218 5,10"
+ id="path5661"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7467)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,327.36218 5,10"
+ id="path5663"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7463)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,337.36218 5,10"
+ id="path5665"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7459)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,347.36218 5,10"
+ id="path5667"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7455)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,357.36218 5,10"
+ id="path5669"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7451)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,367.36218 5,10"
+ id="path5671"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7447)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5673"
+ d="m 250,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7443)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5675"
+ d="m 250,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7439)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5677"
+ d="m 250,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7435)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5679"
+ d="m 250,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7431)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5681"
+ d="m 250,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7427)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,377.36218 5,10"
+ id="path5683"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7423)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5685"
+ d="m 250,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7419)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5687"
+ d="m 250,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7415)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5689"
+ d="m 250,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7411)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5691"
+ d="m 250,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7407)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5693"
+ d="m 250,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7403)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,387.36218 5,10"
+ id="path5695"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7399)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,397.36218 5,10"
+ id="path5697"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7395)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,407.36218 5,10"
+ id="path5699"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7391)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,417.36218 5,10"
+ id="path5701"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7387)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,427.36218 5,10"
+ id="path5703"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7383)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5705"
+ d="m 250,437.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7379)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,327.36218 5,10"
+ id="path5707"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7375)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,337.36218 5,10"
+ id="path5709"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7371)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,347.36218 5,10"
+ id="path5711"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7367)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,357.36218 5,10"
+ id="path5713"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7363)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,367.36218 5,10"
+ id="path5715"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7359)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5717"
+ d="m 265,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7355)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5719"
+ d="m 265,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7351)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5721"
+ d="m 265,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7347)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5723"
+ d="m 265,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7343)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5725"
+ d="m 265,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7339)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,377.36218 5,10"
+ id="path5727"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7335)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5729"
+ d="m 265,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7331)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5731"
+ d="m 265,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7327)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5733"
+ d="m 265,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7323)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5735"
+ d="m 265,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7319)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5737"
+ d="m 265,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7315)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,387.36218 5,10"
+ id="path5739"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7311)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,397.36218 5,10"
+ id="path5741"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7307)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,407.36218 5,10"
+ id="path5743"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7303)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,417.36218 5,10"
+ id="path5745"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7299)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,427.36218 5,10"
+ id="path5747"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7295)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5749"
+ d="m 265,437.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7291)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5751"
+ d="m 280,327.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7287)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5753"
+ d="m 280,337.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7283)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5755"
+ d="m 280,347.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7279)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5757"
+ d="m 280,357.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7275)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5759"
+ d="m 280,367.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7271)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,327.36218 5,10"
+ id="path5761"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7267)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,337.36218 5,10"
+ id="path5763"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7263)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,347.36218 5,10"
+ id="path5765"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7259)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,357.36218 5,10"
+ id="path5767"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7255)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,367.36218 5,10"
+ id="path5769"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7251)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5771"
+ d="m 280,377.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7247)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,387.36218 5,10"
+ id="path5773"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7243)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,397.36218 5,10"
+ id="path5775"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7239)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,407.36218 5,10"
+ id="path5777"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7235)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,417.36218 5,10"
+ id="path5779"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7231)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,427.36218 5,10"
+ id="path5781"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7227)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5783"
+ d="m 280,387.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7223)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5785"
+ d="m 280,397.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7219)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5787"
+ d="m 280,407.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7215)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5789"
+ d="m 280,417.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7211)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path5791"
+ d="m 280,427.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7207)"
+ transform="translate(395,2.6171874e-6)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,437.36218 5,10"
+ id="path5793"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7203)"
+ transform="translate(395,2.6171874e-6)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7907"
+ width="115"
+ height="115"
+ x="565.5"
+ y="327.86218" />
+ <rect
+ style="fill:url(#simple3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect7909"
+ width="115"
+ height="115"
+ x="435.5"
+ y="327.86218" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path7930"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7976)"
+ transform="translate(395,5.0000026)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path7932"
+ d="m 190,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7972)"
+ transform="translate(395,5.0000026)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path7934"
+ d="m 205,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7968)"
+ transform="translate(395,5.0000026)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path7936"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7964)"
+ transform="translate(395,5.0000026)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path7938"
+ d="m 235,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7960)"
+ transform="translate(395,5.0000026)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path7940"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7956)"
+ transform="translate(395,5.0000026)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path7942"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath7952)"
+ transform="translate(395,5.0000026)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path7944"
+ d="m 280,452.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath7948)"
+ transform="translate(395,5.0000026)" />
+ <rect
+ y="457.86218"
+ x="565.5"
+ height="115"
+ width="115"
+ id="rect7980"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ y="457.86218"
+ x="435.5"
+ height="115"
+ width="115"
+ id="rect7982"
+ style="fill:url(#simple4) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <g
+ id="g9142"
+ transform="translate(395,5.0000026)">
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9134)"
+ inkscape:connector-curvature="0"
+ id="path7984"
+ d="m 175,582.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9130)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,587.36218 5,10 5,-5"
+ id="path7986"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9126)"
+ inkscape:connector-curvature="0"
+ id="path7988"
+ d="m 175,592.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9122)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,597.36218 5,10 5,-5"
+ id="path7990"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9118)"
+ inkscape:connector-curvature="0"
+ id="path7992"
+ d="m 175,602.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9114)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,607.36218 5,10 5,-5"
+ id="path7994"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9110)"
+ inkscape:connector-curvature="0"
+ id="path7996"
+ d="m 175,612.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9106)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,617.36218 5,10 5,-5"
+ id="path7998"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9102)"
+ inkscape:connector-curvature="0"
+ id="path8000"
+ d="m 175,622.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9098)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,627.36218 5,10 5,-5"
+ id="path8002"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9094)"
+ inkscape:connector-curvature="0"
+ id="path8004"
+ d="m 175,632.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9090)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,637.36218 5,10 5,-5"
+ id="path8006"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9086)"
+ inkscape:connector-curvature="0"
+ id="path8008"
+ d="m 175,642.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9082)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,647.36218 5,10 5,-5"
+ id="path8010"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9078)"
+ inkscape:connector-curvature="0"
+ id="path8012"
+ d="m 175,652.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9074)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,657.36218 5,10 5,-5"
+ id="path8014"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9070)"
+ inkscape:connector-curvature="0"
+ id="path8016"
+ d="m 175,662.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9066)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,667.36218 5,10 5,-5"
+ id="path8018"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9062)"
+ inkscape:connector-curvature="0"
+ id="path8020"
+ d="m 175,672.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9058)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,582.36218 5,10 5,-5"
+ id="path8022"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9054)"
+ inkscape:connector-curvature="0"
+ id="path8024"
+ d="m 190,587.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9050)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,592.36218 5,10 5,-5"
+ id="path8026"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9046)"
+ inkscape:connector-curvature="0"
+ id="path8028"
+ d="m 190,597.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9042)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,602.36218 5,10 5,-5"
+ id="path8030"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9038)"
+ inkscape:connector-curvature="0"
+ id="path8032"
+ d="m 190,607.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9034)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,612.36218 5,10 5,-5"
+ id="path8034"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9030)"
+ inkscape:connector-curvature="0"
+ id="path8036"
+ d="m 190,617.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9026)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,622.36218 5,10 5,-5"
+ id="path8038"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9022)"
+ inkscape:connector-curvature="0"
+ id="path8040"
+ d="m 190,627.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9018)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,632.36218 5,10 5,-5"
+ id="path8042"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9014)"
+ inkscape:connector-curvature="0"
+ id="path8044"
+ d="m 190,637.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9010)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,642.36218 5,10 5,-5"
+ id="path8046"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9006)"
+ inkscape:connector-curvature="0"
+ id="path8048"
+ d="m 190,647.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath9002)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,652.36218 5,10 5,-5"
+ id="path8050"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8998)"
+ inkscape:connector-curvature="0"
+ id="path8052"
+ d="m 190,657.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8994)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,662.36218 5,10 5,-5"
+ id="path8054"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8990)"
+ inkscape:connector-curvature="0"
+ id="path8056"
+ d="m 190,667.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8986)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,672.36218 5,10 5,-5"
+ id="path8058"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8982)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,582.36218 5,10 5,-5"
+ id="path8060"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8978)"
+ inkscape:connector-curvature="0"
+ id="path8062"
+ d="m 205,587.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8974)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,592.36218 5,10 5,-5"
+ id="path8064"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8970)"
+ inkscape:connector-curvature="0"
+ id="path8066"
+ d="m 205,597.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8966)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,602.36218 5,10 5,-5"
+ id="path8068"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8962)"
+ inkscape:connector-curvature="0"
+ id="path8070"
+ d="m 205,607.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8958)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,612.36218 5,10 5,-5"
+ id="path8072"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8954)"
+ inkscape:connector-curvature="0"
+ id="path8074"
+ d="m 205,617.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8950)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,622.36218 5,10 5,-5"
+ id="path8076"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8946)"
+ inkscape:connector-curvature="0"
+ id="path8078"
+ d="m 205,627.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8942)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,632.36218 5,10 5,-5"
+ id="path8080"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8938)"
+ inkscape:connector-curvature="0"
+ id="path8082"
+ d="m 205,637.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8934)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,642.36218 5,10 5,-5"
+ id="path8084"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8930)"
+ inkscape:connector-curvature="0"
+ id="path8086"
+ d="m 205,647.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8926)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,652.36218 5,10 5,-5"
+ id="path8088"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8922)"
+ inkscape:connector-curvature="0"
+ id="path8090"
+ d="m 205,657.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8918)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,662.36218 5,10 5,-5"
+ id="path8092"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8914)"
+ inkscape:connector-curvature="0"
+ id="path8094"
+ d="m 205,667.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8910)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,672.36218 5,10 5,-5"
+ id="path8096"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8906)"
+ inkscape:connector-curvature="0"
+ id="path8098"
+ d="m 220,582.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8902)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,587.36218 5,10 5,-5"
+ id="path8100"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8898)"
+ inkscape:connector-curvature="0"
+ id="path8102"
+ d="m 220,592.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8894)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,597.36218 5,10 5,-5"
+ id="path8104"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8890)"
+ inkscape:connector-curvature="0"
+ id="path8106"
+ d="m 220,602.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8886)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,607.36218 5,10 5,-5"
+ id="path8108"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8882)"
+ inkscape:connector-curvature="0"
+ id="path8110"
+ d="m 220,612.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8878)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,617.36218 5,10 5,-5"
+ id="path8112"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8874)"
+ inkscape:connector-curvature="0"
+ id="path8114"
+ d="m 220,622.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8870)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,627.36218 5,10 5,-5"
+ id="path8116"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8866)"
+ inkscape:connector-curvature="0"
+ id="path8118"
+ d="m 220,632.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8862)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,637.36218 5,10 5,-5"
+ id="path8120"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8858)"
+ inkscape:connector-curvature="0"
+ id="path8122"
+ d="m 220,642.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8854)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,647.36218 5,10 5,-5"
+ id="path8124"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8850)"
+ inkscape:connector-curvature="0"
+ id="path8126"
+ d="m 220,652.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8846)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,657.36218 5,10 5,-5"
+ id="path8128"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8842)"
+ inkscape:connector-curvature="0"
+ id="path8130"
+ d="m 220,662.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8838)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,667.36218 5,10 5,-5"
+ id="path8132"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8834)"
+ inkscape:connector-curvature="0"
+ id="path8134"
+ d="m 220,672.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8830)"
+ inkscape:connector-curvature="0"
+ id="path8136"
+ d="m 235,582.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8826)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,587.36218 5,10 5,-5"
+ id="path8138"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8822)"
+ inkscape:connector-curvature="0"
+ id="path8140"
+ d="m 235,592.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8818)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,597.36218 5,10 5,-5"
+ id="path8142"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8814)"
+ inkscape:connector-curvature="0"
+ id="path8144"
+ d="m 235,602.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8810)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,607.36218 5,10 5,-5"
+ id="path8146"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8806)"
+ inkscape:connector-curvature="0"
+ id="path8148"
+ d="m 235,612.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8802)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,617.36218 5,10 5,-5"
+ id="path8150"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8798)"
+ inkscape:connector-curvature="0"
+ id="path8152"
+ d="m 235,622.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8794)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,627.36218 5,10 5,-5"
+ id="path8154"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8790)"
+ inkscape:connector-curvature="0"
+ id="path8156"
+ d="m 235,632.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8786)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,637.36218 5,10 5,-5"
+ id="path8158"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8782)"
+ inkscape:connector-curvature="0"
+ id="path8160"
+ d="m 235,642.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8778)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,647.36218 5,10 5,-5"
+ id="path8162"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8774)"
+ inkscape:connector-curvature="0"
+ id="path8164"
+ d="m 235,652.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8770)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,657.36218 5,10 5,-5"
+ id="path8166"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8766)"
+ inkscape:connector-curvature="0"
+ id="path8168"
+ d="m 235,662.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8762)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,667.36218 5,10 5,-5"
+ id="path8170"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8758)"
+ inkscape:connector-curvature="0"
+ id="path8172"
+ d="m 235,672.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8754)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,582.36218 5,10 5,-5"
+ id="path8174"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8750)"
+ inkscape:connector-curvature="0"
+ id="path8176"
+ d="m 250,587.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8746)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,592.36218 5,10 5,-5"
+ id="path8178"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8742)"
+ inkscape:connector-curvature="0"
+ id="path8180"
+ d="m 250,597.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8738)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,602.36218 5,10 5,-5"
+ id="path8182"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8734)"
+ inkscape:connector-curvature="0"
+ id="path8184"
+ d="m 250,607.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8730)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,612.36218 5,10 5,-5"
+ id="path8186"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8726)"
+ inkscape:connector-curvature="0"
+ id="path8188"
+ d="m 250,617.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8722)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,622.36218 5,10 5,-5"
+ id="path8190"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8718)"
+ inkscape:connector-curvature="0"
+ id="path8192"
+ d="m 250,627.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8714)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,632.36218 5,10 5,-5"
+ id="path8194"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8710)"
+ inkscape:connector-curvature="0"
+ id="path8196"
+ d="m 250,637.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8706)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,642.36218 5,10 5,-5"
+ id="path8198"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8702)"
+ inkscape:connector-curvature="0"
+ id="path8200"
+ d="m 250,647.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8698)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,652.36218 5,10 5,-5"
+ id="path8202"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8694)"
+ inkscape:connector-curvature="0"
+ id="path8204"
+ d="m 250,657.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8690)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,662.36218 5,10 5,-5"
+ id="path8206"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8686)"
+ inkscape:connector-curvature="0"
+ id="path8208"
+ d="m 250,667.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8682)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,672.36218 5,10 5,-5"
+ id="path8210"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8678)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,582.36218 5,10 5,-5"
+ id="path8212"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8674)"
+ inkscape:connector-curvature="0"
+ id="path8214"
+ d="m 265,587.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8670)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,592.36218 5,10 5,-5"
+ id="path8216"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8666)"
+ inkscape:connector-curvature="0"
+ id="path8218"
+ d="m 265,597.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8662)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,602.36218 5,10 5,-5"
+ id="path8220"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8658)"
+ inkscape:connector-curvature="0"
+ id="path8222"
+ d="m 265,607.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8654)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,612.36218 5,10 5,-5"
+ id="path8224"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8650)"
+ inkscape:connector-curvature="0"
+ id="path8226"
+ d="m 265,617.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8646)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,622.36218 5,10 5,-5"
+ id="path8228"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8642)"
+ inkscape:connector-curvature="0"
+ id="path8230"
+ d="m 265,627.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8638)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,632.36218 5,10 5,-5"
+ id="path8232"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8634)"
+ inkscape:connector-curvature="0"
+ id="path8234"
+ d="m 265,637.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8630)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,642.36218 5,10 5,-5"
+ id="path8236"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8626)"
+ inkscape:connector-curvature="0"
+ id="path8238"
+ d="m 265,647.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8622)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,652.36218 5,10 5,-5"
+ id="path8240"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8618)"
+ inkscape:connector-curvature="0"
+ id="path8242"
+ d="m 265,657.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8614)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,662.36218 5,10 5,-5"
+ id="path8244"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8610)"
+ inkscape:connector-curvature="0"
+ id="path8246"
+ d="m 265,667.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8606)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,672.36218 5,10 5,-5"
+ id="path8248"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8602)"
+ inkscape:connector-curvature="0"
+ id="path8250"
+ d="m 280,582.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8598)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,587.36218 5,10 5,-5"
+ id="path8252"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8594)"
+ inkscape:connector-curvature="0"
+ id="path8254"
+ d="m 280,592.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8590)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,597.36218 5,10 5,-5"
+ id="path8256"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8586)"
+ inkscape:connector-curvature="0"
+ id="path8258"
+ d="m 280,602.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8582)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,607.36218 5,10 5,-5"
+ id="path8260"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8578)"
+ inkscape:connector-curvature="0"
+ id="path8262"
+ d="m 280,612.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8574)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,617.36218 5,10 5,-5"
+ id="path8264"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8570)"
+ inkscape:connector-curvature="0"
+ id="path8266"
+ d="m 280,622.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8566)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,627.36218 5,10 5,-5"
+ id="path8268"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8562)"
+ inkscape:connector-curvature="0"
+ id="path8270"
+ d="m 280,632.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8558)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,637.36218 5,10 5,-5"
+ id="path8272"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8554)"
+ inkscape:connector-curvature="0"
+ id="path8274"
+ d="m 280,642.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8550)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,647.36218 5,10 5,-5"
+ id="path8276"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8546)"
+ inkscape:connector-curvature="0"
+ id="path8278"
+ d="m 280,652.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8542)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,657.36218 5,10 5,-5"
+ id="path8280"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8538)"
+ inkscape:connector-curvature="0"
+ id="path8282"
+ d="m 280,662.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8534)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,667.36218 5,10 5,-5"
+ id="path8284"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8530)"
+ inkscape:connector-curvature="0"
+ id="path8286"
+ d="m 280,672.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8526)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,577.36218 5,10 5,-5"
+ id="path8288"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8522)"
+ inkscape:connector-curvature="0"
+ id="path8290"
+ d="m 190,577.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8518)"
+ inkscape:connector-curvature="0"
+ id="path8292"
+ d="m 205,577.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8514)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,577.36218 5,10 5,-5"
+ id="path8294"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8510)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,577.36218 5,10 5,-5"
+ id="path8296"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8506)"
+ inkscape:connector-curvature="0"
+ id="path8298"
+ d="m 250,577.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8502)"
+ inkscape:connector-curvature="0"
+ id="path8300"
+ d="m 265,577.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8498)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,577.36218 5,10 5,-5"
+ id="path8302"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8494)"
+ inkscape:connector-curvature="0"
+ id="path8304"
+ d="m 175,572.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8490)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,572.36218 5,10 5,-5"
+ id="path8306"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8486)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,572.36218 5,10 5,-5"
+ id="path8308"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8482)"
+ inkscape:connector-curvature="0"
+ id="path8310"
+ d="m 220,572.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8478)"
+ inkscape:connector-curvature="0"
+ id="path8312"
+ d="m 235,572.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8474)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,572.36218 5,10 5,-5"
+ id="path8314"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8470)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,572.36218 5,10 5,-5"
+ id="path8316"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8466)"
+ inkscape:connector-curvature="0"
+ id="path8318"
+ d="m 280,572.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8462)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,677.36218 5,10 5,-5"
+ id="path8322"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8458)"
+ inkscape:connector-curvature="0"
+ id="path8324"
+ d="m 190,677.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8454)"
+ inkscape:connector-curvature="0"
+ id="path8326"
+ d="m 205,677.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8450)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,677.36218 5,10 5,-5"
+ id="path8328"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8446)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,677.36218 5,10 5,-5"
+ id="path8330"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8442)"
+ inkscape:connector-curvature="0"
+ id="path8332"
+ d="m 250,677.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8438)"
+ inkscape:connector-curvature="0"
+ id="path8334"
+ d="m 265,677.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8434)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,677.36218 5,10 5,-5"
+ id="path8336"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8430)"
+ inkscape:connector-curvature="0"
+ id="path8338"
+ d="m 175,682.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8426)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 190,682.36218 5,10 5,-5"
+ id="path8340"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8422)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 205,682.36218 5,10 5,-5"
+ id="path8342"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8418)"
+ inkscape:connector-curvature="0"
+ id="path8344"
+ d="m 220,682.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8414)"
+ inkscape:connector-curvature="0"
+ id="path8346"
+ d="m 235,682.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8410)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 250,682.36218 5,10 5,-5"
+ id="path8348"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8406)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 265,682.36218 5,10 5,-5"
+ id="path8350"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8402)"
+ inkscape:connector-curvature="0"
+ id="path8352"
+ d="m 280,682.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8398)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 175,687.36218 5,10 5,-5"
+ id="path8354"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8394)"
+ inkscape:connector-curvature="0"
+ id="path8356"
+ d="m 190,687.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8390)"
+ inkscape:connector-curvature="0"
+ id="path8358"
+ d="m 205,687.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8386)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 220,687.36218 5,10 5,-5"
+ id="path8360"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8382)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 235,687.36218 5,10 5,-5"
+ id="path8362"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8378)"
+ inkscape:connector-curvature="0"
+ id="path8364"
+ d="m 250,687.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8374)"
+ inkscape:connector-curvature="0"
+ id="path8366"
+ d="m 265,687.36218 5,10 5,-5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ transform="translate(0,5)"
+ clip-path="url(#clipPath8370)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 280,687.36218 5,10 5,-5"
+ id="path8368"
+ inkscape:connector-curvature="0" />
+ <rect
+ y="582.86218"
+ x="170.5"
+ height="115"
+ width="115"
+ id="rect9138"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <rect
+ style="fill:url(#simple5) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9140"
+ width="115"
+ height="115"
+ x="435.5"
+ y="587.86218" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#g9142"
+ id="use9337"
+ transform="translate(0,130)"
+ width="744.09448"
+ height="1052.3622" />
+ <rect
+ y="717.86218"
+ x="435.5"
+ height="115"
+ width="115"
+ id="rect9339"
+ style="fill:url(#simple6) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 570,847.36218 5,10"
+ id="path9341"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9343"
+ d="m 570,867.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 570,887.36218 5,10"
+ id="path9345"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9347"
+ d="m 570,907.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 570,927.36218 5,10"
+ id="path9349"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9351"
+ d="m 570,947.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9355"
+ d="m 585,847.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 585,867.36218 5,10"
+ id="path9357"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9359"
+ d="m 585,887.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 585,907.36218 5,10"
+ id="path9361"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9363"
+ d="m 585,927.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 585,947.36218 5,10"
+ id="path9365"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 600,847.36218 5,10"
+ id="path9369"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9371"
+ d="m 600,867.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 600,887.36218 5,10"
+ id="path9373"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9375"
+ d="m 600,907.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 600,927.36218 5,10"
+ id="path9377"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9379"
+ d="m 600,947.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9383"
+ d="m 615,847.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 615,867.36218 5,10"
+ id="path9385"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9387"
+ d="m 615,887.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 615,907.36218 5,10"
+ id="path9389"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9391"
+ d="m 615,927.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 615,947.36218 5,10"
+ id="path9393"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 630,847.36218 5,10"
+ id="path9397"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9399"
+ d="m 630,867.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 630,887.36218 5,10"
+ id="path9401"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9403"
+ d="m 630,907.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 630,927.36218 5,10"
+ id="path9405"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9407"
+ d="m 630,947.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9411"
+ d="m 645,847.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 645,867.36218 5,10"
+ id="path9413"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9415"
+ d="m 645,887.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 645,907.36218 5,10"
+ id="path9417"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9419"
+ d="m 645,927.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 645,947.36218 5,10"
+ id="path9421"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 660,847.36218 5,10"
+ id="path9425"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9427"
+ d="m 660,867.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 660,887.36218 5,10"
+ id="path9429"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9431"
+ d="m 660,907.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 660,927.36218 5,10"
+ id="path9433"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9435"
+ d="m 660,947.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9439"
+ d="m 675,847.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 675,867.36218 5,10"
+ id="path9441"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9443"
+ d="m 675,887.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 675,907.36218 5,10"
+ id="path9445"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9447"
+ d="m 675,927.36218 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 675,947.36218 5,10"
+ id="path9449"
+ inkscape:connector-curvature="0" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9453"
+ width="115"
+ height="115"
+ x="565.5"
+ y="847.86218" />
+ <rect
+ style="fill:url(#simple7) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect9455"
+ width="115"
+ height="115"
+ x="435.5"
+ y="847.86218" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 360,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ id="path9457"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath9573)"
+ transform="translate(1153.5,14.500003)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9459"
+ d="m 375,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath9569)"
+ transform="translate(1153.5,14.500003)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 390,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ id="path9461"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath9565)"
+ transform="translate(1153.5,14.500003)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9463"
+ d="m 405,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath9561)"
+ transform="translate(1153.5,14.500003)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 420,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ id="path9465"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath9557)"
+ transform="translate(1153.5,14.500003)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9467"
+ d="m 435,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath9553)"
+ transform="translate(1153.5,14.500003)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 450,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ id="path9469"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath9549)"
+ transform="translate(1153.5,14.500003)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path9471"
+ d="m 465,62.362183 5,10 -5,10 5,10 -5,9.999997 5,10 -5,10 5,10 -5,10 5,10 -5,10 5,10 -5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath9545)"
+ transform="translate(1153.5,14.500003)" />
+ <rect
+ y="77.362183"
+ x="1509"
+ height="115"
+ width="115"
+ id="rect9577"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ y="77.362183"
+ x="1378"
+ height="115"
+ width="115"
+ id="rect9579"
+ style="fill:url(#transform1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 464.08013,373.71824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025"
+ id="path10332"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="25.91987"
+ inkscape:transform-center-y="-54.894536"
+ clip-path="url(#clipPath10442)"
+ transform="translate(988,-59.999997)" />
+ <path
+ inkscape:transform-center-y="-54.894536"
+ inkscape:transform-center-x="25.919871"
+ inkscape:connector-curvature="0"
+ id="path10352"
+ d="m 477.07051,381.21824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10438)"
+ transform="translate(988,-59.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 490.06089,388.71824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025"
+ id="path10354"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="25.919872"
+ inkscape:transform-center-y="-54.894536"
+ clip-path="url(#clipPath10434)"
+ transform="translate(988,-59.999997)" />
+ <path
+ inkscape:transform-center-y="-54.894536"
+ inkscape:transform-center-x="25.919873"
+ inkscape:connector-curvature="0"
+ id="path10356"
+ d="m 503.05127,396.21824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10430)"
+ transform="translate(988,-59.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 516.04165,403.71824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025"
+ id="path10358"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="25.919874"
+ inkscape:transform-center-y="-54.894536"
+ clip-path="url(#clipPath10426)"
+ transform="translate(988,-59.999997)" />
+ <path
+ inkscape:transform-center-y="-54.894536"
+ inkscape:transform-center-x="25.919875"
+ inkscape:connector-curvature="0"
+ id="path10360"
+ d="m 529.03203,411.21824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10422)"
+ transform="translate(988,-59.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 542.02241,418.71824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025"
+ id="path10362"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="25.919876"
+ inkscape:transform-center-y="-54.894536"
+ clip-path="url(#clipPath10418)"
+ transform="translate(988,-59.999997)" />
+ <path
+ inkscape:transform-center-y="-54.894536"
+ inkscape:transform-center-x="25.919877"
+ inkscape:connector-curvature="0"
+ id="path10364"
+ d="m 555.01279,426.21824 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10414)"
+ transform="translate(988,-59.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 568.00318,433.71824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025"
+ id="path10366"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="25.919868"
+ inkscape:transform-center-y="-54.894536"
+ clip-path="url(#clipPath10410)"
+ transform="translate(988,-59.999997)" />
+ <path
+ inkscape:transform-center-y="-54.894536"
+ inkscape:transform-center-x="25.919869"
+ inkscape:connector-curvature="0"
+ id="path10368"
+ d="m 580.99356,441.21824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10406)"
+ transform="translate(988,-59.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 593.98394,448.71824 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025"
+ id="path10370"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="25.91987"
+ inkscape:transform-center-y="-54.894536"
+ clip-path="url(#clipPath10402)"
+ transform="translate(988,-59.999997)" />
+ <path
+ inkscape:transform-center-y="-8.6602474"
+ inkscape:transform-center-x="5"
+ inkscape:connector-curvature="0"
+ id="path10376"
+ d="m 475,797.62497 9.33013,-6.16025 L 485,780.30447 494.33013,774.14421 495,762.98396 504.33013,756.8237 505,745.66345 514.33013,739.5032 515,728.34294 524.33013,722.18269 525,711.02243 534.33013,704.86218 535,693.70193 544.33013,687.54167 545,676.38142 554.33013,670.22116 555,659.06091 564.33013,652.90066 565,641.7404 574.33013,635.58015 575,624.41989"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10669)"
+ transform="translate(978,-105)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 487.99038,805.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ id="path10378"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-7.99038"
+ inkscape:transform-center-y="-16.160247"
+ clip-path="url(#clipPath10665)"
+ transform="translate(978,-105)" />
+ <path
+ inkscape:transform-center-y="-23.660247"
+ inkscape:transform-center-x="-20.98076"
+ inkscape:connector-curvature="0"
+ id="path10380"
+ d="m 500.98076,812.62497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10661)"
+ transform="translate(978,-105)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 513.97114,820.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ id="path10382"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-33.97114"
+ inkscape:transform-center-y="-31.160247"
+ clip-path="url(#clipPath10657)"
+ transform="translate(978,-105)" />
+ <path
+ inkscape:transform-center-y="-38.660247"
+ inkscape:transform-center-x="-46.96153"
+ inkscape:connector-curvature="0"
+ id="path10384"
+ d="m 526.96153,827.62497 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10653)"
+ transform="translate(978,-105)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 539.95191,835.12497 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026"
+ id="path10386"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-59.95191"
+ inkscape:transform-center-y="-46.160247"
+ clip-path="url(#clipPath10649)"
+ transform="translate(978,-105)" />
+ <path
+ inkscape:transform-center-y="-53.660247"
+ inkscape:transform-center-x="-72.94229"
+ inkscape:connector-curvature="0"
+ id="path10388"
+ d="m 552.94229,842.62497 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16026 0.66988,-11.16025 9.33012,-6.16025 0.66988,-11.16026 9.33012,-6.16025 0.66988,-11.16026"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10645)"
+ transform="translate(978,-105)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 565.93267,850.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ id="path10390"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-85.93267"
+ inkscape:transform-center-y="-61.160247"
+ clip-path="url(#clipPath10641)"
+ transform="translate(978,-105)" />
+ <path
+ inkscape:transform-center-y="-68.660247"
+ inkscape:transform-center-x="-98.92305"
+ inkscape:connector-curvature="0"
+ id="path10392"
+ d="m 578.92305,857.62497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10637)"
+ transform="translate(978,-105)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 591.91343,865.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ id="path10394"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-111.91343"
+ inkscape:transform-center-y="-76.160247"
+ clip-path="url(#clipPath10633)"
+ transform="translate(978,-105)" />
+ <path
+ inkscape:transform-center-y="-83.660247"
+ inkscape:transform-center-x="-124.90381"
+ inkscape:connector-curvature="0"
+ id="path10396"
+ d="m 604.90381,872.62497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10629)"
+ transform="translate(978,-105)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 617.89419,880.12497 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16026 0.66987,-11.16025 9.33013,-6.16025 0.66987,-11.16026 9.33013,-6.16025 0.66987,-11.16026"
+ id="path10398"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-137.89419"
+ inkscape:transform-center-y="-91.160247"
+ clip-path="url(#clipPath10625)"
+ transform="translate(978,-105)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10446"
+ width="115"
+ height="115"
+ x="1508"
+ y="207.36218" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 438.68272,550.75053 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066"
+ id="path10452"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="10.6066"
+ inkscape:transform-center-y="-17.677667"
+ clip-path="url(#clipPath10520)"
+ transform="translate(988,-125)" />
+ <path
+ inkscape:transform-center-y="-28.284267"
+ inkscape:connector-curvature="0"
+ id="path10454"
+ d="m 449.28932,561.35713 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10516)"
+ transform="translate(988,-125)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 459.89593,571.96373 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066"
+ id="path10456"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-10.606605"
+ inkscape:transform-center-y="-38.890872"
+ clip-path="url(#clipPath10512)"
+ transform="translate(988,-125)" />
+ <path
+ inkscape:transform-center-y="-49.497472"
+ inkscape:transform-center-x="-21.213205"
+ inkscape:connector-curvature="0"
+ id="path10458"
+ d="m 470.50253,582.57033 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10508)"
+ transform="translate(988,-125)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 481.10913,593.17694 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066"
+ id="path10460"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-31.81981"
+ inkscape:transform-center-y="-60.104077"
+ clip-path="url(#clipPath10504)"
+ transform="translate(988,-125)" />
+ <path
+ inkscape:transform-center-y="-70.710677"
+ inkscape:transform-center-x="-42.42641"
+ inkscape:connector-curvature="0"
+ id="path10462"
+ d="m 491.71573,603.78354 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10500)"
+ transform="translate(988,-125)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 502.32233,614.39014 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066"
+ id="path10464"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-53.03301"
+ inkscape:transform-center-y="-81.317277"
+ clip-path="url(#clipPath10496)"
+ transform="translate(988,-125)" />
+ <path
+ inkscape:transform-center-y="-91.923877"
+ inkscape:transform-center-x="-63.63961"
+ inkscape:connector-curvature="0"
+ id="path10466"
+ d="m 512.92893,624.99674 10.60661,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.60661"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10492)"
+ transform="translate(988,-125)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 523.53554,635.60334 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066"
+ id="path10468"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-74.246215"
+ inkscape:transform-center-y="-102.53048"
+ clip-path="url(#clipPath10488)"
+ transform="translate(988,-125)" />
+ <path
+ inkscape:transform-center-y="-113.13708"
+ inkscape:transform-center-x="-84.852815"
+ inkscape:connector-curvature="0"
+ id="path10470"
+ d="m 534.14214,646.20994 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.60661,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.60661 10.6066,-3.53553 3.53553,-10.6066"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10484)"
+ transform="translate(988,-125)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 544.74874,656.81655 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.6066,-3.53553 3.53554,-10.6066 10.6066,-3.53554 3.53553,-10.6066 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066"
+ id="path10472"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-95.45942"
+ inkscape:transform-center-y="-123.74369"
+ clip-path="url(#clipPath10480)"
+ transform="translate(988,-125)" />
+ <path
+ inkscape:transform-center-y="-134.35029"
+ inkscape:transform-center-x="-106.06602"
+ inkscape:connector-curvature="0"
+ id="path10474"
+ d="m 555.35534,667.42315 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53553 3.53553,-10.60661 10.60661,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066 10.6066,-3.53553 3.53553,-10.6066 10.6066,-3.53554 3.53554,-10.6066"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10476)"
+ transform="translate(988,-125)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10524"
+ width="115"
+ height="115"
+ x="1508"
+ y="337.36218" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 505,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ id="path10528"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-7.815239"
+ inkscape:transform-center-y="-60.5"
+ clip-path="url(#clipPath10582)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <path
+ inkscape:transform-center-y="-60.5"
+ inkscape:transform-center-x="-7.8261091"
+ inkscape:connector-curvature="0"
+ id="path10530"
+ d="m 520,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10578)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 535,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ id="path10532"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-7.8261097"
+ inkscape:transform-center-y="-60.5"
+ clip-path="url(#clipPath10574)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <path
+ inkscape:transform-center-y="-60.5"
+ inkscape:transform-center-x="-7.8261103"
+ inkscape:connector-curvature="0"
+ id="path10534"
+ d="m 550,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10570)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 565,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ id="path10536"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-7.8261109"
+ inkscape:transform-center-y="-60.5"
+ clip-path="url(#clipPath10566)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <path
+ inkscape:transform-center-y="-60.5"
+ inkscape:transform-center-x="-7.8261115"
+ inkscape:connector-curvature="0"
+ id="path10538"
+ d="m 580,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10562)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 595,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ id="path10540"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-7.8261121"
+ inkscape:transform-center-y="-60.5"
+ clip-path="url(#clipPath10558)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <path
+ inkscape:transform-center-y="-60.5"
+ inkscape:transform-center-x="-7.8261127"
+ inkscape:connector-curvature="0"
+ id="path10542"
+ d="m 610,707.36218 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10 5,-10 -5,-10"
+ style="fill:none;stroke:#a080ff;stroke-width:0.978945px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10554)"
+ transform="matrix(1.0434783,0,0,1,981.04348,-110)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10586"
+ width="120.00001"
+ height="115"
+ x="1508"
+ y="467.36218" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1380"
+ y="892.36218"
+ id="text10615"><tspan
+ sodipodi:role="line"
+ id="tspan10617"
+ x="1380"
+ y="892.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">Since hatchUnits=&quot;userSpaceOnUse&quot; is used</tspan><tspan
+ sodipodi:role="line"
+ x="1380"
+ y="905.69556"
+ id="tspan10619"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">the rendering will match when hatched shape</tspan><tspan
+ sodipodi:role="line"
+ x="1380"
+ y="919.02893"
+ id="tspan10621"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">is moved to the point 0,0</tspan></text>
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10673"
+ width="115"
+ height="115"
+ x="1508"
+ y="597.36218" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 503.77134,889.56737 7.41782,-8.36516 -2.24143,-10.95335 7.41781,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335"
+ id="path10731"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-0.773965"
+ inkscape:transform-center-y="-20.612607"
+ clip-path="url(#clipPath10801)"
+ transform="translate(973,-34.999997)" />
+ <path
+ inkscape:transform-center-y="-24.494892"
+ inkscape:transform-center-x="-15.262855"
+ inkscape:connector-curvature="0"
+ id="path10733"
+ d="m 518.26023,893.44966 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10797)"
+ transform="translate(973,-34.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 532.74912,897.33195 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335"
+ id="path10735"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-29.751745"
+ inkscape:transform-center-y="-28.377182"
+ clip-path="url(#clipPath10793)"
+ transform="translate(973,-34.999997)" />
+ <path
+ inkscape:transform-center-y="-32.259467"
+ inkscape:transform-center-x="-44.24063"
+ inkscape:connector-curvature="0"
+ id="path10737"
+ d="m 547.23801,901.21423 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24143,-10.95335"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10789)"
+ transform="translate(973,-34.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 561.72689,905.09652 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336"
+ id="path10739"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-58.729515"
+ inkscape:transform-center-y="-36.141752"
+ clip-path="url(#clipPath10785)"
+ transform="translate(973,-34.999997)" />
+ <path
+ inkscape:transform-center-y="-40.024037"
+ inkscape:transform-center-x="-73.218405"
+ inkscape:connector-curvature="0"
+ id="path10741"
+ d="m 576.21578,908.9788 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24143,-10.95335 7.41781,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10781)"
+ transform="translate(973,-34.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 590.70467,912.86109 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41781,-8.36516 -2.24143,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336"
+ id="path10743"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-87.707295"
+ inkscape:transform-center-y="-43.906322"
+ clip-path="url(#clipPath10777)"
+ transform="translate(973,-34.999997)" />
+ <path
+ inkscape:transform-center-y="-47.788607"
+ inkscape:transform-center-x="-102.19618"
+ inkscape:connector-curvature="0"
+ id="path10745"
+ d="m 605.19356,916.74337 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24143,-10.95335"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10773)"
+ transform="translate(973,-34.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 619.68244,920.62566 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24143,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336"
+ id="path10747"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-116.68507"
+ inkscape:transform-center-y="-51.670892"
+ clip-path="url(#clipPath10769)"
+ transform="translate(973,-34.999997)" />
+ <path
+ inkscape:transform-center-y="-55.553182"
+ inkscape:transform-center-x="-131.17395"
+ inkscape:connector-curvature="0"
+ id="path10749"
+ d="m 634.17133,924.50795 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24143,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10765)"
+ transform="translate(973,-34.999997)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="m 648.66022,928.39023 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24143,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335"
+ id="path10751"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="-145.66284"
+ inkscape:transform-center-y="-59.435467"
+ clip-path="url(#clipPath10761)"
+ transform="translate(973,-34.999997)" />
+ <path
+ inkscape:transform-center-y="-63.317752"
+ inkscape:transform-center-x="-160.15173"
+ inkscape:connector-curvature="0"
+ id="path10753"
+ d="m 663.14911,932.27252 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36517 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336 7.41782,-8.36516 -2.24144,-10.95335 7.41782,-8.36516 -2.24144,-10.95336"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ clip-path="url(#clipPath10757)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10805"
+ width="115"
+ height="115"
+ x="1508"
+ y="727.36218" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 2380.5,82.862185 V 197.86218"
+ id="path10807"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="M 2385.5,82.862185 V 197.86218"
+ id="path10809"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10811"
+ d="M 2395.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10813"
+ d="M 2400.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 2410.5,82.862185 V 197.86218"
+ id="path10815"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="M 2415.5,82.862185 V 197.86218"
+ id="path10817"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10819"
+ d="M 2425.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10821"
+ d="M 2430.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 2440.5,82.862185 V 197.86218"
+ id="path10823"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="M 2445.5,82.862185 V 197.86218"
+ id="path10825"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10827"
+ d="M 2455.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10829"
+ d="M 2460.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="M 2470.5,82.862185 V 197.86218"
+ id="path10831"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176"
+ d="M 2475.5,82.862185 V 197.86218"
+ id="path10833"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10835"
+ d="M 2485.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10837"
+ d="M 2490.5,82.862185 V 197.86218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:0.941176" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10839"
+ width="115"
+ height="115"
+ x="2375.5"
+ y="82.862183" />
+ <rect
+ style="fill:url(#multiple1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect10841"
+ width="115"
+ height="115"
+ x="2245"
+ y="82.362183" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10843"
+ d="m 1010,212.36218 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath10933)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path10845"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10929)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1025,212.36218 v 115"
+ id="path10847"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10925)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10849"
+ d="m 1030,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath10921)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10851"
+ d="m 1040,212.36218 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath10917)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1045,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path10853"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10913)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1055,212.36218 v 115"
+ id="path10855"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10909)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10857"
+ d="m 1060,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath10905)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10859"
+ d="m 1070,212.36218 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath10901)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1075,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path10861"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10897)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1085,212.36218 v 115"
+ id="path10863"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10893)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10865"
+ d="m 1090,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath10889)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path10867"
+ d="m 1100,212.36218 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath10885)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1105,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path10869"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10881)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1115,212.36218 v 115"
+ id="path10871"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath10877)"
+ transform="translate(1370.5,0.5)" />
+ <rect
+ y="212.86218"
+ x="2375.5"
+ height="115"
+ width="115"
+ id="rect10937"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ clip-path="url(#clipPath11091)"
+ inkscape:connector-curvature="0"
+ id="path11001"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ transform="translate(1370.5,130.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1010,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ id="path11003"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11087)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ transform="translate(1385.5,130.5)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path11005"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11083)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11007"
+ d="m 1025,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11079)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ clip-path="url(#clipPath11075)"
+ inkscape:connector-curvature="0"
+ id="path11009"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ transform="translate(1400.5,130.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1040,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ id="path11011"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11071)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ transform="translate(1415.5,130.5)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path11013"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11067)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11015"
+ d="m 1055,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11063)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ clip-path="url(#clipPath11059)"
+ inkscape:connector-curvature="0"
+ id="path11017"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ transform="translate(1430.5,130.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1070,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ id="path11019"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11055)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ transform="translate(1445.5,130.5)"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ id="path11021"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11051)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11023"
+ d="m 1085,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11047)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ clip-path="url(#clipPath11043)"
+ inkscape:connector-curvature="0"
+ id="path11025"
+ d="m 1015,212.36218 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10 h -5 l 5,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ transform="translate(1460.5,130.5)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1100,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ id="path11027"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11039)"
+ transform="translate(1370.5,0.5)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11031"
+ d="m 1115,342.36218 5,17 h -5 l 5,17 h -6 l 6,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17 h -5 l 5,17"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11035)"
+ transform="translate(1370.5,0.5)" />
+ <rect
+ y="342.86218"
+ x="2375.5"
+ height="115"
+ width="115"
+ id="rect11095"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1"
+ d="m 570,1132.3622 v 115"
+ id="path11097"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11099"
+ d="m 585,1132.3622 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1"
+ d="m 600,1132.3622 v 115"
+ id="path11101"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11103"
+ d="m 615,1132.3622 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1"
+ d="m 630,1132.3622 v 115"
+ id="path11105"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11107"
+ d="m 645,1132.3622 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1"
+ d="m 660,1132.3622 v 115"
+ id="path11109"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11111"
+ d="m 675,1132.3622 v 115"
+ style="fill:none;stroke:#a080ff;stroke-width:5px;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:10, 4, 2, 4;stroke-opacity:1" />
+ <rect
+ y="1132.3622"
+ x="565.00018"
+ height="115"
+ width="115"
+ id="rect11113"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1510.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11115"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11117"
+ d="m 1525.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1540.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11119"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11121"
+ d="m 1555.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1570.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11123"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11125"
+ d="m 1585.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1600.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11127"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11129"
+ d="m 1615.5,1132.8622 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11133"
+ width="115"
+ height="115"
+ x="1505.5002"
+ y="1132.8622" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11135"
+ d="m 1485,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11183)"
+ transform="translate(19.5,1175)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1500,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11137"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11179)"
+ transform="translate(19.5,1175)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11139"
+ d="m 1515,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11175)"
+ transform="translate(19.5,1175)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1530,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11141"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11171)"
+ transform="translate(19.5,1175)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11143"
+ d="m 1545,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11167)"
+ transform="translate(19.5,1175)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1560,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11145"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11163)"
+ transform="translate(19.5,1175)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11147"
+ d="m 1575,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ clip-path="url(#clipPath11159)"
+ transform="translate(19.5,1175)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1590,217.36218 5,5 -10,10 5,5 5,5 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10 10,10 -10,10"
+ id="path11149"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11155)"
+ transform="translate(19.5,1175)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11187"
+ width="115"
+ height="115"
+ x="1504.5002"
+ y="1392.3622" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1505.5,1262.8622 5,5 -5,5"
+ id="path11189"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11191"
+ d="m 1505.5,1282.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1505.5,1302.8622 5,5 -5,5"
+ id="path11193"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11195"
+ d="m 1505.5,1322.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1505.5,1342.8622 5,5 -5,5"
+ id="path11197"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11199"
+ d="m 1505.5,1362.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11203"
+ d="m 1520.5,1262.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1520.5,1282.8622 5,5 -5,5"
+ id="path11205"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11207"
+ d="m 1520.5,1302.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1520.5,1322.8622 5,5 -5,5"
+ id="path11209"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11211"
+ d="m 1520.5,1342.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1520.5,1362.8622 5,5 -5,5"
+ id="path11213"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1535.5,1262.8622 5,5 -5,5"
+ id="path11217"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11219"
+ d="m 1535.5,1282.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1535.5,1302.8622 5,5 -5,5"
+ id="path11221"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11223"
+ d="m 1535.5,1322.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1535.5,1342.8622 5,5 -5,5"
+ id="path11225"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11227"
+ d="m 1535.5,1362.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11231"
+ d="m 1550.5,1262.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1550.5,1282.8622 5,5 -5,5"
+ id="path11233"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11235"
+ d="m 1550.5,1302.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1550.5,1322.8622 5,5 -5,5"
+ id="path11237"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11239"
+ d="m 1550.5,1342.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1550.5,1362.8622 5,5 -5,5"
+ id="path11241"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1565.5,1262.8622 5,5 -5,5"
+ id="path11245"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11247"
+ d="m 1565.5,1282.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1565.5,1302.8622 5,5 -5,5"
+ id="path11249"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11251"
+ d="m 1565.5,1322.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1565.5,1342.8622 5,5 -5,5"
+ id="path11253"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11255"
+ d="m 1565.5,1362.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11259"
+ d="m 1580.5,1262.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1580.5,1282.8622 5,5 -5,5"
+ id="path11261"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11263"
+ d="m 1580.5,1302.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1580.5,1322.8622 5,5 -5,5"
+ id="path11265"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11267"
+ d="m 1580.5,1342.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1580.5,1362.8622 5,5 -5,5"
+ id="path11269"
+ inkscape:connector-curvature="0" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1595.5,1262.8622 5,5 -5,5"
+ id="path11273"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11275"
+ d="m 1595.5,1282.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1595.5,1302.8622 5,5 -5,5"
+ id="path11277"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11279"
+ d="m 1595.5,1322.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1595.5,1342.8622 5,5 -5,5"
+ id="path11281"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11283"
+ d="m 1595.5,1362.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11287"
+ d="m 1610.5,1262.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1610.5,1282.8622 5,5 -5,5"
+ id="path11289"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11291"
+ d="m 1610.5,1302.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1610.5,1322.8622 5,5 -5,5"
+ id="path11293"
+ inkscape:connector-curvature="0" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11295"
+ d="m 1610.5,1342.8622 5,5 -5,5"
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 1610.5,1362.8622 5,5 -5,5"
+ id="path11297"
+ inkscape:connector-curvature="0" />
+ <rect
+ y="1262.8622"
+ x="1505.5002"
+ height="115"
+ width="115"
+ id="rect11315"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <g
+ id="g11321"
+ transform="translate(2194,540)">
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4366)"
+ inkscape:connector-curvature="0"
+ id="path11323"
+ d="M 180,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4362)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 195,42.362183 V 192.36218"
+ id="path11325"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4358)"
+ inkscape:connector-curvature="0"
+ id="path11327"
+ d="M 210,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4354)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 225,42.362183 V 192.36218"
+ id="path11329"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4350)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 240,42.362183 V 192.36218"
+ id="path11331"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4346)"
+ inkscape:connector-curvature="0"
+ id="path11333"
+ d="M 255,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4342)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 270,42.362183 V 192.36218"
+ id="path11335"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4338)"
+ inkscape:connector-curvature="0"
+ id="path11337"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="62.362183"
+ x="181"
+ height="115"
+ width="115"
+ id="rect11339"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <g
+ transform="translate(2194,540)"
+ id="g11341">
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 180,42.362183 V 192.36218"
+ id="path11343"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4366)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11345"
+ d="M 195,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4362)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 210,42.362183 V 192.36218"
+ id="path11347"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4358)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11349"
+ d="M 225,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4354)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11351"
+ d="M 240,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4350)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 255,42.362183 V 192.36218"
+ id="path11353"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4346)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11355"
+ d="M 270,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4342)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 285,42.362183 V 192.36218"
+ id="path11357"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4338)"
+ transform="translate(6,20)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11359"
+ width="115"
+ height="115"
+ x="181"
+ y="62.362183" />
+ </g>
+ <g
+ transform="translate(2194,670)"
+ id="g11361">
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 180,42.362183 V 192.36218"
+ id="path11363"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4366)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11365"
+ d="M 195,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4362)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 210,42.362183 V 192.36218"
+ id="path11367"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4358)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11369"
+ d="M 225,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4354)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11371"
+ d="M 240,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4350)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 255,42.362183 V 192.36218"
+ id="path11373"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4346)"
+ transform="translate(6,20)" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11375"
+ d="M 270,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath4342)"
+ transform="translate(6,20)" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 285,42.362183 V 192.36218"
+ id="path11377"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath4338)"
+ transform="translate(6,20)" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11379"
+ width="115"
+ height="115"
+ x="181"
+ y="62.362183" />
+ </g>
+ <g
+ id="g11381"
+ transform="translate(2194,670)">
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4366)"
+ inkscape:connector-curvature="0"
+ id="path11383"
+ d="M 180,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4362)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 195,42.362183 V 192.36218"
+ id="path11385"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4358)"
+ inkscape:connector-curvature="0"
+ id="path11387"
+ d="M 210,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4354)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 225,42.362183 V 192.36218"
+ id="path11389"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4350)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 240,42.362183 V 192.36218"
+ id="path11391"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4346)"
+ inkscape:connector-curvature="0"
+ id="path11393"
+ d="M 255,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4342)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 270,42.362183 V 192.36218"
+ id="path11395"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4338)"
+ inkscape:connector-curvature="0"
+ id="path11397"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="62.362183"
+ x="181"
+ height="115"
+ width="115"
+ id="rect11399"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <path
+ inkscape:connector-curvature="0"
+ id="path11409"
+ d="M 225,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath11528)"
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)"
+ inkscape:transform-center-x="28.034277"
+ inkscape:transform-center-y="-38.122284" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11411"
+ d="M 240,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath11524)"
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)"
+ inkscape:transform-center-x="20.444614"
+ inkscape:transform-center-y="-45.711947" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 255,42.362183 V 192.36218"
+ id="path11413"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11520)"
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)"
+ inkscape:transform-center-x="9.8380123"
+ inkscape:transform-center-y="-51.015248" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path11415"
+ d="M 270,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ clip-path="url(#clipPath11516)"
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)"
+ inkscape:transform-center-x="-0.76858941"
+ inkscape:transform-center-y="-56.318549" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 285,42.362183 V 192.36218"
+ id="path11417"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11512)"
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2332.4597,594.7755)"
+ inkscape:transform-center-x="-11.375191"
+ inkscape:transform-center-y="-61.62185" />
+ <path
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2343.0663,605.3821)"
+ clip-path="url(#clipPath11508)"
+ inkscape:connector-curvature="0"
+ id="path11450"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:transform-center-x="-21.981791"
+ inkscape:transform-center-y="-72.22845" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 285,42.362183 V 192.36218"
+ id="path11452"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11504)"
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2353.6729,615.9887)"
+ inkscape:transform-center-x="-32.588391"
+ inkscape:transform-center-y="-82.83505" />
+ <path
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2364.2795,626.5953)"
+ clip-path="url(#clipPath11500)"
+ inkscape:connector-curvature="0"
+ id="path11454"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:transform-center-x="-43.194991"
+ inkscape:transform-center-y="-93.44165" />
+ <path
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 285,42.362183 V 192.36218"
+ id="path11456"
+ inkscape:connector-curvature="0"
+ clip-path="url(#clipPath11496)"
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2374.8861,637.2019)"
+ inkscape:transform-center-x="-53.801591"
+ inkscape:transform-center-y="-104.04825" />
+ <path
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2385.4927,647.8085)"
+ clip-path="url(#clipPath11492)"
+ inkscape:connector-curvature="0"
+ id="path11458"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:transform-center-x="-64.408191"
+ inkscape:transform-center-y="-114.65485" />
+ <path
+ transform="matrix(0.70710678,0.70710678,-1.1682634,1.1682634,2396.0993,658.41511)"
+ clip-path="url(#clipPath11488)"
+ inkscape:connector-curvature="0"
+ id="path11462"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:0.777987;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ inkscape:transform-center-x="-75.014791"
+ inkscape:transform-center-y="-125.26146" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11532"
+ width="115"
+ height="115"
+ x="2375.5"
+ y="862.86218" />
+ <rect
+ y="1132.3622"
+ x="2370"
+ height="115"
+ width="115"
+ id="rect11534"
+ style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11536"
+ width="115"
+ height="115"
+ x="2370"
+ y="1262.3622" />
+ <rect
+ y="1392.3622"
+ x="2370"
+ height="115"
+ width="115"
+ id="rect11538"
+ style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:#00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11540"
+ width="115"
+ height="115"
+ x="2370"
+ y="1522.3622" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="52.480652"
+ y="112.36218"
+ id="text11542"><tspan
+ sodipodi:role="line"
+ id="tspan11544"
+ x="52.480652"
+ y="112.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;simple1&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="125.69556"
+ id="tspan11546"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; stroke-width=&quot;2&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="139.02893"
+ id="tspan11548"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="52.480652"
+ y="242.36218"
+ id="text11550"><tspan
+ sodipodi:role="line"
+ id="tspan11552"
+ x="52.480652"
+ y="242.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;simple2&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="255.69556"
+ id="tspan11554"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;15&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="269.02893"
+ id="tspan11556"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="52.480652"
+ y="372.36218"
+ id="text11558"><tspan
+ sodipodi:role="line"
+ id="tspan11560"
+ x="52.480652"
+ y="372.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;simple3&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="385.69556"
+ id="tspan11562"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;M 0,0 5,10&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="399.02893"
+ id="tspan11564"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="52.480652"
+ y="502.36218"
+ id="text11566"><tspan
+ sodipodi:role="line"
+ id="tspan11568"
+ x="52.480652"
+ y="502.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;simple4&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="515.69556"
+ id="tspan11570"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;L 0,0 5,10&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="529.02893"
+ id="tspan11572"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="52.480652"
+ y="632.36218"
+ id="text11574"><tspan
+ sodipodi:role="line"
+ id="tspan11576"
+ x="52.480652"
+ y="632.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;simple5&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="645.69556"
+ id="tspan11578"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;M 0,0 5,10 10,5&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="659.02893"
+ id="tspan11580"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="52.480652"
+ y="762.36218"
+ id="text11582"><tspan
+ sodipodi:role="line"
+ id="tspan11584"
+ x="52.480652"
+ y="762.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;simple6&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="775.69556"
+ id="tspan11586"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;m 0,0 5,10 5,-5&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="789.02893"
+ id="tspan11588"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="52.480652"
+ y="892.36218"
+ id="text11590"><tspan
+ sodipodi:role="line"
+ id="tspan11592"
+ x="52.480652"
+ y="892.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;simple7&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="905.69556"
+ id="tspan11594"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;M 0,0 5,10 M 5,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="919.02893"
+ id="tspan11596"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="52.480652"
+ y="932.3623"
+ id="tspan11598"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text>
+ <g
+ id="g11600"
+ transform="translate(1324.4998,1460.5)">
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4366)"
+ inkscape:connector-curvature="0"
+ id="path11602"
+ d="M 180,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4362)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 195,42.362183 V 192.36218"
+ id="path11604"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4358)"
+ inkscape:connector-curvature="0"
+ id="path11606"
+ d="M 210,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4354)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 225,42.362183 V 192.36218"
+ id="path11608"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4350)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 240,42.362183 V 192.36218"
+ id="path11610"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4346)"
+ inkscape:connector-curvature="0"
+ id="path11612"
+ d="M 255,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4342)"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 270,42.362183 V 192.36218"
+ id="path11614"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(6,20)"
+ clip-path="url(#clipPath4338)"
+ inkscape:connector-curvature="0"
+ id="path11616"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#a080ff;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="62.362183"
+ x="181"
+ height="115"
+ width="115"
+ id="rect11618"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4366)"
+ inkscape:connector-curvature="0"
+ id="path11622"
+ d="M 180,42.362183 V 192.36218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4362)"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 195,42.362183 V 192.36218"
+ id="path11624"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4358)"
+ inkscape:connector-curvature="0"
+ id="path11626"
+ d="M 210,42.362183 V 192.36218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4354)"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 225,42.362183 V 192.36218"
+ id="path11628"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4350)"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 240,42.362183 V 192.36218"
+ id="path11630"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4346)"
+ inkscape:connector-curvature="0"
+ id="path11632"
+ d="M 255,42.362183 V 192.36218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4342)"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 270,42.362183 V 192.36218"
+ id="path11634"
+ inkscape:connector-curvature="0" />
+ <path
+ transform="translate(1330.4998,1480.5)"
+ clip-path="url(#clipPath4338)"
+ inkscape:connector-curvature="0"
+ id="path11636"
+ d="M 285,42.362183 V 192.36218"
+ style="fill:none;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="1522.8622"
+ x="1505.5"
+ height="115"
+ width="115"
+ id="rect11638"
+ style="fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#transform2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11649"
+ width="115"
+ height="115"
+ x="1378"
+ y="207.36218" />
+ <rect
+ y="337.36218"
+ x="1378"
+ height="115"
+ width="115"
+ id="rect11651"
+ style="fill:url(#transform4) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#transform7) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11653"
+ width="115"
+ height="115"
+ x="1378"
+ y="467.36218" />
+ <rect
+ y="597.36218"
+ x="1378"
+ height="115"
+ width="115"
+ id="rect11655"
+ style="fill:url(#transform8) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#transform9) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11657"
+ width="115"
+ height="115"
+ x="1378"
+ y="727.36218" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="820"
+ y="112.36218"
+ id="text11659"><tspan
+ sodipodi:role="line"
+ id="tspan11661"
+ x="820"
+ y="112.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;transform1&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="125.69556"
+ id="tspan11663"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;L 0,0 5,10 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="139.02893"
+ id="tspan11665"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="820"
+ y="240.36218"
+ id="text11667"><tspan
+ sodipodi:role="line"
+ id="tspan11669"
+ x="820"
+ y="240.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;transform2&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot; rotate=&quot;30&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="253.69556"
+ id="tspan11671"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="267.02893"
+ id="tspan11673"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="280.3623"
+ id="tspan11675"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="820"
+ y="368.36218"
+ id="text11677"><tspan
+ sodipodi:role="line"
+ id="tspan11679"
+ x="820"
+ y="368.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;transform4&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot; rotate=&quot;45&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="381.69556"
+ id="tspan11681"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="395.02893"
+ id="tspan11683"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="820"
+ y="496.36218"
+ id="text11685"><tspan
+ sodipodi:role="line"
+ id="tspan11687"
+ x="820"
+ y="496.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;transform7&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot; x=&quot;-5&quot; y=&quot;-10&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="509.69556"
+ id="tspan11689"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="523.02893"
+ id="tspan11691"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="820"
+ y="624.36218"
+ id="text11693"><tspan
+ sodipodi:role="line"
+ id="tspan11695"
+ x="820"
+ y="624.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;transform8&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot; x=&quot;-5&quot; y=&quot;-10&quot; rotate=&quot;30&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="637.69556"
+ id="tspan11697"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="651.02893"
+ id="tspan11699"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="820"
+ y="752.36218"
+ id="text11701"><tspan
+ sodipodi:role="line"
+ x="820"
+ y="752.36218"
+ id="tspan11707"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;transform9&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot; rotate=&quot;30&quot; x=&quot;-5&quot; y=&quot;-10&quot; </tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="765.69556"
+ id="tspan11718"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">hatchTransform=&quot;matrix(0.96592583,-0.25881905,0.25881905,0.96592583,-8.4757068,43.273395)&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="779.02893"
+ id="tspan11714"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="820"
+ y="792.3623"
+ id="tspan11716"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <rect
+ y="212.36218"
+ x="2245"
+ height="115"
+ width="115"
+ id="rect11720"
+ style="fill:url(#multiple2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#multiple3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11722"
+ width="115"
+ height="115"
+ x="2245"
+ y="342.36218" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1804"
+ y="120.46635"
+ id="text11724"><tspan
+ sodipodi:role="line"
+ id="tspan11726"
+ x="1804"
+ y="120.46635"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;multiple1&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1804"
+ y="133.79973"
+ id="tspan11728"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1804"
+ y="147.1331"
+ id="tspan11730"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#32ff3f&quot; offset=&quot;10&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1804"
+ y="160.46648"
+ id="tspan11732"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1804"
+ y="250.46635"
+ id="text11734"><tspan
+ sodipodi:role="line"
+ id="tspan11736"
+ x="1804"
+ y="250.46635"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;multiple2&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1804"
+ y="263.79974"
+ id="tspan11738"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1804"
+ y="277.13312"
+ id="tspan11740"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1804"
+ y="290.46649"
+ id="tspan11742"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1814"
+ y="380.46634"
+ id="text11744"><tspan
+ sodipodi:role="line"
+ id="tspan11746"
+ x="1814"
+ y="380.46634"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;multiple3&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1814"
+ y="393.79971"
+ id="tspan11748"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;L 0,0 5,17&quot; /&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1814"
+ y="407.13309"
+ id="tspan11750"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1814"
+ y="420.46646"
+ id="tspan11752"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <rect
+ y="602.36218"
+ x="2245"
+ height="115"
+ width="115"
+ id="rect11754"
+ style="fill:url(#ref1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#ref2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11756"
+ width="115"
+ height="115"
+ x="2245"
+ y="732.36218" />
+ <rect
+ y="862.36218"
+ x="2245"
+ height="115"
+ width="115"
+ id="rect11758"
+ style="fill:url(#ref3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1834"
+ y="640.46637"
+ id="text11760"><tspan
+ sodipodi:role="line"
+ id="tspan11762"
+ x="1834"
+ y="640.46637"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;ref1&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1834"
+ y="653.79974"
+ id="tspan11764"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1834"
+ y="667.13312"
+ id="tspan11766"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1834"
+ y="752.36218"
+ id="text11768"><tspan
+ sodipodi:role="line"
+ id="tspan11770"
+ x="1834"
+ y="752.36218"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;ref2&quot; xlink:href=&quot;#ref1&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1834"
+ y="765.69556"
+ id="tspan11772"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1832.8698"
+ y="900.46637"
+ id="text11774"><tspan
+ sodipodi:role="line"
+ id="tspan11776"
+ x="1832.8698"
+ y="900.46637"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;ref3&quot; xlink:href=&quot;#ref1&quot; pitch=&quot;45&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1832.8698"
+ y="913.79974"
+ id="tspan11778"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <rect
+ y="1132.3622"
+ x="435"
+ height="115"
+ width="115"
+ id="rect11780"
+ style="fill:url(#stroke1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="60"
+ y="1172.3622"
+ id="text11782"><tspan
+ sodipodi:role="line"
+ id="tspan11784"
+ x="60"
+ y="1172.3622"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;stroke1&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="60"
+ y="1185.6956"
+ id="tspan11786"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; stroke-width=&quot;5&quot; </tspan><tspan
+ sodipodi:role="line"
+ x="60"
+ y="1199.0289"
+ id="tspan11792"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> stroke-dasharray=&quot;10 4 2 4&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="60"
+ y="1212.3623"
+ id="tspan11788"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="60"
+ y="1225.6957"
+ id="tspan11790"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text>
+ <rect
+ y="1132.3622"
+ x="1375"
+ height="115"
+ width="115"
+ id="rect11794"
+ style="fill:url(#overflow1) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#overflow2) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11796"
+ width="115"
+ height="115"
+ x="1375"
+ y="1262.3622" />
+ <rect
+ y="1392.3622"
+ x="1375"
+ height="115"
+ width="115"
+ id="rect11798"
+ style="fill:url(#overflow3) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#overflow4) #ff0000;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11800"
+ width="115"
+ height="115"
+ x="1375"
+ y="1522.3622" />
+ <rect
+ style="fill:url(#degenerate1) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11802"
+ width="115"
+ height="115"
+ x="2235"
+ y="1132.3622" />
+ <rect
+ y="1262.3622"
+ x="2235"
+ height="115"
+ width="115"
+ id="rect11804"
+ style="fill:url(#degenerate2) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ style="fill:url(#degenerate3) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11806"
+ width="115"
+ height="115"
+ x="2235"
+ y="1392.3622" />
+ <rect
+ y="1522.3622"
+ x="2235"
+ height="115"
+ width="115"
+ id="rect11808"
+ style="fill:url(#degenerate4) #00ff00;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="870"
+ y="1160.4663"
+ id="text11810"><tspan
+ sodipodi:role="line"
+ id="tspan11812"
+ x="870"
+ y="1160.4663"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;overflow1&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ rotate="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0"
+ sodipodi:role="line"
+ x="870"
+ y="1173.7997"
+ id="tspan11814"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;5&quot; d=&quot;L 0,0 5,5 -5,15, 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="870"
+ y="1187.1331"
+ id="tspan11816"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="860"
+ y="1300.4663"
+ id="text11818"><tspan
+ sodipodi:role="line"
+ id="tspan11820"
+ x="860"
+ y="1300.4663"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;overflow2&quot; style=&quot;overflow:hidden&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1313.7997"
+ id="tspan11822"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;0&quot; d=&quot;L 0,0 5,5 -5,15, 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1327.1331"
+ id="tspan11824"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="860"
+ y="1422.3622"
+ id="text11826"><tspan
+ sodipodi:role="line"
+ id="tspan11828"
+ x="860"
+ y="1422.3622"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;overflow3&quot; style=&quot;overflow:visible&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1435.6956"
+ id="tspan11830"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;0&quot; d=&quot;L 0,0 5,5 -5,15, 0,20&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1449.0289"
+ id="tspan11832"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1462.3623"
+ id="tspan11834"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> </tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="860"
+ y="1561.3726"
+ id="text11836"><tspan
+ sodipodi:role="line"
+ id="tspan11838"
+ x="860"
+ y="1561.3726"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;overflow4&quot; style=&quot;overflow:visible&quot; hatchUnits=&quot;userSpaceOnUse&quot; pitch=&quot;15&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1574.7059"
+ id="tspan11840"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#32ff3f&quot; offset=&quot;5&quot; &gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1588.0393"
+ id="tspan11842"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#ff0000&quot; offset=&quot;20&quot; &gt;</tspan><tspan
+ sodipodi:role="line"
+ x="860"
+ y="1601.3727"
+ id="tspan11844"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1849.0837"
+ y="1191.0269"
+ id="text11846"><tspan
+ sodipodi:role="line"
+ id="tspan11848"
+ x="1849.0837"
+ y="1191.0269"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;degenerate1&quot; pitch=&quot;45&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1849.0837"
+ y="1204.3602"
+ id="tspan11850"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1840"
+ y="1312.3622"
+ id="text11852"><tspan
+ sodipodi:role="line"
+ id="tspan11854"
+ x="1840"
+ y="1312.3622"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;degenerate2&quot; xlink:href=&quot;#nonexisting&quot; pitch=&quot;45&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1840"
+ y="1325.6956"
+ id="tspan11856"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1840"
+ y="1440.4663"
+ id="text11858"><tspan
+ sodipodi:role="line"
+ id="tspan11860"
+ x="1840"
+ y="1440.4663"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;degenerate3&quot; pitch=&quot;30&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1840"
+ y="1453.7997"
+ id="tspan11862"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10 0,15&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1840"
+ y="1467.1331"
+ id="tspan11864"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1840"
+ y="1572.3622"
+ id="text11866"><tspan
+ sodipodi:role="line"
+ id="tspan11868"
+ x="1840"
+ y="1572.3622"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;hatch id=&quot;degenerate4&quot; pitch=&quot;30&quot;&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1840"
+ y="1585.6956"
+ id="tspan11870"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif"> &lt;hatchPath stroke=&quot;#a080ff&quot; offset=&quot;10&quot; d=&quot;L 0,0 5,10 -5,15&quot;/&gt;</tspan><tspan
+ sodipodi:role="line"
+ x="1840"
+ y="1599.0289"
+ id="tspan11872"
+ style="font-size:10.6667px;line-height:1.25;font-family:sans-serif">&lt;/hatch&gt;</tspan></text>
+ <text
+ id="text11874"
+ y="30.596558"
+ x="818.41797"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ xml:space="preserve"><tspan
+ style="font-size:24px;line-height:1.25;font-family:sans-serif"
+ y="30.596558"
+ x="818.41797"
+ id="tspan11876"
+ sodipodi:role="line">Hatch transforms</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1817.6445"
+ y="52.022339"
+ id="text11878"><tspan
+ sodipodi:role="line"
+ id="tspan11880"
+ x="1817.6445"
+ y="52.022339"
+ style="font-size:24px;line-height:1.25;font-family:sans-serif">Multiple hatch paths</tspan></text>
+ <text
+ id="text11882"
+ y="547.37"
+ x="1827.6445"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ xml:space="preserve"><tspan
+ style="font-size:24px;line-height:1.25;font-family:sans-serif"
+ y="547.37"
+ x="1827.6445"
+ id="tspan11884"
+ sodipodi:role="line">Hatch linking</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="64.304688"
+ y="1090.5966"
+ id="text11886"><tspan
+ sodipodi:role="line"
+ id="tspan11888"
+ x="64.304688"
+ y="1090.5966"
+ style="font-size:24px;line-height:1.25;font-family:sans-serif">Stroke style</tspan></text>
+ <text
+ id="text11890"
+ y="1090.5966"
+ x="884.30469"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ xml:space="preserve"><tspan
+ style="font-size:24px;line-height:1.25;font-family:sans-serif"
+ y="1090.5966"
+ x="884.30469"
+ id="tspan11892"
+ sodipodi:role="line">Overflow property</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none"
+ x="1844.3047"
+ y="1090.5966"
+ id="text11894"><tspan
+ sodipodi:role="line"
+ id="tspan11896"
+ x="1844.3047"
+ y="1090.5966"
+ style="font-size:24px;line-height:1.25;font-family:sans-serif">Degenerate cases</tspan></text>
+ <rect
+ style="fill:url(#linearGradient11916);fill-opacity:1;stroke:#32ff3f;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect11898"
+ width="300"
+ height="100"
+ x="180"
+ y="-267.63782" />
+ </g>
+ <script
+ xlink:href="../hatch.js"
+ type="text/javascript"
+ id="script1986" />
+</svg>
diff --git a/src/extension/internal/polyfill/mesh.js b/src/extension/internal/polyfill/mesh.js
new file mode 100644
index 0000000..bcce389
--- /dev/null
+++ b/src/extension/internal/polyfill/mesh.js
@@ -0,0 +1,1192 @@
+// SPDX-License-Identifier: CC0
+/** @file
+ * Use Canvas to render a mesh gradient, passing the rendering to an image via a data stream.
+ *//*
+ * Authors:
+ * - Tavmjong Bah 2018
+ * - Valentin Ionita (2019)
+ * License: CC0 / Public Domain
+ */
+
+(function () {
+ // Name spaces -----------------------------------
+ const svgNS = 'http://www.w3.org/2000/svg';
+ const xlinkNS = 'http://www.w3.org/1999/xlink';
+ const xhtmlNS = 'http://www.w3.org/1999/xhtml';
+ /*
+ * Maximum threshold for Bezier step size
+ * Larger values leave holes, smaller take longer to render.
+ */
+ const maxBezierStep = 2.0;
+
+ // Test if mesh gradients are supported.
+ if (document.createElementNS(svgNS, 'meshgradient').x) {
+ return;
+ }
+
+ /*
+ * Utility functions -----------------------------
+ */
+ // Split Bezier using de Casteljau's method.
+ const splitBezier = (p0, p1, p2, p3) => {
+ let tmp = new Point((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5);
+ let p01 = new Point((p0.x + p1.x) * 0.5, (p0.y + p1.y) * 0.5);
+ let p12 = new Point((p2.x + p3.x) * 0.5, (p2.y + p3.y) * 0.5);
+ let p02 = new Point((tmp.x + p01.x) * 0.5, (tmp.y + p01.y) * 0.5);
+ let p11 = new Point((tmp.x + p12.x) * 0.5, (tmp.y + p12.y) * 0.5);
+ let p03 = new Point((p02.x + p11.x) * 0.5, (p02.y + p11.y) * 0.5);
+
+ return ([
+ [p0, p01, p02, p03],
+ [p03, p11, p12, p3]
+ ]);
+ };
+
+ // See Cairo: cairo-mesh-pattern-rasterizer.c
+ const bezierStepsSquared = (points) => {
+ let tmp0 = points[0].distSquared(points[1]);
+ let tmp1 = points[2].distSquared(points[3]);
+ let tmp2 = points[0].distSquared(points[2]) * 0.25;
+ let tmp3 = points[1].distSquared(points[3]) * 0.25;
+
+ let max1 = tmp0 > tmp1 ? tmp0 : tmp1;
+
+ let max2 = tmp2 > tmp3 ? tmp2 : tmp3;
+
+ let max = max1 > max2 ? max1 : max2;
+
+ return max * 18;
+ };
+
+ // Euclidean distance
+ const distance = (p0, p1) => Math.sqrt(p0.distSquared(p1));
+
+ // Weighted average to find Bezier points for linear sides.
+ const wAvg = (p0, p1) => p0.scale(2.0 / 3.0).add(p1.scale(1.0 / 3.0));
+
+ // Browsers return a string rather than a transform list for gradientTransform!
+ const parseTransform = (t) => {
+ let affine = new Affine();
+ let trans, scale, radian, tan, skewx, skewy, rotate;
+ let transforms = t.match(/(\w+\(\s*[^)]+\))+/g);
+
+ transforms.forEach((i) => {
+ let c = i.match(/[\w.-]+/g);
+ let type = c.shift();
+
+ switch (type) {
+ case 'translate':
+ if (c.length === 2) {
+ trans = new Affine(1, 0, 0, 1, c[0], c[1]);
+ } else {
+ console.error('mesh.js: translate does not have 2 arguments!');
+ trans = new Affine(1, 0, 0, 1, 0, 0);
+ }
+ affine = affine.append(trans);
+ break;
+
+ case 'scale':
+ if (c.length === 1) {
+ scale = new Affine(c[0], 0, 0, c[0], 0, 0);
+ } else if (c.length === 2) {
+ scale = new Affine(c[0], 0, 0, c[1], 0, 0);
+ } else {
+ console.error('mesh.js: scale does not have 1 or 2 arguments!');
+ scale = new Affine(1, 0, 0, 1, 0, 0);
+ }
+ affine = affine.append(scale);
+ break;
+
+ case 'rotate':
+ if (c.length === 3) {
+ trans = new Affine(1, 0, 0, 1, c[1], c[2]);
+ affine = affine.append(trans);
+ }
+ if (c[0]) {
+ radian = c[0] * Math.PI / 180.0;
+ let cos = Math.cos(radian);
+ let sin = Math.sin(radian);
+ if (Math.abs(cos) < 1e-16) { // I hate rounding errors...
+ cos = 0;
+ }
+ if (Math.abs(sin) < 1e-16) { // I hate rounding errors...
+ sin = 0;
+ }
+ rotate = new Affine(cos, sin, -sin, cos, 0, 0);
+ affine = affine.append(rotate);
+ } else {
+ console.error('math.js: No argument to rotate transform!');
+ }
+ if (c.length === 3) {
+ trans = new Affine(1, 0, 0, 1, -c[1], -c[2]);
+ affine = affine.append(trans);
+ }
+ break;
+
+ case 'skewX':
+ if (c[0]) {
+ radian = c[0] * Math.PI / 180.0;
+ tan = Math.tan(radian);
+ skewx = new Affine(1, 0, tan, 1, 0, 0);
+ affine = affine.append(skewx);
+ } else {
+ console.error('math.js: No argument to skewX transform!');
+ }
+ break;
+
+ case 'skewY':
+ if (c[0]) {
+ radian = c[0] * Math.PI / 180.0;
+ tan = Math.tan(radian);
+ skewy = new Affine(1, tan, 0, 1, 0, 0);
+ affine = affine.append(skewy);
+ } else {
+ console.error('math.js: No argument to skewY transform!');
+ }
+ break;
+
+ case 'matrix':
+ if (c.length === 6) {
+ affine = affine.append(new Affine(...c));
+ } else {
+ console.error('math.js: Incorrect number of arguments for matrix!');
+ }
+ break;
+
+ default:
+ console.error('mesh.js: Unhandled transform type: ' + type);
+ break;
+ }
+ });
+
+ return affine;
+ };
+
+ const parsePoints = (s) => {
+ let points = [];
+ let values = s.split(/[ ,]+/);
+ for (let i = 0, imax = values.length - 1; i < imax; i += 2) {
+ points.push(new Point(parseFloat(values[i]), parseFloat(values[i + 1])));
+ }
+ return points;
+ };
+
+ // Set multiple attributes to an element
+ const setAttributes = (el, attrs) => {
+ for (let key in attrs) {
+ el.setAttribute(key, attrs[key]);
+ }
+ };
+
+ // Find the slope of point p_k by the values in p_k-1 and p_k+1
+ const finiteDifferences = (c0, c1, c2, d01, d12) => {
+ let slope = [0, 0, 0, 0];
+ let slow, shigh;
+
+ for (let k = 0; k < 3; ++k) {
+ if ((c1[k] < c0[k] && c1[k] < c2[k]) || (c0[k] < c1[k] && c2[k] < c1[k])) {
+ slope[k] = 0;
+ } else {
+ slope[k] = 0.5 * ((c1[k] - c0[k]) / d01 + (c2[k] - c1[k]) / d12);
+ slow = Math.abs(3.0 * (c1[k] - c0[k]) / d01);
+ shigh = Math.abs(3.0 * (c2[k] - c1[k]) / d12);
+
+ if (slope[k] > slow) {
+ slope[k] = slow;
+ } else if (slope[k] > shigh) {
+ slope[k] = shigh;
+ }
+ }
+ }
+
+ return slope;
+ };
+
+ // Coefficient matrix used for solving linear system
+ const A = [
+ [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [-3, 3, 0, 0, -2, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [2, -2, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, -3, 3, 0, 0, -2, -1, 0, 0],
+ [0, 0, 0, 0, 0, 0, 0, 0, 2, -2, 0, 0, 1, 1, 0, 0],
+ [-3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, -3, 0, 3, 0, 0, 0, 0, 0, -2, 0, -1, 0],
+ [9, -9, -9, 9, 6, 3, -6, -3, 6, -6, 3, -3, 4, 2, 2, 1],
+ [-6, 6, 6, -6, -3, -3, 3, 3, -4, 4, -2, 2, -2, -2, -1, -1],
+ [2, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
+ [0, 0, 0, 0, 2, 0, -2, 0, 0, 0, 0, 0, 1, 0, 1, 0],
+ [-6, 6, 6, -6, -4, -2, 4, 2, -3, 3, -3, 3, -2, -1, -2, -1],
+ [4, -4, -4, 4, 2, 2, -2, -2, 2, -2, 2, -2, 1, 1, 1, 1]
+ ];
+
+ // Solve the linear system for bicubic interpolation
+ const solveLinearSystem = (v) => {
+ let alpha = [];
+
+ for (let i = 0; i < 16; ++i) {
+ alpha[i] = 0;
+ for (let j = 0; j < 16; ++j) {
+ alpha[i] += A[i][j] * v[j];
+ }
+ }
+
+ return alpha;
+ };
+
+ // Evaluate the interpolation parameters at (y, x)
+ const evaluateSolution = (alpha, x, y) => {
+ const xx = x * x;
+ const yy = y * y;
+ const xxx = x * x * x;
+ const yyy = y * y * y;
+
+ let result =
+ alpha[0] +
+ alpha[1] * x +
+ alpha[2] * xx +
+ alpha[3] * xxx +
+ alpha[4] * y +
+ alpha[5] * y * x +
+ alpha[6] * y * xx +
+ alpha[7] * y * xxx +
+ alpha[8] * yy +
+ alpha[9] * yy * x +
+ alpha[10] * yy * xx +
+ alpha[11] * yy * xxx +
+ alpha[12] * yyy +
+ alpha[13] * yyy * x +
+ alpha[14] * yyy * xx +
+ alpha[15] * yyy * xxx;
+
+ return result;
+ };
+
+ // Split a patch into 8x8 smaller patches
+ const splitPatch = (patch) => {
+ let yPatches = [];
+ let xPatches = [];
+ let patches = [];
+
+ // Horizontal splitting
+ for (let i = 0; i < 4; ++i) {
+ yPatches[i] = [];
+ yPatches[i][0] = splitBezier(
+ patch[0][i], patch[1][i],
+ patch[2][i], patch[3][i]
+ );
+
+ yPatches[i][1] = [];
+ yPatches[i][1].push(...splitBezier(...yPatches[i][0][0]));
+ yPatches[i][1].push(...splitBezier(...yPatches[i][0][1]));
+
+ yPatches[i][2] = [];
+ yPatches[i][2].push(...splitBezier(...yPatches[i][1][0]));
+ yPatches[i][2].push(...splitBezier(...yPatches[i][1][1]));
+ yPatches[i][2].push(...splitBezier(...yPatches[i][1][2]));
+ yPatches[i][2].push(...splitBezier(...yPatches[i][1][3]));
+ }
+
+ // Vertical splitting
+ for (let i = 0; i < 8; ++i) {
+ xPatches[i] = [];
+
+ for (let j = 0; j < 4; ++j) {
+ xPatches[i][j] = [];
+ xPatches[i][j][0] = splitBezier(
+ yPatches[0][2][i][j], yPatches[1][2][i][j],
+ yPatches[2][2][i][j], yPatches[3][2][i][j]
+ );
+
+ xPatches[i][j][1] = [];
+ xPatches[i][j][1].push(...splitBezier(...xPatches[i][j][0][0]));
+ xPatches[i][j][1].push(...splitBezier(...xPatches[i][j][0][1]));
+
+ xPatches[i][j][2] = [];
+ xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][0]));
+ xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][1]));
+ xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][2]));
+ xPatches[i][j][2].push(...splitBezier(...xPatches[i][j][1][3]));
+ }
+ }
+
+ for (let i = 0; i < 8; ++i) {
+ patches[i] = [];
+
+ for (let j = 0; j < 8; ++j) {
+ patches[i][j] = [];
+
+ patches[i][j][0] = xPatches[i][0][2][j];
+ patches[i][j][1] = xPatches[i][1][2][j];
+ patches[i][j][2] = xPatches[i][2][2][j];
+ patches[i][j][3] = xPatches[i][3][2][j];
+ }
+ }
+
+ return patches;
+ };
+
+ // Point class -----------------------------------
+ class Point {
+ constructor (x, y) {
+ this.x = x || 0;
+ this.y = y || 0;
+ }
+
+ toString () {
+ return `(x=${this.x}, y=${this.y})`;
+ }
+
+ clone () {
+ return new Point(this.x, this.y);
+ }
+
+ add (v) {
+ return new Point(this.x + v.x, this.y + v.y);
+ }
+
+ scale (v) {
+ if (v.x === undefined) {
+ return new Point(this.x * v, this.y * v);
+ }
+ return new Point(this.x * v.x, this.y * v.y);
+ }
+
+ distSquared (v) {
+ let x = this.x - v.x;
+ let y = this.y - v.y;
+ return (x * x + y * y);
+ }
+
+ // Transform by affine
+ transform (affine) {
+ let x = this.x * affine.a + this.y * affine.c + affine.e;
+ let y = this.x * affine.b + this.y * affine.d + affine.f;
+ return new Point(x, y);
+ }
+ }
+
+ /*
+ * Affine class -------------------------------------
+ *
+ * As defined in the SVG spec
+ * | a c e |
+ * | b d f |
+ * | 0 0 1 |
+ *
+ */
+
+ class Affine {
+ constructor (a, b, c, d, e, f) {
+ if (a === undefined) {
+ this.a = 1;
+ this.b = 0;
+ this.c = 0;
+ this.d = 1;
+ this.e = 0;
+ this.f = 0;
+ } else {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ this.d = d;
+ this.e = e;
+ this.f = f;
+ }
+ }
+
+ toString () {
+ return `affine: ${this.a} ${this.c} ${this.e} \n\
+ ${this.b} ${this.d} ${this.f}`;
+ }
+
+ append (v) {
+ if (!(v instanceof Affine)) {
+ console.error(`mesh.js: argument to Affine.append is not affine!`);
+ }
+ let a = this.a * v.a + this.c * v.b;
+ let b = this.b * v.a + this.d * v.b;
+ let c = this.a * v.c + this.c * v.d;
+ let d = this.b * v.c + this.d * v.d;
+ let e = this.a * v.e + this.c * v.f + this.e;
+ let f = this.b * v.e + this.d * v.f + this.f;
+ return new Affine(a, b, c, d, e, f);
+ }
+ }
+
+ // Curve class --------------------------------------
+ class Curve {
+ constructor (nodes, colors) {
+ this.nodes = nodes; // 4 Bezier points
+ this.colors = colors; // 2 x 4 colors (two ends x R+G+B+A)
+ }
+
+ /*
+ * Paint a Bezier curve
+ * w is canvas.width
+ * h is canvas.height
+ */
+ paintCurve (v, w) {
+ // If inside, see if we need to split
+ if (bezierStepsSquared(this.nodes) > maxBezierStep) {
+ const beziers = splitBezier(...this.nodes);
+ // ([start][end])
+ let colors0 = [[], []];
+ let colors1 = [[], []];
+
+ /*
+ * Linear horizontal interpolation of the middle value for every
+ * patch exceeding thereshold
+ */
+ for (let i = 0; i < 4; ++i) {
+ colors0[0][i] = this.colors[0][i];
+ colors0[1][i] = (this.colors[0][i] + this.colors[1][i]) / 2;
+ colors1[0][i] = colors0[1][i];
+ colors1[1][i] = this.colors[1][i];
+ }
+ let curve0 = new Curve(beziers[0], colors0);
+ let curve1 = new Curve(beziers[1], colors1);
+ curve0.paintCurve(v, w);
+ curve1.paintCurve(v, w);
+ } else {
+ // Directly write data
+ let x = Math.round(this.nodes[0].x);
+ if (x >= 0 && x < w) {
+ let index = (~~this.nodes[0].y * w + x) * 4;
+ v[index] = Math.round(this.colors[0][0]);
+ v[index + 1] = Math.round(this.colors[0][1]);
+ v[index + 2] = Math.round(this.colors[0][2]);
+ v[index + 3] = Math.round(this.colors[0][3]); // Alpha
+ }
+ }
+ }
+ }
+
+ // Patch class -------------------------------------
+ class Patch {
+ constructor (nodes, colors) {
+ this.nodes = nodes; // 4x4 array of points
+ this.colors = colors; // 2x2x4 colors (four corners x R+G+B+A)
+ }
+
+ // Split patch horizontally into two patches.
+ split () {
+ let nodes0 = [[], [], [], []];
+ let nodes1 = [[], [], [], []];
+ let colors0 = [
+ [[], []],
+ [[], []]
+ ];
+ let colors1 = [
+ [[], []],
+ [[], []]
+ ];
+
+ for (let i = 0; i < 4; ++i) {
+ const beziers = splitBezier(
+ this.nodes[0][i], this.nodes[1][i],
+ this.nodes[2][i], this.nodes[3][i]
+ );
+
+ nodes0[0][i] = beziers[0][0];
+ nodes0[1][i] = beziers[0][1];
+ nodes0[2][i] = beziers[0][2];
+ nodes0[3][i] = beziers[0][3];
+ nodes1[0][i] = beziers[1][0];
+ nodes1[1][i] = beziers[1][1];
+ nodes1[2][i] = beziers[1][2];
+ nodes1[3][i] = beziers[1][3];
+ }
+
+ /*
+ * Linear vertical interpolation of the middle value for every
+ * patch exceeding thereshold
+ */
+ for (let i = 0; i < 4; ++i) {
+ colors0[0][0][i] = this.colors[0][0][i];
+ colors0[0][1][i] = this.colors[0][1][i];
+ colors0[1][0][i] = (this.colors[0][0][i] + this.colors[1][0][i]) / 2;
+ colors0[1][1][i] = (this.colors[0][1][i] + this.colors[1][1][i]) / 2;
+ colors1[0][0][i] = colors0[1][0][i];
+ colors1[0][1][i] = colors0[1][1][i];
+ colors1[1][0][i] = this.colors[1][0][i];
+ colors1[1][1][i] = this.colors[1][1][i];
+ }
+
+ return ([new Patch(nodes0, colors0), new Patch(nodes1, colors1)]);
+ }
+
+ paint (v, w) {
+ // Check if we need to split
+ let larger = false;
+ let step;
+ for (let i = 0; i < 4; ++i) {
+ step = bezierStepsSquared([
+ this.nodes[0][i], this.nodes[1][i],
+ this.nodes[2][i], this.nodes[3][i]
+ ]);
+
+ if (step > maxBezierStep) {
+ larger = true;
+ break;
+ }
+ }
+
+ if (larger) {
+ let patches = this.split();
+ patches[0].paint(v, w);
+ patches[1].paint(v, w);
+ } else {
+ /*
+ * Paint a Bezier curve using just the top of the patch. If
+ * the patch is thin enough this should work. We leave this
+ * function here in case we want to do something more fancy.
+ */
+ let curve = new Curve([...this.nodes[0]], [...this.colors[0]]);
+ curve.paintCurve(v, w);
+ }
+ }
+ }
+
+ // Mesh class ---------------------------------------
+ class Mesh {
+ constructor (mesh) {
+ this.readMesh(mesh);
+ this.type = mesh.getAttribute('type') || 'bilinear';
+ }
+
+ // Function to parse an SVG mesh and set the nodes (points) and colors
+ readMesh (mesh) {
+ let nodes = [[]];
+ let colors = [[]];
+
+ let x = Number(mesh.getAttribute('x'));
+ let y = Number(mesh.getAttribute('y'));
+ nodes[0][0] = new Point(x, y);
+
+ let rows = mesh.children;
+ for (let i = 0, imax = rows.length; i < imax; ++i) {
+ // Need to validate if meshrow...
+ nodes[3 * i + 1] = []; // Need three extra rows for each meshrow.
+ nodes[3 * i + 2] = [];
+ nodes[3 * i + 3] = [];
+ colors[i + 1] = []; // Need one more row than number of meshrows.
+
+ let patches = rows[i].children;
+ for (let j = 0, jmax = patches.length; j < jmax; ++j) {
+ let stops = patches[j].children;
+ for (let k = 0, kmax = stops.length; k < kmax; ++k) {
+ let l = k;
+ if (i !== 0) {
+ ++l; // There is no top if row isn't first row.
+ }
+ let path = stops[k].getAttribute('path');
+ let parts;
+
+ let type = 'l'; // We need to still find mid-points even if no path.
+ if (path != null) {
+ parts = path.match(/\s*([lLcC])\s*(.*)/);
+ type = parts[1];
+ }
+ let stopNodes = parsePoints(parts[2]);
+
+ switch (type) {
+ case 'l':
+ if (l === 0) { // Top
+ nodes[3 * i][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j]);
+ nodes[3 * i][3 * j + 1] = wAvg(nodes[3 * i][3 * j], nodes[3 * i][3 * j + 3]);
+ nodes[3 * i][3 * j + 2] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i][3 * j]);
+ } else if (l === 1) { // Right
+ nodes[3 * i + 3][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j + 3]);
+ nodes[3 * i + 1][3 * j + 3] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i + 3][3 * j + 3]);
+ nodes[3 * i + 2][3 * j + 3] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i][3 * j + 3]);
+ } else if (l === 2) { // Bottom
+ if (j === 0) {
+ nodes[3 * i + 3][3 * j + 0] = stopNodes[0].add(nodes[3 * i + 3][3 * j + 3]);
+ }
+ nodes[3 * i + 3][3 * j + 1] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i + 3][3 * j + 3]);
+ nodes[3 * i + 3][3 * j + 2] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i + 3][3 * j]);
+ } else { // Left
+ nodes[3 * i + 1][3 * j] = wAvg(nodes[3 * i][3 * j], nodes[3 * i + 3][3 * j]);
+ nodes[3 * i + 2][3 * j] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i][3 * j]);
+ }
+ break;
+ case 'L':
+ if (l === 0) { // Top
+ nodes[3 * i][3 * j + 3] = stopNodes[0];
+ nodes[3 * i][3 * j + 1] = wAvg(nodes[3 * i][3 * j], nodes[3 * i][3 * j + 3]);
+ nodes[3 * i][3 * j + 2] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i][3 * j]);
+ } else if (l === 1) { // Right
+ nodes[3 * i + 3][3 * j + 3] = stopNodes[0];
+ nodes[3 * i + 1][3 * j + 3] = wAvg(nodes[3 * i][3 * j + 3], nodes[3 * i + 3][3 * j + 3]);
+ nodes[3 * i + 2][3 * j + 3] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i][3 * j + 3]);
+ } else if (l === 2) { // Bottom
+ if (j === 0) {
+ nodes[3 * i + 3][3 * j + 0] = stopNodes[0];
+ }
+ nodes[3 * i + 3][3 * j + 1] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i + 3][3 * j + 3]);
+ nodes[3 * i + 3][3 * j + 2] = wAvg(nodes[3 * i + 3][3 * j + 3], nodes[3 * i + 3][3 * j]);
+ } else { // Left
+ nodes[3 * i + 1][3 * j] = wAvg(nodes[3 * i][3 * j], nodes[3 * i + 3][3 * j]);
+ nodes[3 * i + 2][3 * j] = wAvg(nodes[3 * i + 3][3 * j], nodes[3 * i][3 * j]);
+ }
+ break;
+ case 'c':
+ if (l === 0) { // Top
+ nodes[3 * i][3 * j + 1] = stopNodes[0].add(nodes[3 * i][3 * j]);
+ nodes[3 * i][3 * j + 2] = stopNodes[1].add(nodes[3 * i][3 * j]);
+ nodes[3 * i][3 * j + 3] = stopNodes[2].add(nodes[3 * i][3 * j]);
+ } else if (l === 1) { // Right
+ nodes[3 * i + 1][3 * j + 3] = stopNodes[0].add(nodes[3 * i][3 * j + 3]);
+ nodes[3 * i + 2][3 * j + 3] = stopNodes[1].add(nodes[3 * i][3 * j + 3]);
+ nodes[3 * i + 3][3 * j + 3] = stopNodes[2].add(nodes[3 * i][3 * j + 3]);
+ } else if (l === 2) { // Bottom
+ nodes[3 * i + 3][3 * j + 2] = stopNodes[0].add(nodes[3 * i + 3][3 * j + 3]);
+ nodes[3 * i + 3][3 * j + 1] = stopNodes[1].add(nodes[3 * i + 3][3 * j + 3]);
+ if (j === 0) {
+ nodes[3 * i + 3][3 * j + 0] = stopNodes[2].add(nodes[3 * i + 3][3 * j + 3]);
+ }
+ } else { // Left
+ nodes[3 * i + 2][3 * j] = stopNodes[0].add(nodes[3 * i + 3][3 * j]);
+ nodes[3 * i + 1][3 * j] = stopNodes[1].add(nodes[3 * i + 3][3 * j]);
+ }
+ break;
+ case 'C':
+ if (l === 0) { // Top
+ nodes[3 * i][3 * j + 1] = stopNodes[0];
+ nodes[3 * i][3 * j + 2] = stopNodes[1];
+ nodes[3 * i][3 * j + 3] = stopNodes[2];
+ } else if (l === 1) { // Right
+ nodes[3 * i + 1][3 * j + 3] = stopNodes[0];
+ nodes[3 * i + 2][3 * j + 3] = stopNodes[1];
+ nodes[3 * i + 3][3 * j + 3] = stopNodes[2];
+ } else if (l === 2) { // Bottom
+ nodes[3 * i + 3][3 * j + 2] = stopNodes[0];
+ nodes[3 * i + 3][3 * j + 1] = stopNodes[1];
+ if (j === 0) {
+ nodes[3 * i + 3][3 * j + 0] = stopNodes[2];
+ }
+ } else { // Left
+ nodes[3 * i + 2][3 * j] = stopNodes[0];
+ nodes[3 * i + 1][3 * j] = stopNodes[1];
+ }
+ break;
+ default:
+ console.error('mesh.js: ' + type + ' invalid path type.');
+ }
+
+ if ((i === 0 && j === 0) || k > 0) {
+ let colorRaw = window.getComputedStyle(stops[k]).stopColor
+ .match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i);
+ let alphaRaw = window.getComputedStyle(stops[k]).stopOpacity;
+ let alpha = 255;
+ if (alphaRaw) {
+ alpha = Math.floor(alphaRaw * 255);
+ }
+
+ if (colorRaw) {
+ if (l === 0) { // upper left corner
+ colors[i][j] = [];
+ colors[i][j][0] = Math.floor(colorRaw[1]);
+ colors[i][j][1] = Math.floor(colorRaw[2]);
+ colors[i][j][2] = Math.floor(colorRaw[3]);
+ colors[i][j][3] = alpha; // Alpha
+ } else if (l === 1) { // upper right corner
+ colors[i][j + 1] = [];
+ colors[i][j + 1][0] = Math.floor(colorRaw[1]);
+ colors[i][j + 1][1] = Math.floor(colorRaw[2]);
+ colors[i][j + 1][2] = Math.floor(colorRaw[3]);
+ colors[i][j + 1][3] = alpha; // Alpha
+ } else if (l === 2) { // lower right corner
+ colors[i + 1][j + 1] = [];
+ colors[i + 1][j + 1][0] = Math.floor(colorRaw[1]);
+ colors[i + 1][j + 1][1] = Math.floor(colorRaw[2]);
+ colors[i + 1][j + 1][2] = Math.floor(colorRaw[3]);
+ colors[i + 1][j + 1][3] = alpha; // Alpha
+ } else if (l === 3) { // lower left corner
+ colors[i + 1][j] = [];
+ colors[i + 1][j][0] = Math.floor(colorRaw[1]);
+ colors[i + 1][j][1] = Math.floor(colorRaw[2]);
+ colors[i + 1][j][2] = Math.floor(colorRaw[3]);
+ colors[i + 1][j][3] = alpha; // Alpha
+ }
+ }
+ }
+ }
+
+ // SVG doesn't use tensor points but we need them for rendering.
+ nodes[3 * i + 1][3 * j + 1] = new Point();
+ nodes[3 * i + 1][3 * j + 2] = new Point();
+ nodes[3 * i + 2][3 * j + 1] = new Point();
+ nodes[3 * i + 2][3 * j + 2] = new Point();
+
+ nodes[3 * i + 1][3 * j + 1].x =
+ (-4.0 * nodes[3 * i][3 * j].x +
+ 6.0 * (nodes[3 * i][3 * j + 1].x + nodes[3 * i + 1][3 * j].x) +
+ -2.0 * (nodes[3 * i][3 * j + 3].x + nodes[3 * i + 3][3 * j].x) +
+ 3.0 * (nodes[3 * i + 3][3 * j + 1].x + nodes[3 * i + 1][3 * j + 3].x) +
+ -1.0 * nodes[3 * i + 3][3 * j + 3].x) / 9.0;
+ nodes[3 * i + 1][3 * j + 2].x =
+ (-4.0 * nodes[3 * i][3 * j + 3].x +
+ 6.0 * (nodes[3 * i][3 * j + 2].x + nodes[3 * i + 1][3 * j + 3].x) +
+ -2.0 * (nodes[3 * i][3 * j].x + nodes[3 * i + 3][3 * j + 3].x) +
+ 3.0 * (nodes[3 * i + 3][3 * j + 2].x + nodes[3 * i + 1][3 * j].x) +
+ -1.0 * nodes[3 * i + 3][3 * j].x) / 9.0;
+ nodes[3 * i + 2][3 * j + 1].x =
+ (-4.0 * nodes[3 * i + 3][3 * j].x +
+ 6.0 * (nodes[3 * i + 3][3 * j + 1].x + nodes[3 * i + 2][3 * j].x) +
+ -2.0 * (nodes[3 * i + 3][3 * j + 3].x + nodes[3 * i][3 * j].x) +
+ 3.0 * (nodes[3 * i][3 * j + 1].x + nodes[3 * i + 2][3 * j + 3].x) +
+ -1.0 * nodes[3 * i][3 * j + 3].x) / 9.0;
+ nodes[3 * i + 2][3 * j + 2].x =
+ (-4.0 * nodes[3 * i + 3][3 * j + 3].x +
+ 6.0 * (nodes[3 * i + 3][3 * j + 2].x + nodes[3 * i + 2][3 * j + 3].x) +
+ -2.0 * (nodes[3 * i + 3][3 * j].x + nodes[3 * i][3 * j + 3].x) +
+ 3.0 * (nodes[3 * i][3 * j + 2].x + nodes[3 * i + 2][3 * j].x) +
+ -1.0 * nodes[3 * i][3 * j].x) / 9.0;
+
+ nodes[3 * i + 1][3 * j + 1].y =
+ (-4.0 * nodes[3 * i][3 * j].y +
+ 6.0 * (nodes[3 * i][3 * j + 1].y + nodes[3 * i + 1][3 * j].y) +
+ -2.0 * (nodes[3 * i][3 * j + 3].y + nodes[3 * i + 3][3 * j].y) +
+ 3.0 * (nodes[3 * i + 3][3 * j + 1].y + nodes[3 * i + 1][3 * j + 3].y) +
+ -1.0 * nodes[3 * i + 3][3 * j + 3].y) / 9.0;
+ nodes[3 * i + 1][3 * j + 2].y =
+ (-4.0 * nodes[3 * i][3 * j + 3].y +
+ 6.0 * (nodes[3 * i][3 * j + 2].y + nodes[3 * i + 1][3 * j + 3].y) +
+ -2.0 * (nodes[3 * i][3 * j].y + nodes[3 * i + 3][3 * j + 3].y) +
+ 3.0 * (nodes[3 * i + 3][3 * j + 2].y + nodes[3 * i + 1][3 * j].y) +
+ -1.0 * nodes[3 * i + 3][3 * j].y) / 9.0;
+ nodes[3 * i + 2][3 * j + 1].y =
+ (-4.0 * nodes[3 * i + 3][3 * j].y +
+ 6.0 * (nodes[3 * i + 3][3 * j + 1].y + nodes[3 * i + 2][3 * j].y) +
+ -2.0 * (nodes[3 * i + 3][3 * j + 3].y + nodes[3 * i][3 * j].y) +
+ 3.0 * (nodes[3 * i][3 * j + 1].y + nodes[3 * i + 2][3 * j + 3].y) +
+ -1.0 * nodes[3 * i][3 * j + 3].y) / 9.0;
+ nodes[3 * i + 2][3 * j + 2].y =
+ (-4.0 * nodes[3 * i + 3][3 * j + 3].y +
+ 6.0 * (nodes[3 * i + 3][3 * j + 2].y + nodes[3 * i + 2][3 * j + 3].y) +
+ -2.0 * (nodes[3 * i + 3][3 * j].y + nodes[3 * i][3 * j + 3].y) +
+ 3.0 * (nodes[3 * i][3 * j + 2].y + nodes[3 * i + 2][3 * j].y) +
+ -1.0 * nodes[3 * i][3 * j].y) / 9.0;
+ }
+ }
+
+ this.nodes = nodes; // (m*3+1) x (n*3+1) points
+ this.colors = colors; // (m+1) x (n+1) x 4 colors (R+G+B+A)
+ }
+
+ // Extracts out each patch and then paints it
+ paintMesh (v, w) {
+ let imax = (this.nodes.length - 1) / 3;
+ let jmax = (this.nodes[0].length - 1) / 3;
+
+ if (this.type === 'bilinear' || imax < 2 || jmax < 2) {
+ let patch;
+
+ for (let i = 0; i < imax; ++i) {
+ for (let j = 0; j < jmax; ++j) {
+ let sliceNodes = [];
+ for (let k = i * 3, kmax = (i * 3) + 4; k < kmax; ++k) {
+ sliceNodes.push(this.nodes[k].slice(j * 3, (j * 3) + 4));
+ }
+
+ let sliceColors = [];
+ sliceColors.push(this.colors[i].slice(j, j + 2));
+ sliceColors.push(this.colors[i + 1].slice(j, j + 2));
+
+ patch = new Patch(sliceNodes, sliceColors);
+ patch.paint(v, w);
+ }
+ }
+ } else {
+ // Reference:
+ // https://en.wikipedia.org/wiki/Bicubic_interpolation#Computation
+ let d01, d12, patch, sliceNodes, nodes, f, alpha;
+ const ilast = imax;
+ const jlast = jmax;
+ imax++;
+ jmax++;
+
+ /*
+ * d = the interpolation data
+ * d[i][j] = a node record (Point, color_array, color_dx, color_dy)
+ * d[i][j][0] : Point
+ * d[i][j][1] : [RGBA]
+ * d[i][j][2] = dx [RGBA]
+ * d[i][j][3] = dy [RGBA]
+ * d[i][j][][k] : color channel k
+ */
+ let d = new Array(imax);
+
+ // Setting the node and the colors
+ for (let i = 0; i < imax; ++i) {
+ d[i] = new Array(jmax);
+ for (let j = 0; j < jmax; ++j) {
+ d[i][j] = [];
+ d[i][j][0] = this.nodes[3 * i][3 * j];
+ d[i][j][1] = this.colors[i][j];
+ }
+ }
+
+ // Calculate the inner derivatives
+ for (let i = 0; i < imax; ++i) {
+ for (let j = 0; j < jmax; ++j) {
+ // dx
+ if (i !== 0 && i !== ilast) {
+ d01 = distance(d[i - 1][j][0], d[i][j][0]);
+ d12 = distance(d[i + 1][j][0], d[i][j][0]);
+ d[i][j][2] = finiteDifferences(d[i - 1][j][1], d[i][j][1],
+ d[i + 1][j][1], d01, d12);
+ }
+
+ // dy
+ if (j !== 0 && j !== jlast) {
+ d01 = distance(d[i][j - 1][0], d[i][j][0]);
+ d12 = distance(d[i][j + 1][0], d[i][j][0]);
+ d[i][j][3] = finiteDifferences(d[i][j - 1][1], d[i][j][1],
+ d[i][j + 1][1], d01, d12);
+ }
+
+ // dxy is, by standard, set to 0
+ }
+ }
+
+ /*
+ * Calculate the exterior derivatives
+ * We fit the exterior derivatives onto parabolas generated by
+ * the point and the interior derivatives.
+ */
+ for (let j = 0; j < jmax; ++j) {
+ d[0][j][2] = [];
+ d[ilast][j][2] = [];
+
+ for (let k = 0; k < 4; ++k) {
+ d01 = distance(d[1][j][0], d[0][j][0]);
+ d12 = distance(d[ilast][j][0], d[ilast - 1][j][0]);
+
+ if (d01 > 0) {
+ d[0][j][2][k] = 2.0 * (d[1][j][1][k] - d[0][j][1][k]) / d01 -
+ d[1][j][2][k];
+ } else {
+ console.log(`0 was 0! (j: ${j}, k: ${k})`);
+ d[0][j][2][k] = 0;
+ }
+
+ if (d12 > 0) {
+ d[ilast][j][2][k] = 2.0 * (d[ilast][j][1][k] - d[ilast - 1][j][1][k]) /
+ d12 - d[ilast - 1][j][2][k];
+ } else {
+ console.log(`last was 0! (j: ${j}, k: ${k})`);
+ d[ilast][j][2][k] = 0;
+ }
+ }
+ }
+
+ for (let i = 0; i < imax; ++i) {
+ d[i][0][3] = [];
+ d[i][jlast][3] = [];
+
+ for (let k = 0; k < 4; ++k) {
+ d01 = distance(d[i][1][0], d[i][0][0]);
+ d12 = distance(d[i][jlast][0], d[i][jlast - 1][0]);
+
+ if (d01 > 0) {
+ d[i][0][3][k] = 2.0 * (d[i][1][1][k] - d[i][0][1][k]) / d01 -
+ d[i][1][3][k];
+ } else {
+ console.log(`0 was 0! (i: ${i}, k: ${k})`);
+ d[i][0][3][k] = 0;
+ }
+
+ if (d12 > 0) {
+ d[i][jlast][3][k] = 2.0 * (d[i][jlast][1][k] - d[i][jlast - 1][1][k]) /
+ d12 - d[i][jlast - 1][3][k];
+ } else {
+ console.log(`last was 0! (i: ${i}, k: ${k})`);
+ d[i][jlast][3][k] = 0;
+ }
+ }
+ }
+
+ // Fill patches
+ for (let i = 0; i < ilast; ++i) {
+ for (let j = 0; j < jlast; ++j) {
+ let dLeft = distance(d[i][j][0], d[i + 1][j][0]);
+ let dRight = distance(d[i][j + 1][0], d[i + 1][j + 1][0]);
+ let dTop = distance(d[i][j][0], d[i][j + 1][0]);
+ let dBottom = distance(d[i + 1][j][0], d[i + 1][j + 1][0]);
+ let r = [[], [], [], []];
+
+ for (let k = 0; k < 4; ++k) {
+ f = [];
+
+ f[0] = d[i][j][1][k];
+ f[1] = d[i + 1][j][1][k];
+ f[2] = d[i][j + 1][1][k];
+ f[3] = d[i + 1][j + 1][1][k];
+ f[4] = d[i][j][2][k] * dLeft;
+ f[5] = d[i + 1][j][2][k] * dLeft;
+ f[6] = d[i][j + 1][2][k] * dRight;
+ f[7] = d[i + 1][j + 1][2][k] * dRight;
+ f[8] = d[i][j][3][k] * dTop;
+ f[9] = d[i + 1][j][3][k] * dBottom;
+ f[10] = d[i][j + 1][3][k] * dTop;
+ f[11] = d[i + 1][j + 1][3][k] * dBottom;
+ f[12] = 0; // dxy
+ f[13] = 0; // dxy
+ f[14] = 0; // dxy
+ f[15] = 0; // dxy
+
+ // get alpha values
+ alpha = solveLinearSystem(f);
+
+ for (let l = 0; l < 9; ++l) {
+ r[k][l] = [];
+
+ for (let m = 0; m < 9; ++m) {
+ // evaluation
+ r[k][l][m] = evaluateSolution(alpha, l / 8, m / 8);
+
+ if (r[k][l][m] > 255) {
+ r[k][l][m] = 255;
+ } else if (r[k][l][m] < 0.0) {
+ r[k][l][m] = 0.0;
+ }
+ }
+ }
+ }
+
+ // split the bezier patch into 8x8 patches
+ sliceNodes = [];
+ for (let k = i * 3, kmax = (i * 3) + 4; k < kmax; ++k) {
+ sliceNodes.push(this.nodes[k].slice(j * 3, (j * 3) + 4));
+ }
+
+ nodes = splitPatch(sliceNodes);
+
+ // Create patches and paint the bilinearliy
+ for (let l = 0; l < 8; ++l) {
+ for (let m = 0; m < 8; ++m) {
+ patch = new Patch(
+ nodes[l][m],
+ [[
+ [r[0][l][m], r[1][l][m], r[2][l][m], r[3][l][m]],
+ [r[0][l][m + 1], r[1][l][m + 1], r[2][l][m + 1], r[3][l][m + 1]]
+ ], [
+ [r[0][l + 1][m], r[1][l + 1][m], r[2][l + 1][m], r[3][l + 1][m]],
+ [r[0][l + 1][m + 1], r[1][l + 1][m + 1], r[2][l + 1][m + 1], r[3][l + 1][m + 1]]
+ ]]
+ );
+
+ patch.paint(v, w);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Transforms mesh into coordinate space of canvas (t is either Point or Affine).
+ transform (t) {
+ if (t instanceof Point) {
+ for (let i = 0, imax = this.nodes.length; i < imax; ++i) {
+ for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) {
+ this.nodes[i][j] = this.nodes[i][j].add(t);
+ }
+ }
+ } else if (t instanceof Affine) {
+ for (let i = 0, imax = this.nodes.length; i < imax; ++i) {
+ for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) {
+ this.nodes[i][j] = this.nodes[i][j].transform(t);
+ }
+ }
+ }
+ }
+
+ // Scale mesh into coordinate space of canvas (t is a Point).
+ scale (t) {
+ for (let i = 0, imax = this.nodes.length; i < imax; ++i) {
+ for (let j = 0, jmax = this.nodes[0].length; j < jmax; ++j) {
+ this.nodes[i][j] = this.nodes[i][j].scale(t);
+ }
+ }
+ }
+ }
+
+ // Start of document processing ---------------------
+ const shapes = document.querySelectorAll('rect,circle,ellipse,path,text');
+
+ shapes.forEach((shape, i) => {
+ // Get id. If no id, create one.
+ let shapeId = shape.getAttribute('id');
+ if (!shapeId) {
+ shapeId = 'patchjs_shape' + i;
+ shape.setAttribute('id', shapeId);
+ }
+
+ const fillURL = shape.style.fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);
+ const strokeURL = shape.style.stroke.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);
+
+ if (fillURL && fillURL[1]) {
+ const mesh = document.getElementById(fillURL[1]);
+
+ if (mesh && mesh.nodeName === 'meshgradient') {
+ const bbox = shape.getBBox();
+
+ // Create temporary canvas
+ let myCanvas = document.createElementNS(xhtmlNS, 'canvas');
+ setAttributes(myCanvas, {
+ 'width': bbox.width,
+ 'height': bbox.height
+ });
+
+ const myContext = myCanvas.getContext('2d');
+ let myCanvasImage = myContext.createImageData(bbox.width, bbox.height);
+
+ // Draw a mesh
+ const myMesh = new Mesh(mesh);
+
+ // Adjust for bounding box if necessary.
+ if (mesh.getAttribute('gradientUnits') === 'objectBoundingBox') {
+ myMesh.scale(new Point(bbox.width, bbox.height));
+ }
+
+ // Apply gradient transform.
+ const gradientTransform = mesh.getAttribute('gradientTransform');
+ if (gradientTransform != null) {
+ myMesh.transform(parseTransform(gradientTransform));
+ }
+
+ // Position to Canvas coordinate.
+ if (mesh.getAttribute('gradientUnits') === 'userSpaceOnUse') {
+ myMesh.transform(new Point(-bbox.x, -bbox.y));
+ }
+
+ // Paint
+ myMesh.paintMesh(myCanvasImage.data, myCanvas.width);
+ myContext.putImageData(myCanvasImage, 0, 0);
+
+ // Create image element of correct size
+ const myImage = document.createElementNS(svgNS, 'image');
+ setAttributes(myImage, {
+ 'width': bbox.width,
+ 'height': bbox.height,
+ 'x': bbox.x,
+ 'y': bbox.y
+ });
+
+ // Set image to data url
+ let myPNG = myCanvas.toDataURL();
+ myImage.setAttributeNS(xlinkNS, 'xlink:href', myPNG);
+
+ // Insert image into document
+ shape.parentNode.insertBefore(myImage, shape);
+ shape.style.fill = 'none';
+
+ // Create clip referencing shape and insert into document
+ const use = document.createElementNS(svgNS, 'use');
+ use.setAttributeNS(xlinkNS, 'xlink:href', '#' + shapeId);
+
+ const clipId = 'patchjs_clip' + i;
+ const clip = document.createElementNS(svgNS, 'clipPath');
+ clip.setAttribute('id', clipId);
+ clip.appendChild(use);
+ shape.parentElement.insertBefore(clip, shape);
+ myImage.setAttribute('clip-path', 'url(#' + clipId + ')');
+
+ // Force the Garbage Collector to free the space
+ myCanvasImage = null;
+ myCanvas = null;
+ myPNG = null;
+ }
+ }
+
+ if (strokeURL && strokeURL[1]) {
+ const mesh = document.getElementById(strokeURL[1]);
+
+ if (mesh && mesh.nodeName === 'meshgradient') {
+ const strokeWidth = parseFloat(shape.style.strokeWidth.slice(0, -2));
+ const strokeMiterlimit = parseFloat(shape.style.strokeMiterlimit) ||
+ parseFloat(shape.getAttribute('stroke-miterlimit')) || 1;
+ const phase = strokeWidth * strokeMiterlimit;
+
+ const bbox = shape.getBBox();
+ const boxWidth = Math.trunc(bbox.width + phase);
+ const boxHeight = Math.trunc(bbox.height + phase);
+ const boxX = Math.trunc(bbox.x - phase / 2);
+ const boxY = Math.trunc(bbox.y - phase / 2);
+
+ // Create temporary canvas
+ let myCanvas = document.createElementNS(xhtmlNS, 'canvas');
+ setAttributes(myCanvas, {
+ 'width': boxWidth,
+ 'height': boxHeight
+ });
+
+ const myContext = myCanvas.getContext('2d');
+ let myCanvasImage = myContext.createImageData(boxWidth, boxHeight);
+
+ // Draw a mesh
+ const myMesh = new Mesh(mesh);
+
+ // Adjust for bounding box if necessary.
+ if (mesh.getAttribute('gradientUnits') === 'objectBoundingBox') {
+ myMesh.scale(new Point(boxWidth, boxHeight));
+ }
+
+ // Apply gradient transform.
+ const gradientTransform = mesh.getAttribute('gradientTransform');
+ if (gradientTransform != null) {
+ myMesh.transform(parseTransform(gradientTransform));
+ }
+
+ // Position to Canvas coordinate.
+ if (mesh.getAttribute('gradientUnits') === 'userSpaceOnUse') {
+ myMesh.transform(new Point(-boxX, -boxY));
+ }
+
+ // Paint
+ myMesh.paintMesh(myCanvasImage.data, myCanvas.width);
+ myContext.putImageData(myCanvasImage, 0, 0);
+
+ // Create image element of correct size
+ const myImage = document.createElementNS(svgNS, 'image');
+ setAttributes(myImage, {
+ 'width': boxWidth,
+ 'height': boxHeight,
+ 'x': 0,
+ 'y': 0
+ });
+
+ // Set image to data url
+ let myPNG = myCanvas.toDataURL();
+ myImage.setAttributeNS(xlinkNS, 'xlink:href', myPNG);
+
+ // Create pattern to hold the stroke image
+ const patternId = 'pattern_clip' + i;
+ const myPattern = document.createElementNS(svgNS, 'pattern');
+ setAttributes(myPattern, {
+ 'id': patternId,
+ 'patternUnits': 'userSpaceOnUse',
+ 'width': boxWidth,
+ 'height': boxHeight,
+ 'x': boxX,
+ 'y': boxY
+ });
+ myPattern.appendChild(myImage);
+
+ // Insert image into document
+ mesh.parentNode.appendChild(myPattern);
+ shape.style.stroke = 'url(#' + patternId + ')';
+
+ // Force the Garbage Collector to free the space
+ myCanvasImage = null;
+ myCanvas = null;
+ myPNG = null;
+ }
+ }
+ });
+})();
diff --git a/src/extension/internal/polyfill/mesh_compressed.include b/src/extension/internal/polyfill/mesh_compressed.include
new file mode 100644
index 0000000..275be96
--- /dev/null
+++ b/src/extension/internal/polyfill/mesh_compressed.include
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: CC0
+R"=====(
+!function(){const t="http://www.w3.org/2000/svg",e="http://www.w3.org/1999/xlink",s="http://www.w3.org/1999/xhtml",r=2;if(document.createElementNS(t,"meshgradient").x)return;const n=(t,e,s,r)=>{let n=new x(.5*(e.x+s.x),.5*(e.y+s.y)),o=new x(.5*(t.x+e.x),.5*(t.y+e.y)),i=new x(.5*(s.x+r.x),.5*(s.y+r.y)),a=new x(.5*(n.x+o.x),.5*(n.y+o.y)),h=new x(.5*(n.x+i.x),.5*(n.y+i.y)),l=new x(.5*(a.x+h.x),.5*(a.y+h.y));return[[t,o,a,l],[l,h,i,r]]},o=t=>{let e=t[0].distSquared(t[1]),s=t[2].distSquared(t[3]),r=.25*t[0].distSquared(t[2]),n=.25*t[1].distSquared(t[3]),o=e>s?e:s,i=r>n?r:n;return 18*(o>i?o:i)},i=(t,e)=>Math.sqrt(t.distSquared(e)),a=(t,e)=>t.scale(2/3).add(e.scale(1/3)),h=t=>{let e,s,r,n,o,i,a,h=new g;return t.match(/(\w+\(\s*[^)]+\))+/g).forEach(t=>{let l=t.match(/[\w.-]+/g),d=l.shift();switch(d){case"translate":2===l.length?e=new g(1,0,0,1,l[0],l[1]):(console.error("mesh.js: translate does not have 2 arguments!"),e=new g(1,0,0,1,0,0)),h=h.append(e);break;case"scale":1===l.length?s=new g(l[0],0,0,l[0],0,0):2===l.length?s=new g(l[0],0,0,l[1],0,0):(console.error("mesh.js: scale does not have 1 or 2 arguments!"),s=new g(1,0,0,1,0,0)),h=h.append(s);break;case"rotate":if(3===l.length&&(e=new g(1,0,0,1,l[1],l[2]),h=h.append(e)),l[0]){r=l[0]*Math.PI/180;let t=Math.cos(r),e=Math.sin(r);Math.abs(t)<1e-16&&(t=0),Math.abs(e)<1e-16&&(e=0),a=new g(t,e,-e,t,0,0),h=h.append(a)}else console.error("math.js: No argument to rotate transform!");3===l.length&&(e=new g(1,0,0,1,-l[1],-l[2]),h=h.append(e));break;case"skewX":l[0]?(r=l[0]*Math.PI/180,n=Math.tan(r),o=new g(1,0,n,1,0,0),h=h.append(o)):console.error("math.js: No argument to skewX transform!");break;case"skewY":l[0]?(r=l[0]*Math.PI/180,n=Math.tan(r),i=new g(1,n,0,1,0,0),h=h.append(i)):console.error("math.js: No argument to skewY transform!");break;case"matrix":6===l.length?h=h.append(new g(...l)):console.error("math.js: Incorrect number of arguments for matrix!");break;default:console.error("mesh.js: Unhandled transform type: "+d)}}),h},l=t=>{let e=[],s=t.split(/[ ,]+/);for(let t=0,r=s.length-1;t<r;t+=2)e.push(new x(parseFloat(s[t]),parseFloat(s[t+1])));return e},d=(t,e)=>{for(let s in e)t.setAttribute(s,e[s])},c=(t,e,s,r,n)=>{let o,i,a=[0,0,0,0];for(let h=0;h<3;++h)e[h]<t[h]&&e[h]<s[h]||t[h]<e[h]&&s[h]<e[h]?a[h]=0:(a[h]=.5*((e[h]-t[h])/r+(s[h]-e[h])/n),o=Math.abs(3*(e[h]-t[h])/r),i=Math.abs(3*(s[h]-e[h])/n),a[h]>o?a[h]=o:a[h]>i&&(a[h]=i));return a},u=[[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0],[-3,3,0,0,-2,-1,0,0,0,0,0,0,0,0,0,0],[2,-2,0,0,1,1,0,0,0,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0],[0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0],[0,0,0,0,0,0,0,0,-3,3,0,0,-2,-1,0,0],[0,0,0,0,0,0,0,0,2,-2,0,0,1,1,0,0],[-3,0,3,0,0,0,0,0,-2,0,-1,0,0,0,0,0],[0,0,0,0,-3,0,3,0,0,0,0,0,-2,0,-1,0],[9,-9,-9,9,6,3,-6,-3,6,-6,3,-3,4,2,2,1],[-6,6,6,-6,-3,-3,3,3,-4,4,-2,2,-2,-2,-1,-1],[2,0,-2,0,0,0,0,0,1,0,1,0,0,0,0,0],[0,0,0,0,2,0,-2,0,0,0,0,0,1,0,1,0],[-6,6,6,-6,-4,-2,4,2,-3,3,-3,3,-2,-1,-2,-1],[4,-4,-4,4,2,2,-2,-2,2,-2,2,-2,1,1,1,1]],f=t=>{let e=[];for(let s=0;s<16;++s){e[s]=0;for(let r=0;r<16;++r)e[s]+=u[s][r]*t[r]}return e},p=(t,e,s)=>{const r=e*e,n=s*s,o=e*e*e,i=s*s*s;return t[0]+t[1]*e+t[2]*r+t[3]*o+t[4]*s+t[5]*s*e+t[6]*s*r+t[7]*s*o+t[8]*n+t[9]*n*e+t[10]*n*r+t[11]*n*o+t[12]*i+t[13]*i*e+t[14]*i*r+t[15]*i*o},y=t=>{let e=[],s=[],r=[];for(let s=0;s<4;++s)e[s]=[],e[s][0]=n(t[0][s],t[1][s],t[2][s],t[3][s]),e[s][1]=[],e[s][1].push(...n(...e[s][0][0])),e[s][1].push(...n(...e[s][0][1])),e[s][2]=[],e[s][2].push(...n(...e[s][1][0])),e[s][2].push(...n(...e[s][1][1])),e[s][2].push(...n(...e[s][1][2])),e[s][2].push(...n(...e[s][1][3]));for(let t=0;t<8;++t){s[t]=[];for(let r=0;r<4;++r)s[t][r]=[],s[t][r][0]=n(e[0][2][t][r],e[1][2][t][r],e[2][2][t][r],e[3][2][t][r]),s[t][r][1]=[],s[t][r][1].push(...n(...s[t][r][0][0])),s[t][r][1].push(...n(...s[t][r][0][1])),s[t][r][2]=[],s[t][r][2].push(...n(...s[t][r][1][0])),s[t][r][2].push(...n(...s[t][r][1][1])),s[t][r][2].push(...n(...s[t][r][1][2])),s[t][r][2].push(...n(...s[t][r][1][3]))}for(let t=0;t<8;++t){r[t]=[];for(let e=0;e<8;++e)r[t][e]=[],r[t][e][0]=s[t][0][2][e],r[t][e][1]=s[t][1][2][e],r[t][e][2]=s[t][2][2][e],r[t][e][3]=s[t][3][2][e]}return r};class x{constructor(t,e){this.x=t||0,this.y=e||0}toString(){return`(x=${this.x}, y=${this.y})`}clone(){return new x(this.x,this.y)}add(t){return new x(this.x+t.x,this.y+t.y)}scale(t){return void 0===t.x?new x(this.x*t,this.y*t):new x(this.x*t.x,this.y*t.y)}distSquared(t){let e=this.x-t.x,s=this.y-t.y;return e*e+s*s}transform(t){let e=this.x*t.a+this.y*t.c+t.e,s=this.x*t.b+this.y*t.d+t.f;return new x(e,s)}}class g{constructor(t,e,s,r,n,o){void 0===t?(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0):(this.a=t,this.b=e,this.c=s,this.d=r,this.e=n,this.f=o)}toString(){return`affine: ${this.a} ${this.c} ${this.e} \n ${this.b} ${this.d} ${this.f}`}append(t){t instanceof g||console.error("mesh.js: argument to Affine.append is not affine!");let e=this.a*t.a+this.c*t.b,s=this.b*t.a+this.d*t.b,r=this.a*t.c+this.c*t.d,n=this.b*t.c+this.d*t.d,o=this.a*t.e+this.c*t.f+this.e,i=this.b*t.e+this.d*t.f+this.f;return new g(e,s,r,n,o,i)}}class w{constructor(t,e){this.nodes=t,this.colors=e}paintCurve(t,e){if(o(this.nodes)>r){const s=n(...this.nodes);let r=[[],[]],o=[[],[]];for(let t=0;t<4;++t)r[0][t]=this.colors[0][t],r[1][t]=(this.colors[0][t]+this.colors[1][t])/2,o[0][t]=r[1][t],o[1][t]=this.colors[1][t];let i=new w(s[0],r),a=new w(s[1],o);i.paintCurve(t,e),a.paintCurve(t,e)}else{let s=Math.round(this.nodes[0].x);if(s>=0&&s<e){let r=4*(~~this.nodes[0].y*e+s);t[r]=Math.round(this.colors[0][0]),t[r+1]=Math.round(this.colors[0][1]),t[r+2]=Math.round(this.colors[0][2]),t[r+3]=Math.round(this.colors[0][3])}}}}class m{constructor(t,e){this.nodes=t,this.colors=e}split(){let t=[[],[],[],[]],e=[[],[],[],[]],s=[[[],[]],[[],[]]],r=[[[],[]],[[],[]]];for(let s=0;s<4;++s){const r=n(this.nodes[0][s],this.nodes[1][s],this.nodes[2][s],this.nodes[3][s]);t[0][s]=r[0][0],t[1][s]=r[0][1],t[2][s]=r[0][2],t[3][s]=r[0][3],e[0][s]=r[1][0],e[1][s]=r[1][1],e[2][s]=r[1][2],e[3][s]=r[1][3]}for(let t=0;t<4;++t)s[0][0][t]=this.colors[0][0][t],s[0][1][t]=this.colors[0][1][t],s[1][0][t]=(this.colors[0][0][t]+this.colors[1][0][t])/2,s[1][1][t]=(this.colors[0][1][t]+this.colors[1][1][t])/2,r[0][0][t]=s[1][0][t],r[0][1][t]=s[1][1][t],r[1][0][t]=this.colors[1][0][t],r[1][1][t]=this.colors[1][1][t];return[new m(t,s),new m(e,r)]}paint(t,e){let s,n=!1;for(let t=0;t<4;++t)if((s=o([this.nodes[0][t],this.nodes[1][t],this.nodes[2][t],this.nodes[3][t]]))>r){n=!0;break}if(n){let s=this.split();s[0].paint(t,e),s[1].paint(t,e)}else{new w([...this.nodes[0]],[...this.colors[0]]).paintCurve(t,e)}}}class b{constructor(t){this.readMesh(t),this.type=t.getAttribute("type")||"bilinear"}readMesh(t){let e=[[]],s=[[]],r=Number(t.getAttribute("x")),n=Number(t.getAttribute("y"));e[0][0]=new x(r,n);let o=t.children;for(let t=0,r=o.length;t<r;++t){e[3*t+1]=[],e[3*t+2]=[],e[3*t+3]=[],s[t+1]=[];let r=o[t].children;for(let n=0,o=r.length;n<o;++n){let o=r[n].children;for(let r=0,i=o.length;r<i;++r){let i=r;0!==t&&++i;let h,d=o[r].getAttribute("path"),c="l";null!=d&&(c=(h=d.match(/\s*([lLcC])\s*(.*)/))[1]);let u=l(h[2]);switch(c){case"l":0===i?(e[3*t][3*n+3]=u[0].add(e[3*t][3*n]),e[3*t][3*n+1]=a(e[3*t][3*n],e[3*t][3*n+3]),e[3*t][3*n+2]=a(e[3*t][3*n+3],e[3*t][3*n])):1===i?(e[3*t+3][3*n+3]=u[0].add(e[3*t][3*n+3]),e[3*t+1][3*n+3]=a(e[3*t][3*n+3],e[3*t+3][3*n+3]),e[3*t+2][3*n+3]=a(e[3*t+3][3*n+3],e[3*t][3*n+3])):2===i?(0===n&&(e[3*t+3][3*n+0]=u[0].add(e[3*t+3][3*n+3])),e[3*t+3][3*n+1]=a(e[3*t+3][3*n],e[3*t+3][3*n+3]),e[3*t+3][3*n+2]=a(e[3*t+3][3*n+3],e[3*t+3][3*n])):(e[3*t+1][3*n]=a(e[3*t][3*n],e[3*t+3][3*n]),e[3*t+2][3*n]=a(e[3*t+3][3*n],e[3*t][3*n]));break;case"L":0===i?(e[3*t][3*n+3]=u[0],e[3*t][3*n+1]=a(e[3*t][3*n],e[3*t][3*n+3]),e[3*t][3*n+2]=a(e[3*t][3*n+3],e[3*t][3*n])):1===i?(e[3*t+3][3*n+3]=u[0],e[3*t+1][3*n+3]=a(e[3*t][3*n+3],e[3*t+3][3*n+3]),e[3*t+2][3*n+3]=a(e[3*t+3][3*n+3],e[3*t][3*n+3])):2===i?(0===n&&(e[3*t+3][3*n+0]=u[0]),e[3*t+3][3*n+1]=a(e[3*t+3][3*n],e[3*t+3][3*n+3]),e[3*t+3][3*n+2]=a(e[3*t+3][3*n+3],e[3*t+3][3*n])):(e[3*t+1][3*n]=a(e[3*t][3*n],e[3*t+3][3*n]),e[3*t+2][3*n]=a(e[3*t+3][3*n],e[3*t][3*n]));break;case"c":0===i?(e[3*t][3*n+1]=u[0].add(e[3*t][3*n]),e[3*t][3*n+2]=u[1].add(e[3*t][3*n]),e[3*t][3*n+3]=u[2].add(e[3*t][3*n])):1===i?(e[3*t+1][3*n+3]=u[0].add(e[3*t][3*n+3]),e[3*t+2][3*n+3]=u[1].add(e[3*t][3*n+3]),e[3*t+3][3*n+3]=u[2].add(e[3*t][3*n+3])):2===i?(e[3*t+3][3*n+2]=u[0].add(e[3*t+3][3*n+3]),e[3*t+3][3*n+1]=u[1].add(e[3*t+3][3*n+3]),0===n&&(e[3*t+3][3*n+0]=u[2].add(e[3*t+3][3*n+3]))):(e[3*t+2][3*n]=u[0].add(e[3*t+3][3*n]),e[3*t+1][3*n]=u[1].add(e[3*t+3][3*n]));break;case"C":0===i?(e[3*t][3*n+1]=u[0],e[3*t][3*n+2]=u[1],e[3*t][3*n+3]=u[2]):1===i?(e[3*t+1][3*n+3]=u[0],e[3*t+2][3*n+3]=u[1],e[3*t+3][3*n+3]=u[2]):2===i?(e[3*t+3][3*n+2]=u[0],e[3*t+3][3*n+1]=u[1],0===n&&(e[3*t+3][3*n+0]=u[2])):(e[3*t+2][3*n]=u[0],e[3*t+1][3*n]=u[1]);break;default:console.error("mesh.js: "+c+" invalid path type.")}if(0===t&&0===n||r>0){let e=window.getComputedStyle(o[r]).stopColor.match(/^rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i),a=window.getComputedStyle(o[r]).stopOpacity,h=255;a&&(h=Math.floor(255*a)),e&&(0===i?(s[t][n]=[],s[t][n][0]=Math.floor(e[1]),s[t][n][1]=Math.floor(e[2]),s[t][n][2]=Math.floor(e[3]),s[t][n][3]=h):1===i?(s[t][n+1]=[],s[t][n+1][0]=Math.floor(e[1]),s[t][n+1][1]=Math.floor(e[2]),s[t][n+1][2]=Math.floor(e[3]),s[t][n+1][3]=h):2===i?(s[t+1][n+1]=[],s[t+1][n+1][0]=Math.floor(e[1]),s[t+1][n+1][1]=Math.floor(e[2]),s[t+1][n+1][2]=Math.floor(e[3]),s[t+1][n+1][3]=h):3===i&&(s[t+1][n]=[],s[t+1][n][0]=Math.floor(e[1]),s[t+1][n][1]=Math.floor(e[2]),s[t+1][n][2]=Math.floor(e[3]),s[t+1][n][3]=h))}}e[3*t+1][3*n+1]=new x,e[3*t+1][3*n+2]=new x,e[3*t+2][3*n+1]=new x,e[3*t+2][3*n+2]=new x,e[3*t+1][3*n+1].x=(-4*e[3*t][3*n].x+6*(e[3*t][3*n+1].x+e[3*t+1][3*n].x)+-2*(e[3*t][3*n+3].x+e[3*t+3][3*n].x)+3*(e[3*t+3][3*n+1].x+e[3*t+1][3*n+3].x)+-1*e[3*t+3][3*n+3].x)/9,e[3*t+1][3*n+2].x=(-4*e[3*t][3*n+3].x+6*(e[3*t][3*n+2].x+e[3*t+1][3*n+3].x)+-2*(e[3*t][3*n].x+e[3*t+3][3*n+3].x)+3*(e[3*t+3][3*n+2].x+e[3*t+1][3*n].x)+-1*e[3*t+3][3*n].x)/9,e[3*t+2][3*n+1].x=(-4*e[3*t+3][3*n].x+6*(e[3*t+3][3*n+1].x+e[3*t+2][3*n].x)+-2*(e[3*t+3][3*n+3].x+e[3*t][3*n].x)+3*(e[3*t][3*n+1].x+e[3*t+2][3*n+3].x)+-1*e[3*t][3*n+3].x)/9,e[3*t+2][3*n+2].x=(-4*e[3*t+3][3*n+3].x+6*(e[3*t+3][3*n+2].x+e[3*t+2][3*n+3].x)+-2*(e[3*t+3][3*n].x+e[3*t][3*n+3].x)+3*(e[3*t][3*n+2].x+e[3*t+2][3*n].x)+-1*e[3*t][3*n].x)/9,e[3*t+1][3*n+1].y=(-4*e[3*t][3*n].y+6*(e[3*t][3*n+1].y+e[3*t+1][3*n].y)+-2*(e[3*t][3*n+3].y+e[3*t+3][3*n].y)+3*(e[3*t+3][3*n+1].y+e[3*t+1][3*n+3].y)+-1*e[3*t+3][3*n+3].y)/9,e[3*t+1][3*n+2].y=(-4*e[3*t][3*n+3].y+6*(e[3*t][3*n+2].y+e[3*t+1][3*n+3].y)+-2*(e[3*t][3*n].y+e[3*t+3][3*n+3].y)+3*(e[3*t+3][3*n+2].y+e[3*t+1][3*n].y)+-1*e[3*t+3][3*n].y)/9,e[3*t+2][3*n+1].y=(-4*e[3*t+3][3*n].y+6*(e[3*t+3][3*n+1].y+e[3*t+2][3*n].y)+-2*(e[3*t+3][3*n+3].y+e[3*t][3*n].y)+3*(e[3*t][3*n+1].y+e[3*t+2][3*n+3].y)+-1*e[3*t][3*n+3].y)/9,e[3*t+2][3*n+2].y=(-4*e[3*t+3][3*n+3].y+6*(e[3*t+3][3*n+2].y+e[3*t+2][3*n+3].y)+-2*(e[3*t+3][3*n].y+e[3*t][3*n+3].y)+3*(e[3*t][3*n+2].y+e[3*t+2][3*n].y)+-1*e[3*t][3*n].y)/9}}this.nodes=e,this.colors=s}paintMesh(t,e){let s=(this.nodes.length-1)/3,r=(this.nodes[0].length-1)/3;if("bilinear"===this.type||s<2||r<2){let n;for(let o=0;o<s;++o)for(let s=0;s<r;++s){let r=[];for(let t=3*o,e=3*o+4;t<e;++t)r.push(this.nodes[t].slice(3*s,3*s+4));let i=[];i.push(this.colors[o].slice(s,s+2)),i.push(this.colors[o+1].slice(s,s+2)),(n=new m(r,i)).paint(t,e)}}else{let n,o,a,h,l,d,u;const x=s,g=r;s++,r++;let w=new Array(s);for(let t=0;t<s;++t){w[t]=new Array(r);for(let e=0;e<r;++e)w[t][e]=[],w[t][e][0]=this.nodes[3*t][3*e],w[t][e][1]=this.colors[t][e]}for(let t=0;t<s;++t)for(let e=0;e<r;++e)0!==t&&t!==x&&(n=i(w[t-1][e][0],w[t][e][0]),o=i(w[t+1][e][0],w[t][e][0]),w[t][e][2]=c(w[t-1][e][1],w[t][e][1],w[t+1][e][1],n,o)),0!==e&&e!==g&&(n=i(w[t][e-1][0],w[t][e][0]),o=i(w[t][e+1][0],w[t][e][0]),w[t][e][3]=c(w[t][e-1][1],w[t][e][1],w[t][e+1][1],n,o));for(let t=0;t<r;++t){w[0][t][2]=[],w[x][t][2]=[];for(let e=0;e<4;++e)n=i(w[1][t][0],w[0][t][0]),o=i(w[x][t][0],w[x-1][t][0]),w[0][t][2][e]=n>0?2*(w[1][t][1][e]-w[0][t][1][e])/n-w[1][t][2][e]:0,w[x][t][2][e]=o>0?2*(w[x][t][1][e]-w[x-1][t][1][e])/o-w[x-1][t][2][e]:0}for(let t=0;t<s;++t){w[t][0][3]=[],w[t][g][3]=[];for(let e=0;e<4;++e)n=i(w[t][1][0],w[t][0][0]),o=i(w[t][g][0],w[t][g-1][0]),w[t][0][3][e]=n>0?2*(w[t][1][1][e]-w[t][0][1][e])/n-w[t][1][3][e]:0,w[t][g][3][e]=o>0?2*(w[t][g][1][e]-w[t][g-1][1][e])/o-w[t][g-1][3][e]:0}for(let s=0;s<x;++s)for(let r=0;r<g;++r){let n=i(w[s][r][0],w[s+1][r][0]),o=i(w[s][r+1][0],w[s+1][r+1][0]),c=i(w[s][r][0],w[s][r+1][0]),x=i(w[s+1][r][0],w[s+1][r+1][0]),g=[[],[],[],[]];for(let t=0;t<4;++t){(d=[])[0]=w[s][r][1][t],d[1]=w[s+1][r][1][t],d[2]=w[s][r+1][1][t],d[3]=w[s+1][r+1][1][t],d[4]=w[s][r][2][t]*n,d[5]=w[s+1][r][2][t]*n,d[6]=w[s][r+1][2][t]*o,d[7]=w[s+1][r+1][2][t]*o,d[8]=w[s][r][3][t]*c,d[9]=w[s+1][r][3][t]*x,d[10]=w[s][r+1][3][t]*c,d[11]=w[s+1][r+1][3][t]*x,d[12]=0,d[13]=0,d[14]=0,d[15]=0,u=f(d);for(let e=0;e<9;++e){g[t][e]=[];for(let s=0;s<9;++s)g[t][e][s]=p(u,e/8,s/8),g[t][e][s]>255?g[t][e][s]=255:g[t][e][s]<0&&(g[t][e][s]=0)}}h=[];for(let t=3*s,e=3*s+4;t<e;++t)h.push(this.nodes[t].slice(3*r,3*r+4));l=y(h);for(let s=0;s<8;++s)for(let r=0;r<8;++r)(a=new m(l[s][r],[[[g[0][s][r],g[1][s][r],g[2][s][r],g[3][s][r]],[g[0][s][r+1],g[1][s][r+1],g[2][s][r+1],g[3][s][r+1]]],[[g[0][s+1][r],g[1][s+1][r],g[2][s+1][r],g[3][s+1][r]],[g[0][s+1][r+1],g[1][s+1][r+1],g[2][s+1][r+1],g[3][s+1][r+1]]]])).paint(t,e)}}}transform(t){if(t instanceof x)for(let e=0,s=this.nodes.length;e<s;++e)for(let s=0,r=this.nodes[0].length;s<r;++s)this.nodes[e][s]=this.nodes[e][s].add(t);else if(t instanceof g)for(let e=0,s=this.nodes.length;e<s;++e)for(let s=0,r=this.nodes[0].length;s<r;++s)this.nodes[e][s]=this.nodes[e][s].transform(t)}scale(t){for(let e=0,s=this.nodes.length;e<s;++e)for(let s=0,r=this.nodes[0].length;s<r;++s)this.nodes[e][s]=this.nodes[e][s].scale(t)}}document.querySelectorAll("rect,circle,ellipse,path,text").forEach((r,n)=>{let o=r.getAttribute("id");o||(o="patchjs_shape"+n,r.setAttribute("id",o));const i=r.style.fill.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/),a=r.style.stroke.match(/^url\(\s*"?\s*#([^\s"]+)"?\s*\)/);if(i&&i[1]){const a=document.getElementById(i[1]);if(a&&"meshgradient"===a.nodeName){const i=r.getBBox();let l=document.createElementNS(s,"canvas");d(l,{width:i.width,height:i.height});const c=l.getContext("2d");let u=c.createImageData(i.width,i.height);const f=new b(a);"objectBoundingBox"===a.getAttribute("gradientUnits")&&f.scale(new x(i.width,i.height));const p=a.getAttribute("gradientTransform");null!=p&&f.transform(h(p)),"userSpaceOnUse"===a.getAttribute("gradientUnits")&&f.transform(new x(-i.x,-i.y)),f.paintMesh(u.data,l.width),c.putImageData(u,0,0);const y=document.createElementNS(t,"image");d(y,{width:i.width,height:i.height,x:i.x,y:i.y});let g=l.toDataURL();y.setAttributeNS(e,"xlink:href",g),r.parentNode.insertBefore(y,r),r.style.fill="none";const w=document.createElementNS(t,"use");w.setAttributeNS(e,"xlink:href","#"+o);const m="patchjs_clip"+n,M=document.createElementNS(t,"clipPath");M.setAttribute("id",m),M.appendChild(w),r.parentElement.insertBefore(M,r),y.setAttribute("clip-path","url(#"+m+")"),u=null,l=null,g=null}}if(a&&a[1]){const o=document.getElementById(a[1]);if(o&&"meshgradient"===o.nodeName){const i=parseFloat(r.style.strokeWidth.slice(0,-2))*(parseFloat(r.style.strokeMiterlimit)||parseFloat(r.getAttribute("stroke-miterlimit"))||1),a=r.getBBox(),l=Math.trunc(a.width+i),c=Math.trunc(a.height+i),u=Math.trunc(a.x-i/2),f=Math.trunc(a.y-i/2);let p=document.createElementNS(s,"canvas");d(p,{width:l,height:c});const y=p.getContext("2d");let g=y.createImageData(l,c);const w=new b(o);"objectBoundingBox"===o.getAttribute("gradientUnits")&&w.scale(new x(l,c));const m=o.getAttribute("gradientTransform");null!=m&&w.transform(h(m)),"userSpaceOnUse"===o.getAttribute("gradientUnits")&&w.transform(new x(-u,-f)),w.paintMesh(g.data,p.width),y.putImageData(g,0,0);const M=document.createElementNS(t,"image");d(M,{width:l,height:c,x:0,y:0});let S=p.toDataURL();M.setAttributeNS(e,"xlink:href",S);const k="pattern_clip"+n,A=document.createElementNS(t,"pattern");d(A,{id:k,patternUnits:"userSpaceOnUse",width:l,height:c,x:u,y:f}),A.appendChild(M),o.parentNode.appendChild(A),r.style.stroke="url(#"+k+")",g=null,p=null,S=null}}})}();
+)====="
diff --git a/src/extension/internal/pov-out.cpp b/src/extension/internal/pov-out.cpp
new file mode 100644
index 0000000..96daafb
--- /dev/null
+++ b/src/extension/internal/pov-out.cpp
@@ -0,0 +1,744 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A simple utility for exporting Inkscape svg Shapes as PovRay bezier
+ * prisms. Note that this is output-only, and would thus seem to be
+ * better placed as an 'export' rather than 'output'. However, Export
+ * handles all or partial documents, while this outputs ALL shapes in
+ * the current SVG document.
+ *
+ * For information on the PovRay file format, see:
+ * http://www.povray.org
+ *
+ * Authors:
+ * Bob Jamison <ishmal@inkscape.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2004-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "pov-out.h"
+#include <inkscape.h>
+#include <inkscape-version.h>
+#include <display/curve.h>
+#include <extension/system.h>
+#include <2geom/pathvector.h>
+#include <2geom/rect.h>
+#include <2geom/curves.h>
+#include "helper/geom.h"
+#include "helper/geom-curves.h"
+#include <io/sys.h>
+
+#include "object/sp-root.h"
+#include "object/sp-path.h"
+#include "style.h"
+
+#include <string>
+#include <cstdio>
+#include <cstdarg>
+#include "document.h"
+#include "extension/extension.h"
+
+
+namespace Inkscape
+{
+namespace Extension
+{
+namespace Internal
+{
+
+
+//########################################################################
+//# M E S S A G E S
+//########################################################################
+
+static void err(const char *fmt, ...)
+{
+ va_list args;
+ g_log(nullptr, G_LOG_LEVEL_WARNING, "Pov-out err: ");
+ va_start(args, fmt);
+ g_logv(nullptr, G_LOG_LEVEL_WARNING, fmt, args);
+ va_end(args);
+ g_log(nullptr, G_LOG_LEVEL_WARNING, "\n");
+}
+
+
+
+
+//########################################################################
+//# U T I L I T Y
+//########################################################################
+
+
+
+static double effective_opacity(SPItem const *item)
+{
+ // TODO investigate this. The early return seems that it would abort early.
+ // Plus is will emit a warning, which may not be proper here.
+ double ret = 1.0;
+ for (SPObject const *obj = item; obj; obj = obj->parent) {
+ g_return_val_if_fail(obj->style, ret);
+ ret *= SP_SCALE24_TO_FLOAT(obj->style->opacity.value);
+ }
+ return ret;
+}
+
+
+
+
+
+//########################################################################
+//# OUTPUT FORMATTING
+//########################################################################
+
+PovOutput::PovOutput() :
+ outbuf (),
+ nrNodes (0),
+ nrSegments (0),
+ nrShapes (0),
+ idIndex (0),
+ minx (0),
+ miny (0),
+ maxx (0),
+ maxy (0)
+{
+}
+
+/**
+ * We want to control floating output format
+ */
+static PovOutput::String dstr(double d)
+{
+ char dbuf[G_ASCII_DTOSTR_BUF_SIZE+1];
+ g_ascii_formatd(dbuf, G_ASCII_DTOSTR_BUF_SIZE,
+ "%.8f", (gdouble)d);
+ PovOutput::String s = dbuf;
+ return s;
+}
+
+#define DSTR(d) (dstr(d).c_str())
+
+
+/**
+ * Output data to the buffer, printf()-style
+ */
+void PovOutput::out(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ gchar *output = g_strdup_vprintf(fmt, args);
+ va_end(args);
+ outbuf.append(output);
+ g_free(output);
+}
+
+
+
+
+
+/**
+ * Output a 2d vector
+ */
+void PovOutput::vec2(double a, double b)
+{
+ out("<%s, %s>", DSTR(a), DSTR(b));
+}
+
+
+
+/**
+ * Output a 3d vector
+ */
+void PovOutput::vec3(double a, double b, double c)
+{
+ out("<%s, %s, %s>", DSTR(a), DSTR(b), DSTR(c));
+}
+
+
+
+/**
+ * Output a v4d ector
+ */
+void PovOutput::vec4(double a, double b, double c, double d)
+{
+ out("<%s, %s, %s, %s>", DSTR(a), DSTR(b), DSTR(c), DSTR(d));
+}
+
+
+
+/**
+ * Output an rgbf color vector
+ */
+void PovOutput::rgbf(double r, double g, double b, double f)
+{
+ //"rgbf < %1.3f, %1.3f, %1.3f %1.3f>"
+ out("rgbf ");
+ vec4(r, g, b, f);
+}
+
+
+
+/**
+ * Output one bezier's start, start-control, end-control, and end nodes
+ */
+void PovOutput::segment(int segNr,
+ double startX, double startY,
+ double startCtrlX, double startCtrlY,
+ double endCtrlX, double endCtrlY,
+ double endX, double endY)
+{
+ //" /*%4d*/ <%f, %f>, <%f, %f>, <%f,%f>, <%f,%f>"
+ out(" /*%4d*/ ", segNr);
+ vec2(startX, startY);
+ out(", ");
+ vec2(startCtrlX, startCtrlY);
+ out(", ");
+ vec2(endCtrlX, endCtrlY);
+ out(", ");
+ vec2(endX, endY);
+}
+
+
+
+
+
+/**
+ * Output the file header
+ */
+bool PovOutput::doHeader()
+{
+ time_t tim = time(nullptr);
+ out("/*###################################################################\n");
+ out("### This PovRay document was generated by Inkscape\n");
+ out("### http://www.inkscape.org\n");
+ out("### Created: %s", ctime(&tim));
+ out("### Version: %s\n", Inkscape::version_string);
+ out("#####################################################################\n");
+ out("### NOTES:\n");
+ out("### ============\n");
+ out("### POVRay information can be found at\n");
+ out("### http://www.povray.org\n");
+ out("###\n");
+ out("### The 'AllShapes' objects at the bottom are provided as a\n");
+ out("### preview of how the output would look in a trace. However,\n");
+ out("### the main intent of this file is to provide the individual\n");
+ out("### shapes for inclusion in a POV project.\n");
+ out("###\n");
+ out("### For an example of how to use this file, look at\n");
+ out("### share/examples/istest.pov\n");
+ out("###\n");
+ out("### If you have any problems with this output, please see the\n");
+ out("### Inkscape project at http://www.inkscape.org, or visit\n");
+ out("### the #inkscape channel on irc.freenode.net . \n");
+ out("###\n");
+ out("###################################################################*/\n");
+ out("\n\n");
+ out("/*###################################################################\n");
+ out("## Exports in this file\n");
+ out("##==========================\n");
+ out("## Shapes : %d\n", nrShapes);
+ out("## Segments : %d\n", nrSegments);
+ out("## Nodes : %d\n", nrNodes);
+ out("###################################################################*/\n");
+ out("\n\n\n");
+ return true;
+}
+
+
+
+/**
+ * Output the file footer
+ */
+bool PovOutput::doTail()
+{
+ out("\n\n");
+ out("/*###################################################################\n");
+ out("### E N D F I L E\n");
+ out("###################################################################*/\n");
+ out("\n\n");
+ return true;
+}
+
+
+
+/**
+ * Output the curve data to buffer
+ */
+bool PovOutput::doCurve(SPItem *item, const String &id)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ //### Get the Shape
+ if (!SP_IS_SHAPE(item))//Bulia's suggestion. Allow all shapes
+ return true;
+
+ SPShape *shape = SP_SHAPE(item);
+ if (shape->curve()->is_empty()) {
+ return true;
+ }
+
+ nrShapes++;
+
+ PovShapeInfo shapeInfo;
+ shapeInfo.id = id;
+ shapeInfo.color = "";
+
+ //Try to get the fill color of the shape
+ SPStyle *style = shape->style;
+ /* fixme: Handle other fill types, even if this means translating gradients to a single
+ flat colour. */
+ if (style)
+ {
+ if (style->fill.isColor())
+ {
+ // see color.h for how to parse SPColor
+ float rgb[3];
+ style->fill.value.color.get_rgb_floatv(rgb);
+ double const dopacity = ( SP_SCALE24_TO_FLOAT(style->fill_opacity.value)
+ * effective_opacity(shape) );
+ //gchar *str = g_strdup_printf("rgbf < %1.3f, %1.3f, %1.3f %1.3f>",
+ // rgb[0], rgb[1], rgb[2], 1.0 - dopacity);
+ String rgbf = "rgbf <";
+ rgbf.append(dstr(rgb[0])); rgbf.append(", ");
+ rgbf.append(dstr(rgb[1])); rgbf.append(", ");
+ rgbf.append(dstr(rgb[2])); rgbf.append(", ");
+ rgbf.append(dstr(1.0 - dopacity)); rgbf.append(">");
+ shapeInfo.color += rgbf;
+ }
+ }
+
+ povShapes.push_back(shapeInfo); //passed all tests. save the info
+
+ // convert the path to only lineto's and cubic curveto's:
+ Geom::Affine tf = item->i2dt_affine();
+ Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers(shape->curve()->get_pathvector() * tf);
+
+ /*
+ * We need to know the number of segments (NR_CURVETOs/LINETOs, including
+ * closing line segment) before we write out segment data. Since we are
+ * going to skip degenerate (zero length) paths, we need to loop over all
+ * subpaths and segments first.
+ */
+ int segmentCount = 0;
+ /**
+ * For all Subpaths in the <path>
+ */
+ for (const auto & pit : pathv)
+ {
+ /**
+ * For all segments in the subpath, including extra closing segment defined by 2geom
+ */
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_closed(); ++cit)
+ {
+
+ // Skip zero length segments.
+ if( !cit->isDegenerate() ) ++segmentCount;
+ }
+ }
+
+ out("/*###################################################\n");
+ out("### PRISM: %s\n", id.c_str());
+ out("###################################################*/\n");
+ out("#declare %s = prism {\n", id.c_str());
+ out(" linear_sweep\n");
+ out(" bezier_spline\n");
+ out(" 1.0, //top\n");
+ out(" 0.0, //bottom\n");
+ out(" %d //nr points\n", segmentCount * 4);
+ int segmentNr = 0;
+
+ nrSegments += segmentCount;
+
+ /**
+ * at moment of writing, 2geom lacks proper initialization of empty intervals in rect...
+ */
+ Geom::Rect cminmax( pathv.front().initialPoint(), pathv.front().initialPoint() );
+
+
+ /**
+ * For all Subpaths in the <path>
+ */
+ for (const auto & pit : pathv)
+ {
+
+ cminmax.expandTo(pit.initialPoint());
+
+ /**
+ * For all segments in the subpath, including extra closing segment defined by 2geom
+ */
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_closed(); ++cit)
+ {
+
+ // Skip zero length segments
+ if( cit->isDegenerate() )
+ continue;
+
+ if( is_straight_curve(*cit) )
+ {
+ Geom::Point p0 = cit->initialPoint();
+ Geom::Point p1 = cit->finalPoint();
+ segment(segmentNr++,
+ p0[X], p0[Y], p0[X], p0[Y], p1[X], p1[Y], p1[X], p1[Y] );
+ nrNodes += 8;
+ }
+ else if(Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const*>(&*cit))
+ {
+ std::vector<Geom::Point> points = cubic->controlPoints();
+ Geom::Point p0 = points[0];
+ Geom::Point p1 = points[1];
+ Geom::Point p2 = points[2];
+ Geom::Point p3 = points[3];
+ segment(segmentNr++,
+ p0[X],p0[Y], p1[X],p1[Y], p2[X],p2[Y], p3[X],p3[Y]);
+ nrNodes += 8;
+ }
+ else
+ {
+ err("logical error, because pathv_to_linear_and_cubic_beziers was used");
+ return false;
+ }
+
+ if (segmentNr < segmentCount)
+ out(",\n");
+ else
+ out("\n");
+ if (segmentNr > segmentCount)
+ {
+ err("Too many segments");
+ return false;
+ }
+
+ cminmax.expandTo(cit->finalPoint());
+
+ }
+ }
+
+ out("}\n");
+
+ double cminx = cminmax.min()[X];
+ double cmaxx = cminmax.max()[X];
+ double cminy = cminmax.min()[Y];
+ double cmaxy = cminmax.max()[Y];
+
+ out("#declare %s_MIN_X = %s;\n", id.c_str(), DSTR(cminx));
+ out("#declare %s_CENTER_X = %s;\n", id.c_str(), DSTR((cmaxx+cminx)/2.0));
+ out("#declare %s_MAX_X = %s;\n", id.c_str(), DSTR(cmaxx));
+ out("#declare %s_WIDTH = %s;\n", id.c_str(), DSTR(cmaxx-cminx));
+ out("#declare %s_MIN_Y = %s;\n", id.c_str(), DSTR(cminy));
+ out("#declare %s_CENTER_Y = %s;\n", id.c_str(), DSTR((cmaxy+cminy)/2.0));
+ out("#declare %s_MAX_Y = %s;\n", id.c_str(), DSTR(cmaxy));
+ out("#declare %s_HEIGHT = %s;\n", id.c_str(), DSTR(cmaxy-cminy));
+ if (shapeInfo.color.length()>0)
+ out("#declare %s_COLOR = %s;\n",
+ id.c_str(), shapeInfo.color.c_str());
+ out("/*###################################################\n");
+ out("### end %s\n", id.c_str());
+ out("###################################################*/\n\n\n\n");
+
+ if (cminx < minx)
+ minx = cminx;
+ if (cmaxx > maxx)
+ maxx = cmaxx;
+ if (cminy < miny)
+ miny = cminy;
+ if (cmaxy > maxy)
+ maxy = cmaxy;
+
+ return true;
+}
+
+/**
+ * Descend the svg tree recursively, translating data
+ */
+bool PovOutput::doTreeRecursive(SPDocument *doc, SPObject *obj)
+{
+
+ String id;
+ if (!obj->getId())
+ {
+ char buf[16];
+ sprintf(buf, "id%d", idIndex++);
+ id = buf;
+ }
+ else
+ {
+ id = obj->getId();
+ }
+
+ if (SP_IS_ITEM(obj))
+ {
+ SPItem *item = SP_ITEM(obj);
+ if (!doCurve(item, id))
+ return false;
+ }
+
+ /**
+ * Descend into children
+ */
+ for (auto &child: obj->children)
+ {
+ if (!doTreeRecursive(doc, &child))
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Output the curve data to buffer
+ */
+bool PovOutput::doTree(SPDocument *doc)
+{
+ double bignum = 1000000.0;
+ minx = bignum;
+ maxx = -bignum;
+ miny = bignum;
+ maxy = -bignum;
+
+ if (!doTreeRecursive(doc, doc->getRoot()))
+ return false;
+
+ //## Let's make a union of all of the Shapes
+ if (!povShapes.empty())
+ {
+ String id = "AllShapes";
+ char *pfx = (char *)id.c_str();
+ out("/*###################################################\n");
+ out("### UNION OF ALL SHAPES IN DOCUMENT\n");
+ out("###################################################*/\n");
+ out("\n\n");
+ out("/**\n");
+ out(" * Allow the user to redefine the finish{}\n");
+ out(" * by declaring it before #including this file\n");
+ out(" */\n");
+ out("#ifndef (%s_Finish)\n", pfx);
+ out("#declare %s_Finish = finish {\n", pfx);
+ out(" phong 0.5\n");
+ out(" reflection 0.3\n");
+ out(" specular 0.5\n");
+ out("}\n");
+ out("#end\n");
+ out("\n\n");
+ out("#declare %s = union {\n", id.c_str());
+ for (auto & povShape : povShapes)
+ {
+ out(" object { %s\n", povShape.id.c_str());
+ out(" texture { \n");
+ if (povShape.color.length()>0)
+ out(" pigment { %s }\n", povShape.color.c_str());
+ else
+ out(" pigment { rgb <0,0,0> }\n");
+ out(" finish { %s_Finish }\n", pfx);
+ out(" } \n");
+ out(" } \n");
+ }
+ out("}\n\n\n\n");
+
+
+ double zinc = 0.2 / (double)povShapes.size();
+ out("/*#### Same union, but with Z-diffs (actually Y in pov) ####*/\n");
+ out("\n\n");
+ out("/**\n");
+ out(" * Allow the user to redefine the Z-Increment\n");
+ out(" */\n");
+ out("#ifndef (AllShapes_Z_Increment)\n");
+ out("#declare AllShapes_Z_Increment = %s;\n", DSTR(zinc));
+ out("#end\n");
+ out("\n");
+ out("#declare AllShapes_Z_Scale = 1.0;\n");
+ out("\n\n");
+ out("#declare %s_Z = union {\n", pfx);
+
+ for (auto & povShape : povShapes)
+ {
+ out(" object { %s\n", povShape.id.c_str());
+ out(" texture { \n");
+ if (povShape.color.length()>0)
+ out(" pigment { %s }\n", povShape.color.c_str());
+ else
+ out(" pigment { rgb <0,0,0> }\n");
+ out(" finish { %s_Finish }\n", pfx);
+ out(" } \n");
+ out(" scale <1, %s_Z_Scale, 1>\n", pfx);
+ out(" } \n");
+ out("#declare %s_Z_Scale = %s_Z_Scale + %s_Z_Increment;\n\n",
+ pfx, pfx, pfx);
+ }
+
+ out("}\n");
+
+ out("#declare %s_MIN_X = %s;\n", pfx, DSTR(minx));
+ out("#declare %s_CENTER_X = %s;\n", pfx, DSTR((maxx+minx)/2.0));
+ out("#declare %s_MAX_X = %s;\n", pfx, DSTR(maxx));
+ out("#declare %s_WIDTH = %s;\n", pfx, DSTR(maxx-minx));
+ out("#declare %s_MIN_Y = %s;\n", pfx, DSTR(miny));
+ out("#declare %s_CENTER_Y = %s;\n", pfx, DSTR((maxy+miny)/2.0));
+ out("#declare %s_MAX_Y = %s;\n", pfx, DSTR(maxy));
+ out("#declare %s_HEIGHT = %s;\n", pfx, DSTR(maxy-miny));
+ out("/*##############################################\n");
+ out("### end %s\n", id.c_str());
+ out("##############################################*/\n");
+ out("\n\n");
+ }
+
+ return true;
+}
+
+
+//########################################################################
+//# M A I N O U T P U T
+//########################################################################
+
+
+
+/**
+ * Set values back to initial state
+ */
+void PovOutput::reset()
+{
+ nrNodes = 0;
+ nrSegments = 0;
+ nrShapes = 0;
+ idIndex = 0;
+ outbuf.clear();
+ povShapes.clear();
+}
+
+
+
+/**
+ * Saves the Shapes of an Inkscape SVG file as PovRay spline definitions
+ */
+void PovOutput::saveDocument(SPDocument *doc, gchar const *filename_utf8)
+{
+ reset();
+
+ //###### SAVE IN POV FORMAT TO BUFFER
+ //# Lets do the curves first, to get the stats
+ if (!doTree(doc))
+ {
+ err("Could not output curves for %s", filename_utf8);
+ return;
+ }
+
+ String curveBuf = outbuf;
+ outbuf.clear();
+
+ if (!doHeader())
+ {
+ err("Could not write header for %s", filename_utf8);
+ return;
+ }
+
+ outbuf.append(curveBuf);
+
+ if (!doTail())
+ {
+ err("Could not write footer for %s", filename_utf8);
+ return;
+ }
+
+
+
+
+ //###### WRITE TO FILE
+ Inkscape::IO::dump_fopen_call(filename_utf8, "L");
+ FILE *f = Inkscape::IO::fopen_utf8name(filename_utf8, "w");
+ if (!f)
+ return;
+
+ for (String::iterator iter = outbuf.begin() ; iter!=outbuf.end(); ++iter)
+ {
+ int ch = *iter;
+ fputc(ch, f);
+ }
+
+ fclose(f);
+}
+
+
+
+
+//########################################################################
+//# EXTENSION API
+//########################################################################
+
+
+
+#include "clear-n_.h"
+
+
+
+/**
+ * API call to save document
+*/
+void
+PovOutput::save(Inkscape::Extension::Output */*mod*/,
+ SPDocument *doc, gchar const *filename_utf8)
+{
+ /* See comments in JavaFSOutput::save re the name `filename_utf8'. */
+ saveDocument(doc, filename_utf8);
+}
+
+
+
+/**
+ * Make sure that we are in the database
+ */
+bool PovOutput::check (Inkscape::Extension::Extension */*module*/)
+{
+ /* We don't need a Key
+ if (NULL == Inkscape::Extension::db.get(SP_MODULE_KEY_OUTPUT_POV))
+ return FALSE;
+ */
+
+ return true;
+}
+
+
+
+/**
+ * This is the definition of PovRay output. This function just
+ * calls the extension system with the memory allocated XML that
+ * describes the data.
+*/
+void
+PovOutput::init()
+{
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("PovRay Output") "</name>\n"
+ "<id>org.inkscape.output.pov</id>\n"
+ "<output>\n"
+ "<extension>.pov</extension>\n"
+ "<mimetype>text/x-povray-script</mimetype>\n"
+ "<filetypename>" N_("PovRay (*.pov) (paths and shapes only)") "</filetypename>\n"
+ "<filetypetooltip>" N_("PovRay Raytracer File") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>",
+ new PovOutput());
+ // clang-format on
+}
+
+
+
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/pov-out.h b/src/extension/internal/pov-out.h
new file mode 100644
index 0000000..3dee88b
--- /dev/null
+++ b/src/extension/internal/pov-out.h
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * A simple utility for exporting Inkscape svg Shapes as PovRay bezier
+ * prisms. Note that this is output-only, and would thus seem to be
+ * better placed as an 'export' rather than 'output'. However, Export
+ * handles all or partial documents, while this outputs ALL shapes in
+ * the current SVG document.
+ *
+ * Authors:
+ * Bob Jamison <ishmal@inkscape.org>
+ *
+ * Copyright (C) 2004-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef EXTENSION_INTERNAL_POV_OUT_H
+#define EXTENSION_INTERNAL_POV_OUT_H
+
+#include <glib.h>
+#include "extension/implementation/implementation.h"
+
+class SPObject;
+class SPItem;
+
+namespace Inkscape
+{
+namespace Extension
+{
+namespace Internal
+{
+
+
+
+/**
+ * Output bezier splines in POVRay format.
+ *
+ * For information, @see:
+ * http://www.povray.org
+ */
+class PovOutput : public Inkscape::Extension::Implementation::Implementation
+{
+
+
+public:
+
+ PovOutput();
+
+ /**
+ * Our internal String definition
+ */
+ typedef Glib::ustring String;
+
+
+ /**
+ * Check whether we can actually output using this module
+ */
+ bool check (Inkscape::Extension::Extension * module) override;
+
+ /**
+ * API call to perform the output to a file
+ */
+ void save(Inkscape::Extension::Output *mod,
+ SPDocument *doc, gchar const *filename) override;
+
+ /**
+ * Inkscape runtime startup call.
+ */
+ static void init();
+
+ /**
+ * Reset variables to initial state
+ */
+ void reset();
+
+private:
+
+ /**
+ * Format text to our output buffer
+ */
+ void out(const char *fmt, ...) G_GNUC_PRINTF(2,3);
+
+ /**
+ * Output a 2d vector
+ */
+ void vec2(double a, double b);
+
+ /**
+ * Output a 3d vector
+ */
+ void vec3(double a, double b, double c);
+
+ /**
+ * Output a 4d vector
+ */
+ void vec4(double a, double b, double c, double d);
+
+ /**
+ * Output an rgbf color vector
+ */
+ void rgbf(double r, double g, double b, double f);
+
+ /**
+ * Output one bezier's start, start-control,
+ * end-control, and end nodes
+ */
+ void segment(int segNr, double a0, double a1,
+ double b0, double b1,
+ double c0, double c1,
+ double d0, double d1);
+
+
+ /**
+ * Output the file header
+ */
+ bool doHeader();
+
+ /**
+ * Output the file footer
+ */
+ bool doTail();
+
+ /**
+ * Output the SVG document's curve data as POV curves
+ */
+ bool doCurve(SPItem *item, const String &id);
+ bool doTreeRecursive(SPDocument *doc, SPObject *obj);
+ bool doTree(SPDocument *doc);
+
+ /**
+ * Actual method to save document
+ */
+ void saveDocument(SPDocument *doc, gchar const *filename);
+
+
+ /**
+ * used for saving information about shapes
+ */
+ class PovShapeInfo
+ {
+ public:
+ PovShapeInfo()
+ = default;
+ PovShapeInfo(const PovShapeInfo &other)
+ { assign(other); }
+ PovShapeInfo& operator=(const PovShapeInfo &other)
+ { assign(other); return *this; }
+ virtual ~PovShapeInfo()
+ = default;
+ String id;
+ String color;
+
+ private:
+ void assign(const PovShapeInfo &other)
+ {
+ id = other.id;
+ color = other.color;
+ }
+ };
+
+ //A list for saving information about the shapes
+ std::vector<PovShapeInfo> povShapes;
+
+ //For formatted output
+ String outbuf;
+
+ //For statistics
+ int nrNodes;
+ int nrSegments;
+ int nrShapes;
+ int idIndex;
+
+ double minx;
+ double miny;
+ double maxx;
+ double maxy;
+
+};
+
+
+
+
+} // namespace Internal
+} // namespace Extension
+} // namespace Inkscape
+
+
+
+#endif /* EXTENSION_INTERNAL_POV_OUT_H */
+
diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp
new file mode 100644
index 0000000..461d174
--- /dev/null
+++ b/src/extension/internal/svg.cpp
@@ -0,0 +1,1056 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is the code that moves all of the SVG loading and saving into
+ * the module format. Really Inkscape is built to handle these formats
+ * internally, so this is just calling those internal functions.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2002-2003 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm.h>
+
+#include <giomm/file.h>
+#include <giomm/action.h>
+
+#include "document.h"
+#include "inkscape.h"
+#include "inkscape-application.h"
+#include "preferences.h"
+#include "extension/output.h"
+#include "extension/input.h"
+#include "extension/system.h"
+#include "file.h"
+#include "svg.h"
+#include "file.h"
+#include "display/cairo-utils.h"
+#include "extension/system.h"
+#include "extension/output.h"
+#include "xml/attribute-record.h"
+#include "xml/simple-document.h"
+
+#include "object/sp-image.h"
+#include "object/sp-root.h"
+#include "object/sp-text.h"
+
+#include "util/units.h"
+#include "selection-chemistry.h"
+
+// TODO due to internal breakage in glibmm headers, this must be last:
+#include <glibmm/i18n.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+#include "clear-n_.h"
+
+using Inkscape::XML::Node;
+
+/*
+ * Removes all sodipodi and inkscape elements and attributes from an xml tree.
+ * used to make plain svg output.
+ */
+static void pruneExtendedNamespaces( Inkscape::XML::Node *repr )
+{
+ if (repr) {
+ if ( repr->type() == Inkscape::XML::NodeType::ELEMENT_NODE ) {
+ std::vector<gchar const*> attrsRemoved;
+ for ( const auto & it : repr->attributeList()) {
+ const gchar* attrName = g_quark_to_string(it.key);
+ if ((strncmp("inkscape:", attrName, 9) == 0) || (strncmp("sodipodi:", attrName, 9) == 0)) {
+ attrsRemoved.push_back(attrName);
+ }
+ }
+ // Can't change the set we're iterating over while we are iterating.
+ for (auto & it : attrsRemoved) {
+ repr->removeAttribute(it);
+ }
+ }
+
+ std::vector<Inkscape::XML::Node *> nodesRemoved;
+ for ( Node *child = repr->firstChild(); child; child = child->next() ) {
+ if((strncmp("inkscape:", child->name(), 9) == 0) || strncmp("sodipodi:", child->name(), 9) == 0) {
+ nodesRemoved.push_back(child);
+ } else {
+ pruneExtendedNamespaces(child);
+ }
+ }
+ for (auto & it : nodesRemoved) {
+ repr->removeChild(it);
+ }
+ }
+}
+
+/*
+ * Similar to the above sodipodi and inkscape prune, but used on all documents
+ * to remove problematic elements (for example Adobe's i:pgf tag) only removes
+ * known garbage tags.
+ */
+static void pruneProprietaryGarbage( Inkscape::XML::Node *repr )
+{
+ if (repr) {
+ std::vector<Inkscape::XML::Node *> nodesRemoved;
+ for ( Node *child = repr->firstChild(); child; child = child->next() ) {
+ if((strncmp("i:pgf", child->name(), 5) == 0)) {
+ nodesRemoved.push_back(child);
+ g_warning( "An Adobe proprietary tag was found which is known to cause issues. It was removed before saving.");
+ } else {
+ pruneProprietaryGarbage(child);
+ }
+ }
+ for (auto & it : nodesRemoved) {
+ repr->removeChild(it);
+ }
+ }
+}
+
+/**
+ * \return None
+ *
+ * \brief Create new markers where necessary to simulate the SVG 2 marker attribute 'orient'
+ * value 'auto-start-reverse'.
+ *
+ * \param repr The current element to check.
+ * \param defs A pointer to the <defs> element.
+ * \param css The properties of the element to check.
+ * \param property Which property to check, either 'marker' or 'marker-start'.
+ *
+ */
+static void remove_marker_auto_start_reverse(Inkscape::XML::Node *repr,
+ Inkscape::XML::Node *defs,
+ SPCSSAttr *css,
+ Glib::ustring const &property)
+{
+ Glib::ustring value = sp_repr_css_property (css, property.c_str(), "");
+
+ if (!value.empty()) {
+
+ // Find reference <marker>
+ static Glib::RefPtr<Glib::Regex> regex = Glib::Regex::create("url\\(#([^\\)]*)\\)");
+ Glib::MatchInfo matchInfo;
+ regex->match(value, matchInfo);
+
+ if (matchInfo.matches()) {
+
+ auto marker_name = matchInfo.fetch(1).raw();
+ Inkscape::XML::Node *marker = sp_repr_lookup_child (defs, "id", marker_name.c_str());
+ if (marker) {
+
+ // Does marker use "auto-start-reverse"?
+ if (strncmp(marker->attribute("orient"), "auto-start-reverse", 17)==0) {
+
+ // See if a reversed marker already exists.
+ auto marker_name_reversed = marker_name + "_reversed";
+ Inkscape::XML::Node *marker_reversed =
+ sp_repr_lookup_child (defs, "id", marker_name_reversed.c_str());
+
+ if (!marker_reversed) {
+
+ // No reversed marker, need to create!
+ marker_reversed = repr->document()->createElement("svg:marker");
+
+ // Copy attributes
+ for (const auto & iter : marker->attributeList()) {
+ marker_reversed->setAttribute(g_quark_to_string(iter.key), iter.value);
+ }
+
+ // Override attributes
+ marker_reversed->setAttribute("id", marker_name_reversed);
+ marker_reversed->setAttribute("orient", "auto");
+
+ // Find transform
+ const char* refX = marker_reversed->attribute("refX");
+ const char* refY = marker_reversed->attribute("refY");
+ std::string transform = "rotate(180";
+ if (refX) {
+ transform += ",";
+ transform += refX;
+
+ if (refY) {
+ if (refX) {
+ transform += ",";
+ transform += refY;
+ } else {
+ transform += ",0,";
+ transform += refY;
+ }
+ }
+ }
+ transform += ")";
+
+ // We can't set a transform on a marker... must create group first.
+ Inkscape::XML::Node *group = repr->document()->createElement("svg:g");
+ group->setAttribute("transform", transform);
+ marker_reversed->addChild(group, nullptr);
+
+ // Copy all marker content to group.
+ for (auto child = marker->firstChild() ; child != nullptr ; child = child->next() ) {
+ auto new_child = child->duplicate(repr->document());
+ group->addChild(new_child, nullptr);
+ new_child->release();
+ }
+
+ // Add new marker to <defs>.
+ defs->addChild(marker_reversed, marker);
+ marker_reversed->release();
+ }
+
+ // Change url to reference reversed marker.
+ std::string marker_url("url(#" + marker_name_reversed + ")");
+ sp_repr_css_set_property(css, "marker-start", marker_url.c_str());
+
+ // Also fix up if property is marker shorthand.
+ if (property == "marker") {
+ std::string marker_old_url("url(#" + marker_name + ")");
+ sp_repr_css_unset_property(css, "marker");
+ sp_repr_css_set_property(css, "marker-mid", marker_old_url.c_str());
+ sp_repr_css_set_property(css, "marker-end", marker_old_url.c_str());
+ }
+
+ sp_repr_css_set(repr, css, "style");
+
+ } // Uses auto-start-reverse
+ }
+ }
+ }
+}
+
+// Called by remove_marker_context_paint() for each property value ("marker", "marker-start", ...).
+static void remove_marker_context_paint (Inkscape::XML::Node *repr,
+ Inkscape::XML::Node *defs,
+ Glib::ustring property)
+{
+ // Value of 'marker', 'marker-start', ... property.
+ std::string value("url(#");
+ value += repr->attribute("id");
+ value += ")";
+
+ // Generate a list of elements that reference this marker.
+ std::vector<Inkscape::XML::Node *> to_fix_fill_stroke =
+ sp_repr_lookup_property_many(repr->root(), property, value);
+
+ for (auto it: to_fix_fill_stroke) {
+
+ // Figure out value of fill... could be inherited.
+ SPCSSAttr* css = sp_repr_css_attr_inherited (it, "style");
+ Glib::ustring fill = sp_repr_css_property (css, "fill", "");
+ Glib::ustring stroke = sp_repr_css_property (css, "stroke", "");
+
+ // Name of new marker.
+ Glib::ustring marker_fixed_id = repr->attribute("id");
+ if (!fill.empty()) {
+ marker_fixed_id += "_F" + fill;
+ }
+ if (!stroke.empty()) {
+ marker_fixed_id += "_S" + stroke;
+ }
+
+ {
+ // Replace characters from color value that are invalid in ids
+ gchar *normalized_id = g_strdup(marker_fixed_id.c_str());
+ g_strdelimit(normalized_id, "#%", '-');
+ g_strdelimit(normalized_id, "(), \n\t\r", '.');
+ marker_fixed_id = normalized_id;
+ g_free(normalized_id);
+ }
+
+ // See if a fixed marker already exists.
+ // Could be more robust, assumes markers are direct children of <defs>.
+ Inkscape::XML::Node* marker_fixed = sp_repr_lookup_child(defs, "id", marker_fixed_id.c_str());
+
+ if (!marker_fixed) {
+
+ // Need to create new marker.
+
+ marker_fixed = repr->duplicate(repr->document());
+ marker_fixed->setAttribute("id", marker_fixed_id);
+
+ // This needs to be turned into a function that fixes all descendents.
+ for (auto child = marker_fixed->firstChild() ; child != nullptr ; child = child->next()) {
+ // Find style.
+ SPCSSAttr* css = sp_repr_css_attr ( child, "style" );
+
+ Glib::ustring fill2 = sp_repr_css_property (css, "fill", "");
+ if (fill2 == "context-fill" ) {
+ sp_repr_css_set_property (css, "fill", fill.c_str());
+ }
+ if (fill2 == "context-stroke" ) {
+ sp_repr_css_set_property (css, "fill", stroke.c_str());
+ }
+
+ Glib::ustring stroke2 = sp_repr_css_property (css, "stroke", "");
+ if (stroke2 == "context-fill" ) {
+ sp_repr_css_set_property (css, "stroke", fill.c_str());
+ }
+ if (stroke2 == "context-stroke" ) {
+ sp_repr_css_set_property (css, "stroke", stroke.c_str());
+ }
+
+ sp_repr_css_set(child, css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+
+ defs->addChild(marker_fixed, repr);
+ marker_fixed->release();
+ }
+
+ Glib::ustring marker_value = "url(#" + marker_fixed_id + ")";
+ sp_repr_css_set_property (css, property.c_str(), marker_value.c_str());
+ sp_repr_css_set (it, css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+}
+
+static void remove_marker_context_paint (Inkscape::XML::Node *repr,
+ Inkscape::XML::Node *defs)
+{
+ if (strncmp("svg:marker", repr->name(), 10) == 0) {
+
+ if (!repr->attribute("id")) {
+
+ std::cerr << "remove_marker_context_paint: <marker> without 'id'!" << std::endl;
+
+ } else {
+
+ // First see if we need to do anything.
+ bool need_to_fix = false;
+
+ // This needs to be turned into a function that searches all descendents.
+ for (auto child = repr->firstChild() ; child != nullptr ; child = child->next()) {
+
+ // Find style.
+ SPCSSAttr* css = sp_repr_css_attr ( child, "style" );
+ Glib::ustring fill = sp_repr_css_property (css, "fill", "");
+ Glib::ustring stroke = sp_repr_css_property (css, "stroke", "");
+ if (fill == "context-fill" ||
+ fill == "context-stroke" ||
+ stroke == "context-fill" ||
+ stroke == "context-stroke" ) {
+ need_to_fix = true;
+ break;
+ }
+ sp_repr_css_attr_unref(css);
+ }
+
+ if (need_to_fix) {
+
+ // Now we need to search document for all elements that use this marker.
+ remove_marker_context_paint (repr, defs, "marker");
+ remove_marker_context_paint (repr, defs, "marker-start");
+ remove_marker_context_paint (repr, defs, "marker-mid");
+ remove_marker_context_paint (repr, defs, "marker-end");
+ }
+ }
+ }
+}
+
+/*
+ * Recursively insert SVG 1.1 fallback for SVG 2 text (ignored by SVG 2 renderers including ours).
+ * Notes:
+ * Text must have been layed out. Access via old document.
+ */
+static void insert_text_fallback( Inkscape::XML::Node *repr, const SPDocument *original_doc, Inkscape::XML::Node *defs = nullptr )
+{
+ if (repr) {
+
+ if (strncmp("svg:text", repr->name(), 8) == 0) {
+
+ auto id = repr->attribute("id");
+ // std::cout << "insert_text_fallback: found text! id: " << (id?id:"null") << std::endl;
+
+ // We need to get original SPText object to access layout.
+ SPText* text = static_cast<SPText *>(original_doc->getObjectById( id ));
+ if (text == nullptr) {
+ std::cerr << "insert_text_fallback: bad cast" << std::endl;
+ return;
+ }
+
+ if (!text->has_inline_size() &&
+ !text->has_shape_inside()) {
+ // No SVG 2 text, nothing to do.
+ return;
+ }
+
+ // We will keep this text node but replace all children.
+ // Text object must be visible for the text calculatons to work
+ bool was_hidden = text->isHidden();
+ text->setHidden(false);
+
+ // For text in a shape, We need to unset 'text-anchor' or SVG 1.1 fallback won't work.
+ // Note 'text' here refers to original document while 'repr' refers to new document copy.
+ if (text->has_shape_inside()) {
+ SPCSSAttr *css = sp_repr_css_attr(repr, "style" );
+ sp_repr_css_unset_property(css, "text-anchor");
+ sp_repr_css_set(repr, css, "style");
+ sp_repr_css_attr_unref(css);
+ }
+
+ // We need to put trailing white space into it's own tspan for inline size so
+ // it is excluded during calculation of line position in SVG 1.1 renderers.
+ bool trim = text->has_inline_size() &&
+ !(text->style->text_anchor.computed == SP_CSS_TEXT_ANCHOR_START);
+
+ // Make a list of children to delete at end:
+ std::vector<Inkscape::XML::Node *> old_children;
+ for (auto child = repr->firstChild(); child; child = child->next()) {
+ old_children.push_back(child);
+ }
+
+ // For round-tripping, xml:space (or 'white-space:pre') must be set.
+ repr->setAttribute("xml:space", "preserve");
+
+ double text_x = repr->getAttributeDouble("x", 0.0);
+ double text_y = repr->getAttributeDouble("y", 0.0);
+ // std::cout << "text_x: " << text_x << " text_y: " << text_y << std::endl;
+
+ // Loop over all lines in layout.
+ for (auto it = text->layout.begin() ; it != text->layout.end() ; ) {
+
+ // Create a <tspan> with 'x' and 'y' for each line.
+ Inkscape::XML::Node *line_tspan = repr->document()->createElement("svg:tspan");
+
+ // This could be useful if one wants to edit in an old version of Inkscape but we
+ // need to check if it breaks anything:
+ // line_tspan->setAttribute("sodipodi:role", "line");
+
+ // Hide overflow tspan (one line of text).
+ if (text->layout.isHidden(it)) {
+ line_tspan->setAttribute("style", "visibility:hidden");
+ }
+
+ Geom::Point line_anchor_point = text->layout.characterAnchorPoint(it);
+ double line_x = line_anchor_point[Geom::X];
+ double line_y = line_anchor_point[Geom::Y];
+
+ // std::cout << " line_anchor_point: " << line_anchor_point << std::endl;
+ if (line_tspan->childCount() == 0) {
+ if (text->is_horizontal()) {
+ // std::cout << " horizontal: " << text_x << " " << line_anchor_point[Geom::Y] << std::endl;
+ if (text->has_inline_size()) {
+ // We use text_x as this is the reference for 'text-anchor'
+ // (line_x is the start of the line which gives wrong position when 'text-anchor' not start).
+ line_tspan->setAttributeSvgDouble("x", text_x);
+ } else {
+ // shape-inside (we don't have to worry about 'text-anchor').
+ line_tspan->setAttributeSvgDouble("x", line_x);
+ }
+ line_tspan->setAttributeSvgDouble("y", line_y); // FIXME: this will pick up the wrong end of counter-directional runs
+ } else {
+ // std::cout << " vertical: " << line_anchor_point[Geom::X] << " " << text_y << std::endl;
+ line_tspan->setAttributeSvgDouble("x", line_x); // FIXME: this will pick up the wrong end of counter-directional runs
+ if (text->has_inline_size()) {
+ line_tspan->setAttributeSvgDouble("y", text_y);
+ } else {
+ line_tspan->setAttributeSvgDouble("y", line_y);
+ }
+ }
+ }
+
+ // Inside line <tspan>, create <tspan>s for each change of style or shift. (No shifts in SVG 2 flowed text.)
+ // For simple lines, this creates an unneeded <tspan> but so be it.
+ Inkscape::Text::Layout::iterator it_line_end = it;
+ it_line_end.nextStartOfLine();
+
+ // Find last span in line so we can put trailing whitespace in its own tspan for SVG 1.1 fallback.
+ Inkscape::Text::Layout::iterator it_last_span = it;
+ it_last_span.nextStartOfLine();
+ it_last_span.prevStartOfSpan();
+
+ Glib::ustring trailing_whitespace;
+
+ // Loop over chunks in line
+ while (it != it_line_end) {
+
+ Inkscape::XML::Node *span_tspan = repr->document()->createElement("svg:tspan");
+
+ // use kerning to simulate justification and whatnot
+ Inkscape::Text::Layout::iterator it_span_end = it;
+ it_span_end.nextStartOfSpan();
+ Inkscape::Text::Layout::OptionalTextTagAttrs attrs;
+ text->layout.simulateLayoutUsingKerning(it, it_span_end, &attrs);
+
+ // 'dx' and 'dy' attributes are used to simulated justified text.
+ if (!text->is_horizontal()) {
+ std::swap(attrs.dx, attrs.dy);
+ }
+ TextTagAttributes(attrs).writeTo(span_tspan);
+ SPObject *source_obj = nullptr;
+ Glib::ustring::iterator span_text_start_iter;
+ text->layout.getSourceOfCharacter(it, &source_obj, &span_text_start_iter);
+
+ // Set tspan style
+ Glib::ustring style_text = (dynamic_cast<SPString *>(source_obj) ? source_obj->parent : source_obj)
+ ->style->writeIfDiff(text->style);
+ if (!style_text.empty()) {
+ span_tspan->setAttributeOrRemoveIfEmpty("style", style_text);
+ }
+
+ // If this tspan has no attributes, discard it and add content directly to parent element.
+ if (span_tspan->attributeList().empty()) {
+ Inkscape::GC::release(span_tspan);
+ span_tspan = line_tspan;
+ } else {
+ line_tspan->appendChild(span_tspan);
+ Inkscape::GC::release(span_tspan);
+ }
+
+ // Add text node
+ SPString *str = dynamic_cast<SPString *>(source_obj);
+ if (str) {
+ Glib::ustring *string = &(str->string); // TODO fixme: dangerous, unsafe premature-optimization
+ SPObject *span_end_obj = nullptr;
+ Glib::ustring::iterator span_text_end_iter;
+ text->layout.getSourceOfCharacter(it_span_end, &span_end_obj, &span_text_end_iter);
+ if (span_end_obj != source_obj) {
+ if (it_span_end == text->layout.end()) {
+ span_text_end_iter = span_text_start_iter;
+ for (int i = text->layout.iteratorToCharIndex(it_span_end) - text->layout.iteratorToCharIndex(it) ; i ; --i)
+ ++span_text_end_iter;
+ } else
+ span_text_end_iter = string->end(); // spans will never straddle a source boundary
+ }
+
+ if (span_text_start_iter != span_text_end_iter) {
+ Glib::ustring new_string;
+ while (span_text_start_iter != span_text_end_iter)
+ new_string += *span_text_start_iter++; // grr. no substr() with iterators
+
+ if (it == it_last_span && trim) {
+ // Found last span in line
+ const auto s = new_string.find_last_not_of(" \t"); // Any other white space characters needed?
+ trailing_whitespace = new_string.substr(s+1, new_string.length());
+ new_string.erase(s+1);
+ }
+
+ Inkscape::XML::Node *new_text = repr->document()->createTextNode(new_string.c_str());
+ span_tspan->appendChild(new_text);
+ Inkscape::GC::release(new_text);
+ // std::cout << " new_string: |" << new_string << "|" << std::endl;
+ }
+ }
+ it = it_span_end;
+ }
+
+ // Add line tspan to document
+ repr->appendChild(line_tspan);
+ Inkscape::GC::release(line_tspan);
+
+ // For center and end justified text, we need to remove any spaces and put them
+ // into a separate tspan (alignment is done by "text chunk" and spaces at ends of
+ // line will mess this up).
+ if (trim && trailing_whitespace.length() != 0) {
+
+ Inkscape::XML::Node *space_tspan = repr->document()->createElement("svg:tspan");
+ // Set either 'x' or 'y' to force a new text chunk. To do: this really should
+ // be positioned at the end of the line (overhanging).
+ if (text->is_horizontal()) {
+ space_tspan->setAttributeSvgDouble("y", line_y);
+ } else {
+ space_tspan->setAttributeSvgDouble("x", line_x);
+ }
+ Inkscape::XML::Node *space = repr->document()->createTextNode(trailing_whitespace.c_str());
+ space_tspan->appendChild(space);
+ Inkscape::GC::release(space);
+ line_tspan->appendChild(space_tspan);
+ Inkscape::GC::release(space_tspan);
+ }
+
+ }
+
+ for (auto i: old_children) {
+ repr->removeChild (i);
+ }
+
+ text->setHidden(was_hidden);
+ return; // No need to look at children of <text>
+ }
+
+ for ( Node *child = repr->firstChild(); child; child = child->next() ) {
+ insert_text_fallback (child, original_doc, defs);
+ }
+ }
+}
+
+
+static void insert_mesh_polyfill( Inkscape::XML::Node *repr )
+{
+ if (repr) {
+
+ Inkscape::XML::Node *defs = sp_repr_lookup_name (repr, "svg:defs");
+
+ if (defs == nullptr) {
+ // We always put meshes in <defs>, no defs -> no mesh.
+ return;
+ }
+
+ bool has_mesh = false;
+ for ( Node *child = defs->firstChild(); child; child = child->next() ) {
+ if (strncmp("svg:meshgradient", child->name(), 16) == 0) {
+ has_mesh = true;
+ break;
+ }
+ }
+
+ Inkscape::XML::Node *script = sp_repr_lookup_child (repr, "id", "mesh_polyfill");
+
+ if (has_mesh && script == nullptr) {
+
+ script = repr->document()->createElement("svg:script");
+ script->setAttribute ("id", "mesh_polyfill");
+ script->setAttribute ("type", "text/javascript");
+ repr->root()->appendChild(script); // Must be last
+
+ // Insert JavaScript via raw string literal.
+ Glib::ustring js =
+#include "polyfill/mesh_compressed.include"
+;
+
+ Inkscape::XML::Node *script_text = repr->document()->createTextNode(js.c_str());
+ script->appendChild(script_text);
+ }
+ }
+}
+
+
+static void insert_hatch_polyfill( Inkscape::XML::Node *repr )
+{
+ if (repr) {
+
+ Inkscape::XML::Node *defs = sp_repr_lookup_name (repr, "svg:defs");
+
+ if (defs == nullptr) {
+ // We always put meshes in <defs>, no defs -> no mesh.
+ return;
+ }
+
+ bool has_hatch = false;
+ for ( Node *child = defs->firstChild(); child; child = child->next() ) {
+ if (strncmp("svg:hatch", child->name(), 16) == 0) {
+ has_hatch = true;
+ break;
+ }
+ }
+
+ Inkscape::XML::Node *script = sp_repr_lookup_child (repr, "id", "hatch_polyfill");
+
+ if (has_hatch && script == nullptr) {
+
+ script = repr->document()->createElement("svg:script");
+ script->setAttribute ("id", "hatch_polyfill");
+ script->setAttribute ("type", "text/javascript");
+ repr->root()->appendChild(script); // Must be last
+
+ // Insert JavaScript via raw string literal.
+ Glib::ustring js =
+#include "polyfill/hatch_compressed.include"
+;
+
+ Inkscape::XML::Node *script_text = repr->document()->createTextNode(js.c_str());
+ script->appendChild(script_text);
+ }
+ }
+}
+
+/*
+ * Recursively transform SVG 2 to SVG 1.1, if possible.
+ */
+static void transform_2_to_1( Inkscape::XML::Node *repr, Inkscape::XML::Node *defs = nullptr )
+{
+ if (repr) {
+
+ // std::cout << "transform_2_to_1: " << repr->name() << std::endl;
+
+ // Things we do once per node. -----------------------
+
+ // Find defs, if does not exist, create.
+ if (defs == nullptr) {
+ defs = sp_repr_lookup_name (repr, "svg:defs");
+ }
+ if (defs == nullptr) {
+ defs = repr->document()->createElement("svg:defs");
+ repr->root()->addChild(defs, nullptr);
+ }
+
+ // Find style.
+ SPCSSAttr* css = sp_repr_css_attr ( repr, "style" );
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // Individual items ----------------------------------
+
+ // SVG 2 marker attribute orient:auto-start-reverse:
+ if ( prefs->getBool("/options/svgexport/marker_autostartreverse", false) ) {
+ // Do "marker-start" first for efficiency reasons.
+ remove_marker_auto_start_reverse(repr, defs, css, "marker-start");
+ remove_marker_auto_start_reverse(repr, defs, css, "marker");
+ }
+
+ // SVG 2 paint values 'context-fill', 'context-stroke':
+ if ( prefs->getBool("/options/svgexport/marker_contextpaint", false) ) {
+ remove_marker_context_paint(repr, defs);
+ }
+
+ // *** To Do ***
+ // Context fill & stroke outside of markers
+ // Paint-Order
+ // Meshes
+ // Hatches
+
+ for ( Node *child = repr->firstChild(); child; child = child->next() ) {
+ transform_2_to_1 (child, defs);
+ }
+
+ sp_repr_css_attr_unref(css);
+ }
+}
+
+
+
+
+/**
+ \return None
+ \brief What would an SVG editor be without loading/saving SVG
+ files. This function sets that up.
+
+ For each module there is a call to Inkscape::Extension::build_from_mem
+ with a rather large XML file passed in. This is a constant string
+ that describes the module. At the end of this call a module is
+ returned that is basically filled out. The one thing that it doesn't
+ have is the key function for the operation. And that is linked at
+ the end of each call.
+*/
+void
+Svg::init()
+{
+ // clang-format off
+ /* SVG in */
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("SVG Input") "</name>\n"
+ "<id>" SP_MODULE_KEY_INPUT_SVG "</id>\n"
+ SVG_COMMON_INPUT_PARAMS
+ "<input>\n"
+ "<extension>.svg</extension>\n"
+ "<mimetype>image/svg+xml</mimetype>\n"
+ "<filetypename>" N_("Scalable Vector Graphic (*.svg)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Inkscape native file format and W3C standard") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>", new Svg());
+
+ /* SVG out Inkscape */
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("SVG Output Inkscape") "</name>\n"
+ "<id>" SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE "</id>\n"
+ "<output is_exported='true'>\n"
+ "<extension>.svg</extension>\n"
+ "<mimetype>image/x-inkscape-svg</mimetype>\n"
+ "<filetypename>" N_("Inkscape SVG (*.svg)") "</filetypename>\n"
+ "<filetypetooltip>" N_("SVG format with Inkscape extensions") "</filetypetooltip>\n"
+ "<dataloss>false</dataloss>\n"
+ "</output>\n"
+ "</inkscape-extension>", new Svg());
+
+ /* SVG out */
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("SVG Output") "</name>\n"
+ "<id>" SP_MODULE_KEY_OUTPUT_SVG "</id>\n"
+ "<output is_exported='true'>\n"
+ "<extension>.svg</extension>\n"
+ "<mimetype>image/svg+xml</mimetype>\n"
+ "<filetypename>" N_("Plain SVG (*.svg)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Scalable Vector Graphics format as defined by the W3C") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>", new Svg());
+ // clang-format on
+
+ return;
+}
+
+
+/**
+ \return A new document just for you!
+ \brief This function takes in a filename of a SVG document and
+ turns it into a SPDocument.
+ \param mod Module to use
+ \param uri The path or URI to the file (UTF-8)
+
+ This function is really simple, it just calls sp_document_new...
+ That's BS, it does all kinds of things for importing documents
+ that probably should be in a separate function.
+
+ Most of the import code was copied from gdkpixpuf-input.cpp.
+*/
+SPDocument *
+Svg::open (Inkscape::Extension::Input *mod, const gchar *uri)
+{
+ g_assert(mod != nullptr);
+
+ // This is only used at the end... but it should go here once uri stuff is fixed.
+ auto file = Gio::File::create_for_commandline_arg(uri);
+ const auto path = file->get_path();
+
+ // Fixing this means fixing a whole string of things.
+ // if (path.empty()) {
+ // // We lied, the uri wasn't a uri, try as path.
+ // file = Gio::File::create_for_path(uri);
+ // }
+
+ // std::cout << "Svg::open: uri in: " << uri << std::endl;
+ // std::cout << " : uri: " << file->get_uri() << std::endl;
+ // std::cout << " : scheme: " << file->get_uri_scheme() << std::endl;
+ // std::cout << " : path: " << file->get_path() << std::endl;
+ // std::cout << " : parse: " << file->get_parse_name() << std::endl;
+ // std::cout << " : base: " << file->get_basename() << std::endl;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+
+ // Get import preferences.
+ bool ask_svg = prefs->getBool( "/dialogs/import/ask_svg");
+ Glib::ustring import_mode_svg = prefs->getString("/dialogs/import/import_mode_svg");
+ Glib::ustring scale = prefs->getString("/dialogs/import/scale");
+
+ // If we popped up a window asking about import preferences, get values from
+ // there and update preferences.
+ if(mod->get_gui() && ask_svg) {
+ ask_svg = !mod->get_param_bool("do_not_ask");
+ import_mode_svg = mod->get_param_optiongroup("import_mode_svg");
+ scale = mod->get_param_optiongroup("scale");
+
+ prefs->setBool( "/dialogs/import/ask_svg", ask_svg);
+ prefs->setString("/dialogs/import/import_mode_svg", import_mode_svg );
+ prefs->setString("/dialogs/import/scale", scale );
+ }
+
+ bool import = prefs->getBool("/options/onimport", false);
+ bool import_pages = (import_mode_svg == "pages");
+ // Do we open a new svg instead of import?
+ if (uri && import && import_mode_svg == "new") {
+ prefs->setBool("/options/onimport", false); // set back to true in file_import
+ static auto gapp = InkscapeApplication::instance()->gtk_app();
+ auto action = gapp->lookup_action("file-open-window");
+ auto file_dnd = Glib::Variant<Glib::ustring>::create(uri);
+ action->activate(file_dnd);
+ return SPDocument::createNewDoc (nullptr, true, true);
+ }
+ // Do we "import" as <image>?
+ if (import && import_mode_svg != "include" && !import_pages) {
+ // We import!
+
+ // New wrapper document.
+ SPDocument * doc = SPDocument::createNewDoc (nullptr, true, true);
+
+ // Imported document
+ // SPDocument * ret = SPDocument::createNewDoc(file->get_uri().c_str(), true);
+ SPDocument * ret = SPDocument::createNewDoc(uri, true);
+
+ if (!ret) {
+ return nullptr;
+ }
+
+ // What is display unit doing here?
+ Glib::ustring display_unit = doc->getDisplayUnit()->abbr;
+ double width = ret->getWidth().value(display_unit);
+ double height = ret->getHeight().value(display_unit);
+ if (width < 0 || height < 0) {
+ return nullptr;
+ }
+
+ // Create image node
+ Inkscape::XML::Document *xml_doc = doc->getReprDoc();
+ Inkscape::XML::Node *image_node = xml_doc->createElement("svg:image");
+
+ // Set default value as we honor "preserveAspectRatio".
+ image_node->setAttribute("preserveAspectRatio", "none");
+
+ double svgdpi = mod->get_param_float("svgdpi");
+ image_node->setAttribute("inkscape:svg-dpi", Glib::ustring::format(svgdpi));
+
+ image_node->setAttribute("width", Glib::ustring::format(width));
+ image_node->setAttribute("height", Glib::ustring::format(height));
+
+ // This is actually "image-rendering"
+ Glib::ustring scale = prefs->getString("/dialogs/import/scale");
+ if( scale != "auto") {
+ SPCSSAttr *css = sp_repr_css_attr_new();
+ sp_repr_css_set_property(css, "image-rendering", scale.c_str());
+ sp_repr_css_set(image_node, css, "style");
+ sp_repr_css_attr_unref( css );
+ }
+
+ // Do we embed or link?
+ if (import_mode_svg == "embed") {
+ std::unique_ptr<Inkscape::Pixbuf> pb(Inkscape::Pixbuf::create_from_file(uri, svgdpi));
+ if(pb) {
+ sp_embed_svg(image_node, uri);
+ }
+ } else {
+ // Convert filename to uri (why do we need to do this, we claimed it was already a uri).
+ gchar* _uri = g_filename_to_uri(uri, nullptr, nullptr);
+ if(_uri) {
+ // if (strcmp(_uri, uri) != 0) {
+ // std::cout << "Svg::open: _uri != uri! " << _uri << ":" << uri << std::endl;
+ // }
+ image_node->setAttribute("xlink:href", _uri);
+ g_free(_uri);
+ } else {
+ image_node->setAttribute("xlink:href", uri);
+ }
+ }
+
+ // Add the image to a layer.
+ Inkscape::XML::Node *layer_node = xml_doc->createElement("svg:g");
+ layer_node->setAttribute("inkscape:groupmode", "layer");
+ layer_node->setAttribute("inkscape:label", "Image");
+ doc->getRoot()->appendChildRepr(layer_node);
+ layer_node->appendChild(image_node);
+ Inkscape::GC::release(image_node);
+ Inkscape::GC::release(layer_node);
+ fit_canvas_to_drawing(doc);
+
+ // Set viewBox if it doesn't exist. What is display unit doing here?
+ if (!doc->getRoot()->viewBox_set) {
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit())));
+ }
+ return doc;
+ }
+
+ // We are not importing as <image>. Open as new document.
+
+ // Try to open non-local file (when does this occur?).
+ if (!file->get_uri_scheme().empty()) {
+ if (path.empty()) {
+ try {
+ char *contents;
+ gsize length;
+ file->load_contents(contents, length);
+ return SPDocument::createNewDocFromMem(contents, length, true);
+ } catch (Gio::Error &e) {
+ g_warning("Could not load contents of non-local URI %s\n", uri);
+ return nullptr;
+ }
+ } else {
+ // Do we ever get here and does this actually work?
+ uri = path.c_str();
+ }
+ }
+
+ SPDocument *doc = SPDocument::createNewDoc(uri, true);
+
+ // Convert single page docs into multi page mode, and visa-versa if
+ // we are importing. We never change the mode for opening.
+ if (doc && import) {
+ doc->setPages(import_pages);
+ }
+
+ return doc;
+}
+
+/**
+ \return None
+ \brief This is the function that does all of the SVG saves in
+ Inkscape. It detects whether it should do a Inkscape
+ namespace save internally.
+ \param mod Extension to use.
+ \param doc Document to save.
+ \param uri The filename to save the file to.
+
+ This function first checks its parameters, and makes sure that
+ we're getting good data. It also checks the module ID of the
+ incoming module to figure out whether this save should include
+ the Inkscape namespace stuff or not. The result of that comparison
+ is stored in the exportExtensions variable.
+
+ If there is not to be Inkscape name spaces a new document is created
+ without. (I think, I'm not sure on this code)
+
+ All of the internally referenced imageins are also set to relative
+ paths in the file. And the file is saved.
+
+ This really needs to be fleshed out more, but I don't quite understand
+ all of this code. I just stole it.
+*/
+void
+Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
+{
+ g_return_if_fail(doc != nullptr);
+ g_return_if_fail(filename != nullptr);
+ Inkscape::XML::Document *rdoc = doc->getReprDoc();
+ const SPDocument *original_doc = doc->getOriginalDocument();
+
+ bool const exportExtensions = ( !mod->get_id()
+ || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)
+ || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE));
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool const transform_2_to_1_flag =
+ prefs->getBool("/dialogs/save_as/enable_svgexport", false);
+
+ bool const insert_text_fallback_flag =
+ prefs->getBool("/options/svgexport/text_insertfallback", true);
+ bool const insert_mesh_polyfill_flag =
+ prefs->getBool("/options/svgexport/mesh_insertpolyfill", true);
+ bool const insert_hatch_polyfill_flag =
+ prefs->getBool("/options/svgexport/hatch_insertpolyfill", true);
+
+ // We used to copy the svg here if there was modification, but no need now
+ // We copy the svg document for ALL exports and saves and the extensions can
+ // decide what to do without fear.
+
+ // We prune the in-use document and deliberately loose data, because there
+ // is no known use for this data at the present time.
+ pruneProprietaryGarbage(rdoc->root());
+
+ // Start off with a svg 2.0 document
+ rdoc->setAttribute("standalone", "no");
+ rdoc->setAttribute("version", "2.0");
+
+ if (!exportExtensions) {
+ pruneExtendedNamespaces(rdoc->root());
+ }
+
+ if (transform_2_to_1_flag) {
+ transform_2_to_1 (rdoc->root());
+ rdoc->setAttribute("version", "1.1");
+ }
+
+ if (insert_text_fallback_flag && original_doc) {
+ insert_text_fallback (rdoc->root(), original_doc);
+ }
+
+ if (insert_mesh_polyfill_flag) {
+ insert_mesh_polyfill (rdoc->root());
+ }
+
+ if (insert_hatch_polyfill_flag) {
+ insert_hatch_polyfill (rdoc->root());
+ }
+
+ if (!sp_repr_save_rebased_file(rdoc, filename, SP_SVG_NS_URI,
+ doc->getDocumentBase(), //
+ m_detachbase ? nullptr : filename)) {
+ throw Inkscape::Extension::Output::save_failed();
+ }
+}
+
+} } } /* namespace inkscape, module, implementation */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/svg.h b/src/extension/internal/svg.h
new file mode 100644
index 0000000..ec76bf0
--- /dev/null
+++ b/src/extension/internal/svg.h
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is the code that moves all of the SVG loading and saving into
+ * the module format. Really Sodipodi is built to handle these formats
+ * internally, so this is just calling those internal functions.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2003 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __SVG_H__
+#define __SVG_H__
+
+#include "../implementation/implementation.h"
+
+// clang-format off
+#define SVG_COMMON_INPUT_PARAMS \
+ "<param name='import_mode_svg' type='optiongroup' gui-text='" N_("SVG Image Import Type:") "' >\n" \
+ "<option value='include' >" N_("Include SVG image as editable object(s) in the current file") "</option>\n" \
+ "<option value='pages' >" N_("Add SVG as new page(s) in the current file") "</option>\n" \
+ "<option value='embed' >" N_("Embed the SVG file in an image tag (not editable in this document)") "</option>\n" \
+ "<option value='link' >" N_("Link the SVG file in an image tag (not editable in this document).") "</option>\n" \
+ "<option value='new' >" N_("Open SVG image as separate document") "</option>\n" \
+ "</param>\n" \
+ "<param name='svgdpi' type='float' precision='2' min='1' max='999999' gui-text='" N_("DPI for rendered SVG") "'>96.00</param>\n" \
+ "<param name='scale' appearance='combo' type='optiongroup' gui-text='" N_("Image Rendering Mode:") "' gui-description='" N_("When an image is upscaled, apply smoothing or keep blocky (pixelated). (Will not work in all browsers.)") "' >\n" \
+ "<option value='auto' >" N_("None (auto)") "</option>\n" \
+ "<option value='optimizeQuality' >" N_("Smooth (optimizeQuality)") "</option>\n" \
+ "<option value='optimizeSpeed' >" N_("Blocky (optimizeSpeed)") "</option>\n" \
+ "</param>\n" \
+ "<param name=\"do_not_ask\" gui-description='" N_("Hide the dialog next time and always apply the same actions.") "' gui-text=\"" N_("Don't ask again") "\" type=\"bool\" >false</param>\n"
+// clang-format on
+
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class Svg : public Inkscape::Extension::Implementation::Implementation {
+ bool m_detachbase = false;
+
+public:
+ void setDetachBase(bool detach) override { m_detachbase = detach; }
+
+ void save( Inkscape::Extension::Output *mod,
+ SPDocument *doc,
+ gchar const *filename ) override;
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+ static void init( );
+
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+#endif /* __SVG_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/svgz.cpp b/src/extension/internal/svgz.cpp
new file mode 100644
index 0000000..7799c67
--- /dev/null
+++ b/src/extension/internal/svgz.cpp
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Code to handle compressed SVG loading and saving. Almost identical to svg
+ * routines, but separated for simpler extension maintenance.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2002-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "svgz.h"
+#include "extension/extension.h"
+#include "extension/system.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+#include "clear-n_.h"
+
+/**
+ \return None
+ \brief What would an SVG editor be without loading/saving SVG
+ files. This function sets that up.
+
+ For each module there is a call to Inkscape::Extension::build_from_mem
+ with a rather large XML file passed in. This is a constant string
+ that describes the module. At the end of this call a module is
+ returned that is basically filled out. The one thing that it doesn't
+ have is the key function for the operation. And that is linked at
+ the end of each call.
+*/
+void
+Svgz::init()
+{
+ // clang-format off
+ /* SVGZ in */
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("SVGZ Input") "</name>\n"
+ "<id>" SP_MODULE_KEY_INPUT_SVGZ "</id>\n"
+ "<dependency type=\"extension\">" SP_MODULE_KEY_INPUT_SVG "</dependency>\n"
+ SVG_COMMON_INPUT_PARAMS
+ "<input>\n"
+ "<extension>.svgz</extension>\n"
+ "<mimetype>image/svg+xml-compressed</mimetype>\n"
+ "<filetypename>" N_("Compressed Inkscape SVG (*.svgz)") "</filetypename>\n"
+ "<filetypetooltip>" N_("SVG file format compressed with GZip") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>", new Svgz());
+
+ /* SVGZ out Inkscape */
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("SVGZ Output") "</name>\n"
+ "<id>" SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE "</id>\n"
+ "<output>\n"
+ "<extension>.svgz</extension>\n"
+ "<mimetype>image/x-inkscape-svg-compressed</mimetype>\n"
+ "<filetypename>" N_("Compressed Inkscape SVG (*.svgz)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Inkscape's native file format compressed with GZip") "</filetypetooltip>\n"
+ "<dataloss>false</dataloss>\n"
+ "</output>\n"
+ "</inkscape-extension>", new Svgz());
+
+ /* SVGZ out */
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("SVGZ Output") "</name>\n"
+ "<id>" SP_MODULE_KEY_OUTPUT_SVGZ "</id>\n"
+ "<output>\n"
+ "<extension>.svgz</extension>\n"
+ "<mimetype>image/svg+xml-compressed</mimetype>\n"
+ "<filetypename>" N_("Compressed plain SVG (*.svgz)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Scalable Vector Graphics format compressed with GZip") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>\n", new Svgz());
+ // clang-format on
+
+ return;
+}
+
+
+} } } // namespace inkscape, module, implementation
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/svgz.h b/src/extension/internal/svgz.h
new file mode 100644
index 0000000..e923c4c
--- /dev/null
+++ b/src/extension/internal/svgz.h
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Code to handle compressed SVG loading and saving. Almost identical to svg
+ * routines, but separated for simpler extension maintenance.
+ *
+ * Authors:
+ * Lauris Kaplinski <lauris@kaplinski.com>
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2002-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_SVGZ_H
+#define SEEN_SVGZ_H
+
+#include "svg.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class Svgz : public Svg {
+public:
+ static void init( );
+};
+
+} } } // namespace Inkscape, Extension, Implementation
+#endif // SEEN_SVGZ_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/text_reassemble.c b/src/extension/internal/text_reassemble.c
new file mode 100644
index 0000000..543106f
--- /dev/null
+++ b/src/extension/internal/text_reassemble.c
@@ -0,0 +1,2973 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * text_reassemble.c from libTERE
+ *//*
+ * Authors: see below
+ *
+ *
+ * Copyright (C) 2018 Authors
+ * Released under GNU GPL v2.0+, read the file 'COPYING' for more information.
+ */
+
+
+/**
+ @file text_reassemble.c
+
+\verbatim
+Method:
+ 1. For all ordered text objects which are sequential and share the same esc.
+ 2. For the first only pull x,y,esc and save, these define origin and rotation.
+ 3. Save the text object.
+ 4. Phase I: For all saved text objects construct lines.
+ 5. Check for allowed overlaps on sequential saved text object bounding rectangles.
+ 6 If found merge second with first, check next one.
+ 7. If not found, start a new complex (line).
+ 8. Phase II; for all lines construct paragraphs.
+ 9. Check alignment and line spacing of preceding line with current line.
+ 10. if alignment is the same, and line spacing is compatible merge current line into
+ current paragraph. Reaverage line spacing over all lines in paragraph. Check next one.
+ 11. If alignment does not match start a new paragraph.
+ (Test program)
+ 12. Over all phase II paragraphs
+ 13. Over all phase I lines in each paragraph.
+ 14. Over all text objects in each line.
+ Emit SVG corresponding to this construct to a file dump.svg.
+ (Conversion to other file types would be modeled on this example.)
+ 15. Clean up.
+ (General program)
+ Like for the Test program, but final representation may not be SVG.
+ Text object and bounding rectangle memory would all be released. If another set of
+ text will be processed then hang onto both Freetype and Fontconfig structures. If no
+ other text will be processed here, then also release Freetype structures. If the caller uses
+ Fontconfig elsewhere then do not release it, otherwise, do so.
+
+NOTE ON COORDINATES: x is positive to the right, y is positive down. So (0,0) is the upper left corner, and the
+lower left corner of a rectangle has a LARGER Y coordinate than the upper left. Ie, LL=(10,10) UR=(30,5) is typical.
+\endverbatim
+*/
+
+/*
+
+Compilation of test program (with all debugging output, but not loop testing):
+On Windows use:
+
+ gcc -Wall -DWIN32 -DTEST -DDBG_TR_PARA -DDBG_TR_INPUT \
+ -I. -I/c/progs/devlibs32/include -I/c/progs/devlibs32/include/freetype2\
+ -o text_reassemble text_reassemble.c uemf_utf.c \
+ -lfreetype6 -lfontconfig-1 -liconv -lm -L/c/progs/devlibs32/bin
+
+On Linux use:
+
+ gcc -Wall -DTEST -DDBG_TR_PARA -DDBG_TR_INPUT -I. -I/usr/include/freetype2 -o text_reassemble text_reassemble.c uemf_utf.c -lfreetype -lfontconfig -lm
+
+Compilation of object file only (Windows):
+
+ gcc -Wall -DWIN32 -c \
+ -I. -I/c/progs/devlibs32/include -I/c/progs/devlibs32/include/freetype2\
+ text_reassemble.c
+
+Compilation of object file only (Linux):
+ gcc -Wall -c -I. -I/usr/include/freetype2 text_reassemble.c
+
+
+Optional compiler switches for development:
+ -DDBG_TR_PARA draw bounding rectangles for paragraphs in SVG output
+ -DDBG_TR_INPUT draw input text and their bounding rectangles in SVG output
+ -DTEST build the test program
+ -DDBG_LOOP force the test program to cycle 5 times. Useful for finding
+ memory leaks. Output file is overwritten each time.
+
+
+File: text_reassemble.c
+Version: 0.0.18
+Date: 11-MAR-2016
+Author: David Mathog, Biology Division, Caltech
+email: mathog@caltech.edu
+Copyright: 2016 David Mathog and California Institute of Technology (Caltech)
+*/
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "text_reassemble.h"
+#include <3rdparty/libuemf/uemf_utf.h> /* For a couple of text functions. Exact copy from libUEMF. */
+#include <locale.h>
+#include <float.h>
+
+/* Code generated by make_ucd_mn_table.c using:
+ cat mnlist.txt | ./make_ucd_mn_table >generated.c
+*/
+#include <stdint.h>
+int is_mn_unicode(int test){
+#define MN_TEST_LIMIT 921600
+#define N_SPAGES 225
+#define N_CPAGES 192
+#define N_CPDATA 344
+#define C_PER_S 16
+
+ // clang-format off
+ 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};
+ // clang-format on
+
+ int result=0;
+
+ int spage_idx;
+ int cpage_row, cpage_column, cpage_idx;
+ int cd_row, cd_column, cd_idx, cd_bit;
+
+ if(test<MN_TEST_LIMIT){
+ spage_idx = test >> 12;
+ cpage_row = superpages[spage_idx];
+ cpage_column = (test>>8) & 15;
+ cpage_idx = C_PER_S*cpage_row + cpage_column;
+ cd_row = cpages[cpage_idx];
+ cd_column = test>>5 & 7;
+ cd_idx = 8*cd_row + cd_column;
+ cd_bit = test & 31;
+ result = cpage_data[cd_idx] & (1 << cd_bit);
+ }
+ return(result);
+}
+
+
+
+/**
+ \brief Find a (sub)string in a caseinvariant manner, used for locating "Narrow" in font name
+ \return Returns -1 if no match, else returns the position (numbered from 0) of the first character of the match.
+ \param string Text to search
+ \param sub Text to find
+*/
+int TR_findcasesub(const char *string, const char *sub){
+ int i,j;
+ int match=0;
+ for(i=0; string[i]; i++){
+ for(match=1,j=0; sub[j] && string[i+j]; j++){
+ if(toupper(sub[j]) != toupper(string[i+j])){
+ match=0;
+ break;
+ }
+ }
+ if(match && !sub[j])break; /* matched over the entire substring */
+ }
+ return((match ? i : -1));
+}
+
+/**
+ \brief Constrouct a fontspec from a TCHUNK_SPECS and a fontname
+ \return Returns NULL on error, new fontspec on success
+ \param tsp pointer to TCHUNK_SPECS to use for information
+ \param fontname Fontname to use in the new fontspec
+*/
+ /* construct a font name */
+char *TR_construct_fontspec(const TCHUNK_SPECS *tsp, const char *fontname){
+ int newlen = 128 + strlen(fontname); /* too big, but not by much */
+ char *newfs = NULL;
+ newfs = (char *) malloc(newlen);
+ sprintf(newfs,"%s:slant=%d:weight=%d:size=%f:width=%d",fontname,tsp->italics,tsp->weight,tsp->fs,(tsp->co ? 75 : tsp->condensed));
+ return(newfs);
+}
+
+
+/**
+ \brief Reconstrouct a fontspec by substituting a font name into an existing spec
+ \return Returns NULL on error, new fontspec on success
+ \param fontspec Original fontspec, only the name will be changed
+ \param fontname Fontname to substitute into the new fontspec
+*/
+ /* construct a font name */
+char *TR_reconstruct_fontspec(const char *fontspec, const char *fontname){
+ int colon;
+ int newlen = strlen(fontspec) + strlen(fontname) + 1; /* too big, but not by much */
+ char *newfs = NULL;
+ newfs = (char *) malloc(newlen);
+ colon = strcspn(fontspec,":");
+ if(colon){ sprintf(newfs,"%s%s",fontname,&fontspec[colon]); }
+ return(newfs);
+}
+
+/**
+ \brief Find a font in the list that has a glyph for this character, change alternate to match
+ \return Returns 0 if no match or an error, else returns the glyph index in the new alternate font
+ \param fti pointer to the FT_INFO structure, may be modified if alternate font is added
+ \param efsp Pointer to a Pointer to the original FNT_SPECS struct. On return contains the FNT_SPECS corresponding to the glyph_index..
+ \param wc Current character (32 bit int)
+*/
+int TR_find_alternate_font(FT_INFO *fti, FNT_SPECS **efsp, uint32_t wc){
+ int glyph_index=0; /* this is the unknown character glyph */
+ uint32_t i;
+ FcCharSet *cs;
+ FcResult result = FcResultMatch;
+ FcPattern *pattern, *fpat;
+ char *filename;
+ char *fontname;
+ char *newfontspec;
+ int fi_idx;
+ FNT_SPECS *fsp,*fsp2;
+ if(!fti || !efsp || !*efsp)return(0);
+ fsp = *efsp;
+ for(i=0;i<fsp->used;i++){ /* first check in alts */
+ fsp2 = &fti->fonts[fsp->alts[i].fi_idx]; /* these are in order of descending previous usage */
+ glyph_index = FT_Get_Char_Index( fsp2->face, wc); /* we have the face, might as well check that directly */
+ if (glyph_index){ /* found a glyph for the character in this font */
+ (void) fsp_alts_weight(fsp, i);
+ *efsp = fsp2;
+ return(glyph_index);
+ }
+ }
+
+ /* it was not in alts, now go through fontset and see if it is in there */
+ for(i=1; i< (unsigned int) fsp->fontset->nfont;i++){ /* already know the primary does not have this character */
+ result = FcPatternGetCharSet(fsp->fontset->fonts[i], FC_CHARSET, 0, &cs);
+ if(result != FcResultMatch) return(0); /* some terrible problem, this should never happen */
+ if (FcCharSetHasChar(cs, wc)){ /* found a glyph for the character in this font */
+ glyph_index = i;
+
+ /* Do a lot of work to find the filename corresponding to the fontset entry.
+ None of these should ever fail, but if one does, return 0
+ */
+ if(
+ !(pattern = FcNameParse((const FcChar8 *)&(fsp->fontspec))) ||
+ !FcConfigSubstitute(NULL, pattern, FcMatchPattern)
+ )return(0);
+ FcDefaultSubstitute(pattern);
+ if(
+ !(fpat = FcFontRenderPrepare(NULL, pattern, fsp->fontset->fonts[i])) ||
+ (FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch) ||
+ (FcPatternGetString( fsp->fontset->fonts[i], FC_FULLNAME, 0, (FcChar8 **)&fontname) != FcResultMatch)
+ )return(0);
+
+ /* find the font (added from an unrelated fontset, for instance) or insert it as new */
+ fi_idx = ftinfo_find_loaded_by_src(fti, (uint8_t *) filename);
+ if(fi_idx < 0){
+ newfontspec = TR_reconstruct_fontspec((char *) fsp->fontspec, fontname);
+ fi_idx = ftinfo_load_fontname(fti, newfontspec);
+ free(newfontspec);
+ if(fi_idx < 0)return(0); /* This could happen if we run out of memory*/
+ }
+
+ /* add the new font index to the alts list on the (current) fsp. */
+ (void) fsp_alts_insert(fsp, fi_idx);
+
+ /* release FC's own memory related to this call that does not need to be kept around so that face will work */
+ FcPatternDestroy(pattern);
+
+ *efsp = &(fti->fonts[fi_idx]);
+ return(glyph_index);
+ }
+ }
+
+ return(0);
+}
+
+/**
+ \brief Get the advance for the 32 bit character
+
+ \return Returns -1 on error, or advance in units of 1/64th of a Point.
+ \param fti pointer to the FT_INFO structure, may be modified if alternate font is required
+ \param fsp Pointer to FNT_SPECS struct.
+ \param wc Current character (32 bit int)
+ \param pc Previous character
+ \param load_flags Controls internal advance:
+ FT_LOAD_NO_SCALE, internal advance is in 1/64th of a point. (kerning values are still scaled)
+ FT_LOAD_TARGET_NORMAL internal advance is in 1/64th of a point. The scale
+ factor seems to be (Font Size in points)*(DPI)/(32.0 pnts)*(72 dpi).
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+ \param ymin If the pointer is defined, the value is adjusted if ymin of wc character is less than the current value.
+ \param ymax If the pointer is defined, the value is adjusted if ymin of wc character is more than the current value.
+*/
+int TR_getadvance(FT_INFO *fti, FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax){
+ FT_Glyph glyph;
+ int glyph_index;
+ int advance=-1;
+ FT_BBox bbox;
+
+ if(is_mn_unicode(wc))return(0); /* no advance on Unicode Mn characters */
+
+ glyph_index = FT_Get_Char_Index( fsp->face, wc);
+ if(!glyph_index){ /* not in primary font, check alternates */
+ glyph_index = TR_find_alternate_font(fti, &fsp, wc);
+ }
+ if(glyph_index){
+ if (!FT_Load_Glyph( fsp->face, glyph_index, load_flags )){
+ if ( !FT_Get_Glyph( fsp->face->glyph, &glyph ) ) {
+ advance = fsp->face->glyph->advance.x;
+ FT_Glyph_Get_CBox( glyph, FT_GLYPH_BBOX_UNSCALED, &bbox );
+ if(ymin && (bbox.yMin < *ymin))*ymin=bbox.yMin;
+ if(ymax && (bbox.yMax > *ymax))*ymax=bbox.yMax;
+ if(pc)advance += TR_getkern2(fsp, wc, pc, kern_mode);
+ FT_Done_Glyph(glyph);
+ }
+ }
+ }
+ /* If there was no way to determine the width, this returns the error value */
+ return(advance);
+}
+
+/**
+ \brief Get the kerning for a pair of 32 bit characters
+ \return Returns 0 on error, or kerning value (which may be 0) for the pair in units of 1/64th of a point.
+ \param fsp Pointer to FNT_SPECS struct.
+ \param wc Current character (32 bit int)
+ \param pc Previous character
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+*/
+int TR_getkern2(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int kern_mode){
+ int this_glyph_index;
+ int prev_glyph_index;
+ int kern=0;
+ FT_Vector akerning;
+
+ this_glyph_index = FT_Get_Char_Index( fsp->face, wc);
+ prev_glyph_index = FT_Get_Char_Index( fsp->face, pc);
+ if(!FT_Get_Kerning( fsp->face,
+ prev_glyph_index,
+ this_glyph_index,
+ kern_mode,
+ &akerning )){
+ kern = akerning.x; /* Is sign correct? */
+ }
+ return(kern);
+}
+
+/**
+ \brief Get the kerning for a pair of 32 bit characters, where one is the last character in the previous text block, and the other is the first in the current text block.
+ \return Returns 0 on error, or kerning value (which may be 0) for the pair in units of 1/64th of a point.
+ \param fsp Pointer to FNT_SPECS struct.
+ \param tsp current text object
+ \param ptsp previous text object
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+*/
+int TR_kern_gap(FNT_SPECS *fsp, TCHUNK_SPECS *tsp, TCHUNK_SPECS *ptsp, int kern_mode){
+ int kern=0;
+ uint32_t *text32=NULL;
+ uint32_t *ptxt32=NULL;
+ size_t tlen,plen;
+ while(ptsp && tsp){
+ text32 = U_Utf8ToUtf32le((char *) tsp->string, 0, &tlen);
+ if(!text32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail
+ text32 = U_Latin1ToUtf32le((char *) tsp->string,0, &tlen);
+ if(!text32)break;
+ }
+ ptxt32 = U_Utf8ToUtf32le((char *) ptsp->string,0,&plen);
+ if(!ptxt32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail
+ ptxt32 = U_Latin1ToUtf32le((char *) ptsp->string,0, &plen);
+ if(!ptxt32)break;
+ }
+ kern = TR_getkern2(fsp, *text32, ptxt32[plen-1], kern_mode);
+ break;
+ }
+ if(text32)free(text32);
+ if(ptxt32)free(ptxt32);
+ return(kern);
+}
+
+
+
+
+/**
+ \brief Find baseline on Y axis of a complex.
+ If the complex is a TR_TEXT or TR_LINE find its baseline.
+ If the complex is TR_PARA_[UCLR]J find the baseline of the last line.
+ If there are multiple text elements in a TR_LINE, the baseline is that of the
+ element that uses the largest font. This will definitely give the wrong
+ result if that line starts with a super or subscript that is full font size, but
+ they are usually smaller.
+ \return Returns 0 if it cannot determine a baseline, else returns the baseline Y coordinate.
+ \param tri pointer to the TR_INFO structure holding all TR data
+ \param src index of the current complex
+ \param ymax If the pointer is defined, the value is adjusted if ymax of current complex is more than the current value.
+ \param ymin If the pointer is defined, the value is adjusted if ymin of current complex is less than the current value.
+*/
+double TR_baseline(TR_INFO *tri, int src, double *ymax, double *ymin){
+ double baseline=0;
+ double tmp=0.0;
+ 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);
+ if(*ymax <= tmp){
+ *ymax = tmp;
+ baseline = bri->rects[trec].yll - tpi->chunks[trec].boff;
+ }
+ }
+ else if(ymin){
+ tmp = tpi->chunks[trec].fs * (((double)-fsp->face->bbox.yMin)/yheight); /* yMin in face is negative */
+ if(*ymin <= tmp){
+ *ymin = tmp;
+ baseline = bri->rects[trec].yll - tpi->chunks[trec].boff;
+ }
+ }
+ }
+ break;
+ case TR_PARA_UJ:
+ case TR_PARA_LJ:
+ case TR_PARA_CJ:
+ case TR_PARA_RJ:
+ trec = cxi->cx[src].kids.members[last];
+ baseline = TR_baseline(tri, trec, ymax, ymin);
+ break;
+ }
+ return(baseline);
+}
+
+/**
+ \brief Check or set vertical advance on the growing complex relative to the current complex.
+ Vadvance is a multiplicative factor like 1.25.
+ The distance between successive baselines is vadvance * max(font_size), where the maximum
+ is over all text elements in src.
+ The growing complex is always the last one in the CX_INFO section of the TR_INFO structure.
+ If an existing vadvance does not match the one which would be required to fit the next complex
+ to add to the growing one, it terminates a growing complex. (Ie, starts a new paragraph.)
+ Find baseline on Y axis of a complex.
+ If the complex is a TR_TEXT or TR_LINE find its baseline.
+ If the complex is TR_PARA+* find the baseline of the last line.
+ If there are multiple text elements in a TR_LINE, the baseline is that of the
+ element that uses the largest font. This will definitely give the wrong
+ result if that line starts with a super or subscript that is full font size, but
+ they are usually smaller.
+ \return Returns 0 on success, !0 on failure.
+ \param tri pointer to the TR_INFO structure holding all TR data
+ \param src index of the current complex, to be added to the growing complex.
+ This lets the value of "src - lines" determine the weight to give to each new vadvance value
+ as it is merged into the running weighted average. This improves the accuracy of the vertical advance,
+ since there can be some noise introduced when lines have different maximum font sizes.
+ \param lines index of the first text block that was added to the growing complex.
+*/
+int TR_check_set_vadvance(TR_INFO *tri, int src, int lines){
+ int status = 0;
+ CX_INFO *cxi = tri->cxi;
+ TP_INFO *tpi = tri->tpi;
+ double ymax = DBL_MIN;
+ double ymin = DBL_MIN;
+ double prevbase;
+ double thisbase;
+ double weight;
+ int trec;
+ double newV;
+ int dst;
+
+ dst = cxi->used-1; /* complex being grown */
+
+ prevbase = TR_baseline(tri, dst, NULL, &ymin);
+ thisbase = TR_baseline(tri, src, &ymax, NULL);
+ newV = (thisbase - prevbase)/(ymax + ymin);
+ trec = cxi->cx[dst].kids.members[0]; /* complex whose first text record holds vadvance for this complex */
+ trec = cxi->cx[trec].kids.members[0]; /* text record that halds vadvance for this complex */
+ if(tpi->chunks[trec].vadvance){
+ /* already set on the first text (only place it is stored.)
+ See if the line to be added is compatible.
+ All text fields in a complex have the same advance, so just set/check the first one.
+ vadvance must be within 1% or do not add a new line */
+ if(fabs(1.0 - (tpi->chunks[trec].vadvance/newV)) > 0.01){
+ status = 1;
+ }
+ else { /* recalculate the weighted vadvance */
+ weight = (1.0 / (double) (src - lines));
+ tpi->chunks[trec].vadvance = tpi->chunks[trec].vadvance*(1.0-weight) + newV*weight;
+ }
+ }
+ else { /* only happens when src = lines + 1*/
+ tpi->chunks[trec].vadvance = newV;
+ }
+ return(status);
+}
+
+
+/**
+ \brief Initialize an FT_INFO structure. Sets up a freetype library to use in this context.
+ \returns a pointer to the FT_INFO structure created, or NULL on error.
+*/
+FT_INFO *ftinfo_init(void){
+ FT_INFO *fti = NULL;
+ if(FcInit()){
+ fti = (FT_INFO *)calloc(1,sizeof(FT_INFO));
+ if(fti){
+ if(!FT_Init_FreeType( &(fti->library))){
+ fti->space=0;
+ fti->used=0;
+
+ if(ftinfo_make_insertable(fti)){
+ FT_Done_FreeType(fti->library);
+ free(fti);
+ fti=NULL;
+ }
+ }
+ else {
+ free(fti);
+ fti=NULL;
+ }
+ }
+ if(!fti)FcFini();
+ }
+ return(fti);
+}
+
+/**
+ \brief Make an FT_INFO structure insertable. Adds storage as needed.
+ \param fti pointer to the FT_INFO structure
+ \returns 0 on success, !0 on error.
+*/
+int ftinfo_make_insertable(FT_INFO *fti){
+ int status=0;
+ FNT_SPECS *tmp;
+ if(!fti)return(2);
+ if(fti->used >= fti->space){
+ fti->space += ALLOCINFO_CHUNK;
+ tmp = (FNT_SPECS *) realloc(fti->fonts, fti->space * sizeof(FNT_SPECS) );
+ if(tmp){
+ fti->fonts = tmp;
+ memset(&fti->fonts[fti->used],0,(fti->space - fti->used)*sizeof(FNT_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Insert a copy of a FNT_SPECS structure into the FT_INFO structure.
+ \param fti pointer to the FT_INFO structure.
+ \param fsp pointer to the FNT_SPECS structure.
+ \returns 0 on success, !0 on error.
+*/
+int ftinfo_insert(FT_INFO *fti, FNT_SPECS *fsp){
+ int status=1;
+ if(!fti)return(2);
+ if(!fsp)return(3);
+ if(!(status = ftinfo_make_insertable(fti))){
+ memcpy(&(fti->fonts[fti->used]),fsp,sizeof(FNT_SPECS));
+ fti->used++;
+ }
+ return(status);
+}
+
+
+
+/**
+ \brief Release an FT_INFO structure. Release all associated memory.
+ Use like: fi_ptr = ftinfo_release(fi_ptr)
+ \param fti pointer to the FT_INFO structure.
+ \returns NULL.
+*/
+FT_INFO *ftinfo_release(FT_INFO *fti){
+ (void) ftinfo_clear(fti);
+ FcFini(); /* shut down FontConfig, release memory, patterns must have already been released or boom! */
+ return NULL;
+}
+
+
+/**
+ \brief Clear an FT_INFO structure. Release all Freetype memory but does not release Fontconfig.
+ This would be called in preference to ftinfo_release() if some other part of the program needed
+ to continue using Fontconfig.
+ Use like: fi_ptr = ftinfo_clear(fi_ptr)
+ \param fti pointer to the FT_INFO structure.
+ \returns NULL.
+*/
+FT_INFO *ftinfo_clear(FT_INFO *fti){
+ uint32_t i;
+ FNT_SPECS *fsp;
+ if(fti){
+ for(i=0;i<fti->used;i++){
+ fsp = &(fti->fonts[i]);
+ FT_Done_Face(fsp->face); /* release memory for face controlled by FreeType */
+ free(fsp->file); /* release memory holding copies of paths */
+ free(fsp->fontspec); /* release memory holding copies of font names */
+ FcPatternDestroy(fsp->fpat); /* release memory for FontConfig fpats */
+ FcFontSetDestroy(fsp->fontset);
+ if(fsp->alts){ free(fsp->alts); }
+ }
+ free(fti->fonts);
+ FT_Done_FreeType(fti->library); /* release all other FreeType memory */
+ free(fti);
+ }
+ return NULL;
+}
+
+
+/**
+ \brief Find the loaded font matching fontspec
+ \returns index of font on success, -1 if not found
+ \param tri pointer to the TR_INFO structure.
+ \param fontspec UTF-8 description of the font, as constructed in trinfo_load_fontname
+*/
+
+int ftinfo_find_loaded_by_spec(const FT_INFO *fti, const uint8_t *fontspec){
+ uint32_t i;
+ int status = -1;
+ /* If it is already loaded, do not load it again */
+ for(i=0;i<fti->used;i++){
+ if(0==strcmp((char *) fti->fonts[i].fontspec, (char *)fontspec)){
+ status=i;
+ break;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Find the loaded font matching the source file
+ \returns index of font on success, -1 if not found
+ \param tri pointer to the TR_INFO structure.
+ \param filename UTF-8 file name for the font
+*/
+
+int ftinfo_find_loaded_by_src(const FT_INFO *fti, const uint8_t *filename){
+ uint32_t i;
+ int status = -1;
+ /* If it is already loaded, do not load it again */
+ for(i=0;i<fti->used;i++){
+ if(0==strcmp((char *) fti->fonts[i].file, (char *) filename)){
+ status=i;
+ break;
+ }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Load a (new) font by name into a TR_INFO structure or find it if it is already loaded
+ \returns fi_idx of inserted (or found) font on success, <0 on error.
+ \param fti pointer to the FT_INFO structure.
+ \param fontname UTF-8 font name
+ \param fontspec UTF-8 font specification used for query string.
+*/
+
+int ftinfo_load_fontname(FT_INFO *fti, const char *fontspec){
+ FcPattern *pattern = NULL;
+ FcPattern *fpat = NULL;
+ FcFontSet *fontset = NULL;
+ FcResult result = FcResultMatch;
+ char *filename;
+ double fd;
+ FNT_SPECS *fsp;
+ int status;
+ int fi_idx;
+
+ if(!fti)return(-1);
+
+ /* If it is already loaded, do not load it again */
+ status = ftinfo_find_loaded_by_spec(fti, (uint8_t *) fontspec);
+ if(status >= 0){ return(status); }
+ status = 0; /* was -1, reset to 0 */
+
+ ftinfo_make_insertable(fti);
+ fi_idx = fti->used;
+
+ pattern = FcNameParse((const FcChar8 *)fontspec);
+ while(1) { /* this is NOT a loop, it uses breaks to avoid gotos and deep nesting */
+ if(!(pattern)){ status = -2; break; }
+ if(!FcConfigSubstitute(NULL, pattern, FcMatchPattern)){ status = -3; break; };
+ FcDefaultSubstitute(pattern);
+ /* get a fontset, trimmed to only those with new glyphs as needed, so that missing glyph's may be handled */
+ if(!(fontset = FcFontSort (NULL,pattern, FcTrue, NULL, &result)) || (result != FcResultMatch)){ status = -4; break; }
+ if(!(fpat = FcFontRenderPrepare(NULL, pattern, fontset->fonts[0]))){ status = -405; break; }
+ if(FcPatternGetString( fpat, FC_FILE, 0, (FcChar8 **)&filename) != FcResultMatch){ status = -5; break; }
+ if(FcPatternGetDouble( fpat, FC_SIZE, 0, &fd) != FcResultMatch){ status = -6; break; }
+
+ /* copy these into memory for external use */
+ fsp = &(fti->fonts[fti->used]);
+ fsp->fontset = fontset;
+ fsp->alts = NULL; /* Initially no links to alternate fonts */
+ fsp->space = 0;
+ fsp->file = (uint8_t *) U_strdup((char *) filename);
+ fsp->fontspec = (uint8_t *) U_strdup((char *) fontspec);
+ fsp->fpat = fpat;
+ fsp->fsize = fd;
+ break;
+ }
+ /* release FC's own memory related to this call that does not need to be kept around so that face will work */
+ if(pattern)FcPatternDestroy(pattern); /* done with this memory */
+ if(status<0){
+ if(fontset)FcFontSetDestroy(fontset);
+ if(fpat)FcPatternDestroy(fpat);
+ return(status);
+ }
+
+ /* get the current face */
+ if(FT_New_Face( fti->library, (const char *) fsp->file, 0, &(fsp->face) )){ return(-8); }
+
+ if(FT_Set_Char_Size(
+ fsp->face, /* handle to face object */
+ 0, /* char_width in 1/64th of points */
+ fd*64, /* char_height in 1/64th of points */
+ 72, /* horizontal device resolution, DPI */
+ 72) /* vebrical device resolution, DPI */
+ ){ return(-9); }
+
+ /* The space advance is needed in various places. Get it now, and get it in the font units,
+ so that it can be scaled later with the text size */
+ status = TR_getadvance(fti, fsp,' ',0,FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP, FT_KERNING_UNSCALED, NULL, NULL);
+ if(status < 0)return(-7);
+ fsp->spcadv = ((double) status)/(64.0);
+
+ fti->used++;
+
+/*
+ char *fs;
+ int fb;
+ if(FcPatternGetBool( fpat, FC_OUTLINE, 0, &fb)== FcResultMatch){ printf("outline: %d\n",fb);fflush(stdout); }
+ if(FcPatternGetBool( fpat, FC_SCALABLE, 0, &fb)== FcResultMatch){ printf("scalable: %d\n",fb);fflush(stdout); }
+ if(FcPatternGetDouble( fpat, FC_DPI, 0, &fd)== FcResultMatch){ printf("DPI: %f\n",fd);fflush(stdout); }
+ if(FcPatternGetInteger( fpat, FC_FONTVERSION, 0, &fb)== FcResultMatch){ printf("fontversion: %d\n",fb);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FULLNAME , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAME : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FAMILY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILY : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_STYLE , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLE : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FOUNDRY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FOUNDRY : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FAMILYLANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FAMILYLANG : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_STYLELANG , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("STYLELANG : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FULLNAMELANG, 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FULLNAMELANG: %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_CAPABILITY , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("CAPABILITY : %s\n",fs);fflush(stdout); }
+ if(FcPatternGetString( fpat, FC_FONTFORMAT , 0, (FcChar8 **)&fs)== FcResultMatch){ printf("FONTFORMAT : %s\n",fs);fflush(stdout); }
+*/
+
+ return(fi_idx);
+}
+
+/**
+ \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code.
+ \param tri pointer to the TR_INFO structure.
+*/
+void ftinfo_dump(const FT_INFO *fti){
+ uint32_t i,j;
+ FNT_SPECS *fsp;
+ printf("fti space: %d\n",fti->space);
+ printf("fti used: %d\n",fti->used);
+ for(i=0; i< fti->used; i++){
+ fsp = &(fti->fonts[i]);
+ printf("fti font: %6d space: %6d used: %6d spcadv %8f fsize %8f \n",i,fsp->space,fsp->used,fsp->spcadv,fsp->fsize);
+ printf(" file: %s\n",fsp->file);
+ printf(" fspc: %s\n",fsp->fontspec);
+ for(j=0;j<fsp->used;j++){
+ printf(" alts: %6d fi_idx: %6d wgt: %6d\n",j,fsp->alts[j].fi_idx,fsp->alts[j].weight);
+ }
+ }
+
+}
+
+/**
+ \brief Make the FNT_SPECS alts structure insertable. Adds storage as needed.
+ \param fti pointer to the FT_INFO structure
+ \returns 0 on success, !0 on error.
+*/
+int fsp_alts_make_insertable(FNT_SPECS *fsp){
+ int status=0;
+ ALT_SPECS *tmp;
+ if(!fsp)return(2);
+ if(fsp->used >= fsp->space){
+ fsp->space += ALLOCINFO_CHUNK;
+ tmp = (ALT_SPECS *) realloc(fsp->alts, fsp->space * sizeof(ALT_SPECS) );
+ if(tmp){
+ fsp->alts = tmp;
+ memset(&fsp->alts[fsp->used],0,(fsp->space - fsp->used)*sizeof(ALT_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Insert a new ALT_SPECS into the FNT_SPECS alts list.
+ \param fsp pointer to the FNT_SPECS structure.
+ \param fi_idx font index to add to the alts list
+ \returns 0 on success, !0 on error.
+*/
+int fsp_alts_insert(FNT_SPECS *fsp, uint32_t fi_idx){
+ int status=1;
+ ALT_SPECS alt;
+ if(!fsp)return(3);
+ alt.fi_idx = fi_idx;
+ alt.weight = 1; /* new ones start with this weight, it can only go up */
+ if(!(status = fsp_alts_make_insertable(fsp))){
+ fsp->alts[fsp->used] = alt;
+ fsp->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Increment the weight of an alts entry by 1, readjust order if necessary
+ \param fsp pointer to the FNT_SPECS structure.
+ \param idx index of the alts entry to increment
+ \returns 0 on success, !0 on error.
+*/
+int fsp_alts_weight(FNT_SPECS *fsp, uint32_t a_idx){
+ uint32_t i;
+ ALT_SPECS alt;
+ if(!fsp)return(1);
+ if(!fsp->used)return(2);
+ if(a_idx >= fsp->used)return(3);
+ /* If a counter hits the limit divide all counts in half. */
+ if(fsp->alts[a_idx].weight == UINT32_MAX){
+ for(i=0; i<fsp->used; i++){ fsp->alts[i].weight /= 2; }
+ }
+ fsp->alts[a_idx].weight++;
+ for(i=a_idx; i>0; i--){
+ if(fsp->alts[i-1].weight >= fsp->alts[a_idx].weight)break;
+ alt = fsp->alts[i-1];
+ fsp->alts[i-1] = fsp->alts[a_idx];
+ fsp->alts[a_idx] = alt;
+ }
+ return(0);
+}
+
+
+
+/**
+ \brief Make a CHILD_SPECS structure insertable. Adds storage as needed.
+ \param csp pointer to the CHILD_SPECS structure
+ \returns 0 on success, !0 on error.
+*/
+int csp_make_insertable(CHILD_SPECS *csp){
+ int status=0;
+ int *tmp;
+ if(!csp)return(2);
+ if(csp->used >= csp->space){
+ csp->space += ALLOCINFO_CHUNK;
+ tmp = (int *) realloc(csp->members, csp->space * sizeof(int) );
+ if(tmp){
+ csp->members = tmp;
+ memset(&csp->members[csp->used],0,(csp->space - csp->used)*sizeof(int));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Add a member to a CHILD_SPECS structure. (Member is an index for either a text object or a complex.)
+ \param dst pointer to the CHILD_SPECS structure.
+ \param src index of the member.
+ \returns 0 on success, !0 on error.
+*/
+int csp_insert(CHILD_SPECS *dst, int src){
+ int status=1;
+ if(!dst)return(2);
+ if(!(status=csp_make_insertable(dst))){
+ dst->members[dst->used]=src;
+ dst->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Append all the members of one CHILD_SPECS structure to another CHILD_SPECS structure.
+ Member is an index for either a text object or a complex.
+ The donor is not modified.
+ \param dst pointer to the recipient CHILD_SPECS structure.
+ \param src pointer to the donor CHILD_SPECS structure.
+ \returns 0 on success, !0 on error.
+*/
+int csp_merge(CHILD_SPECS *dst, CHILD_SPECS *src){
+ uint32_t i;
+ int status=1;
+ if(!dst)return(2);
+ if(!src)return(3);
+ for(i=0;i<src->used;i++){
+ status = csp_insert(dst, src->members[i]);
+ if(status)break;
+ }
+ return(status);
+}
+
+/**
+ \brief Release a CHILD_SPECS structure. Release all associated memory.
+ \param csp pointer to the CHILD_SPECS structure.
+ \returns NULL.
+*/
+void csp_release(CHILD_SPECS *csp){
+ if(csp){
+ free(csp->members);
+ csp->space = 0;
+ csp->used = 0;
+ }
+}
+
+/**
+ \brief Clear a CHILD_SPECS structure, making all allocated slots usable. Does not release associated memory.
+ \param csp pointer to the CHILD_SPECS structure.
+ \returns NULL.
+*/
+void csp_clear(CHILD_SPECS *csp){
+ csp->used = 0;
+}
+
+
+/**
+ \brief Initialize an CX_INFO structure. Holds complexes (multiple text objects in known positions and order.)
+ \returns a pointer to the CX_INFO structure created, or NULL on error.
+*/
+CX_INFO *cxinfo_init(void){
+ CX_INFO *cxi = NULL;
+ cxi = (CX_INFO *)calloc(1,sizeof(CX_INFO));
+ if(cxi){
+ if(cxinfo_make_insertable(cxi)){
+ free(cxi);
+ cxi=NULL;
+ }
+ }
+ return(cxi);
+}
+
+/**
+ \brief Make a CX_INFO structure insertable. Adds storage as needed.
+ \returns 0 on success, !0 on error.
+ \param cxi pointer to the CX_INFO structure
+*/
+int cxinfo_make_insertable(CX_INFO *cxi){
+ int status=0;
+ CX_SPECS *tmp;
+ if(cxi->used >= cxi->space){
+ cxi->space += ALLOCINFO_CHUNK;
+ tmp = (CX_SPECS *) realloc(cxi->cx, cxi->space * sizeof(CX_SPECS) );
+ if(tmp){
+ cxi->cx = tmp;
+ memset(&cxi->cx[cxi->used],0,(cxi->space - cxi->used)*sizeof(CX_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Insert a complex into the CX_INFO structure. (Insert may be either TR_TEXT or TR_LINE.)
+ \returns 0 on success, !0 on error.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \param src index of the complex to insert.
+ \param src_rt_tidx index of the bounding rectangle
+ \param type TR_TEXT (index is for tpi->chunks[]) or TR_LINE (index is for cxi->kids[])
+*/
+int cxinfo_insert(CX_INFO *cxi, int src, int src_rt_tidx, enum tr_classes type){
+ int status=1;
+ if(!cxi)return(2);
+ if(!(status=cxinfo_make_insertable(cxi))){
+ cxi->cx[cxi->used].rt_cidx = src_rt_tidx;
+ cxi->cx[cxi->used].type = type;
+ status = csp_insert(&(cxi->cx[cxi->used].kids), src);
+ cxi->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Append a complex to the CX_INFO structure and give it a type.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \param src index of the complex to append.
+ \param type TR_LINE (src is an index for tpi->chunks[]) or TR_PARA (src is an index for cxi->kids[]).
+ \returns 0 on success, !0 on error.
+*/
+int cxinfo_append(CX_INFO *cxi, int src, enum tr_classes type){
+ int status=1;
+ if(!cxi)return(2);
+ if(!(status=cxinfo_make_insertable(cxi))){
+ cxi->cx[cxi->used-1].type = type;
+ status = csp_insert(&(cxi->cx[cxi->used-1].kids), src);
+ }
+ return(status);
+}
+
+
+/**
+ \brief Merge a complex dst with N members (N>=1) by adding a second complex src, and change the type.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \param dst index of the complex to expand.
+ \param src index of the donor complex (which is not modified).
+ \param type TR_LINE (src is an index for tpi->chunks[]) or TR_PARA (src is an index for cxi->kids[]).
+ \returns 0 on success, !0 on error.
+*/
+int cxinfo_merge(CX_INFO *cxi, int dst, int src, enum tr_classes type){
+ int status =1;
+ if(!cxi)return(2);
+ if(!cxi->used)return(3);
+ if(dst < 0 || dst >= (int) cxi->used)return(4);
+ if(src < 0)return(5);
+ cxi->cx[dst].type = type;
+ status = csp_merge(&(cxi->cx[dst].kids), &(cxi->cx[src].kids));
+ return(status);
+}
+
+/**
+ \brief Trim the last complex from thelist of complexes.
+ \param cxi pointer to the CX_INFO structure (complexes).
+ \returns 0 on success, !0 on error.
+*/
+int cxinfo_trim(CX_INFO *cxi){
+ int status = 0;
+ int last ;
+ if(!cxi)return(1);
+ if(!cxi->used)return(2);
+ last = cxi->used - 1;
+ csp_clear(&(cxi->cx[last].kids));
+ cxi->used--;
+ return(status);
+}
+
+
+/**
+ \brief Dump the contents of the TR_INFO structure to stdout. For debugging purposes,not used in production code.
+ \param tri pointer to the TR_INFO structure.
+*/
+void cxinfo_dump(const TR_INFO *tri){
+ uint32_t i,j,k;
+ CX_INFO *cxi = tri->cxi;
+ BR_INFO *bri = tri->bri;
+ TP_INFO *tpi = tri->tpi;
+ BRECT_SPECS *bsp;
+ CX_SPECS *csp;
+ if(cxi){
+ printf("cxi space: %d\n",cxi->space);
+ printf("cxi used: %d\n",cxi->used);
+ printf("cxi phase1: %d\n",cxi->phase1);
+ printf("cxi lines: %d\n",cxi->lines);
+ printf("cxi paras: %d\n",cxi->paras);
+ printf("cxi xy: %f , %f\n",tri->x,tri->y);
+
+ for(i=0;i<cxi->used;i++){
+ csp = &(cxi->cx[i]);
+ bsp = &(bri->rects[csp->rt_cidx]);
+ printf("cxi cx[%d] type:%d rt_tidx:%d kids_used:%d kids_space:%d\n",i, csp->type, csp->rt_cidx, csp->kids.used, csp->kids.space);
+ printf("cxi cx[%d] br (LL,UR) (%f,%f),(%f,%f)\n",i,bsp->xll,bsp->yll,bsp->xur,bsp->yur);
+ for(j=0;j<csp->kids.used;j++){
+ k = csp->kids.members[j];
+ bsp = &(bri->rects[k]);
+ if(csp->type == TR_TEXT || csp->type == TR_LINE){
+ printf("cxi cx[%d] member:%3d tp_idx:%3d ldir:%d rt_tidx:%3d br (LL,UR) (%8.3f,%8.3f),(%8.3f,%8.3f) xy (%8.3f,%8.3f) kern (%8.3f,%8.3f) text:<%s> decor:%5.5x\n",
+ i, j, k, tpi->chunks[k].ldir, tpi->chunks[k].rt_tidx,
+ bsp->xll,bsp->yll,bsp->xur,bsp->yur,
+ tpi->chunks[k].x, tpi->chunks[k].y,
+ tpi->chunks[k].xkern, tpi->chunks[k].ykern,
+ tpi->chunks[k].string, tpi->chunks[k].decoration );
+ }
+ else { /* TR_PARA_* */
+ printf("cxi cx[%d] member:%d cx_idx:%d\n",i, j, k);
+ }
+ }
+ }
+ }
+ return;
+}
+
+/**
+ \brief Release a CX_INFO structure. Release all associated memory.
+ use like: cxi = cxiinfo_release(cxi);
+ \param cxi pointer to the CX_INFO structure.
+ \returns NULL.
+*/
+CX_INFO *cxinfo_release(CX_INFO *cxi){
+ uint32_t i;
+ if(cxi){
+ for(i=0;i<cxi->used;i++){ csp_release(&cxi->cx[i].kids); }
+ free(cxi->cx);
+ free(cxi); /* release the overall cxinfo structure */
+ }
+ return NULL;
+}
+
+
+/**
+ \brief Initialize an TP_INFO structure. Holds text objects from which complexes are built.
+ \returns a pointer to the TP_INFO structure created, or NULL on error.
+*/
+TP_INFO *tpinfo_init(void){
+ TP_INFO *tpi = NULL;
+ tpi = (TP_INFO *)calloc(1,sizeof(TP_INFO));
+ if(tpi){
+ if(tpinfo_make_insertable(tpi)){
+ free(tpi);
+ tpi=NULL;
+ }
+ }
+ return(tpi);
+}
+
+
+/**
+ \brief Make a TP_INFO structure insertable. Adds storage as needed.
+ \returns 0 on success, !0 on error.
+ \param tpi pointer to the TP_INFO structure
+*/
+int tpinfo_make_insertable(TP_INFO *tpi){
+ int status=0;
+ TCHUNK_SPECS *tmp;
+ if(tpi->used >= tpi->space){
+ tpi->space += ALLOCINFO_CHUNK;
+ tmp = (TCHUNK_SPECS *) realloc(tpi->chunks, tpi->space * sizeof(TCHUNK_SPECS) );
+ if(tmp){
+ tpi->chunks = tmp;
+ memset(&tpi->chunks[tpi->used],0,(tpi->space - tpi->used)*sizeof(TCHUNK_SPECS));
+ }
+ else {
+ status=1;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Insert a copy of a TCHUNK_SPECS structure into a TP_INFO structure. (Insert a text object.)
+ \returns 0 on success, !0 on error.
+ \param tpi pointer to the TP_INFO structure
+ \param tsp pointer to the TCHUNK_SPECS structure
+*/
+int tpinfo_insert(TP_INFO *tpi, const TCHUNK_SPECS *tsp){
+ int status=1;
+ TCHUNK_SPECS *ltsp;
+ if(!tpi)return(2);
+ if(!tsp)return(3);
+ if(!(status = tpinfo_make_insertable(tpi))){
+ ltsp = &(tpi->chunks[tpi->used]);
+ memcpy(ltsp,tsp,sizeof(TCHUNK_SPECS));
+ if(tsp->co)ltsp->condensed = 75; /* Narrow was set in the font name */
+ ltsp->xkern = ltsp->ykern = 0.0; /* kerning will be calculated from the derived layout */
+ tpi->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Release a TP_INFO structure. Release all associated memory.
+ use like: tpi = tpinfo_release(tpi);
+ \returns NULL.
+ \param tpi pointer to the TP_INFO structure.
+*/
+TP_INFO *tpinfo_release(TP_INFO *tpi){
+ uint32_t i;
+ if(tpi){
+ for(i=0;i<tpi->used;i++){
+ free(tpi->chunks[i].string); }
+ free(tpi->chunks); /* release the array */
+ free(tpi); /* release the overall tpinfo structure */
+ }
+ return NULL;
+}
+
+/**
+ \brief Initialize an BR_INFO structure. Holds bounding rectangles, for both text objects and complexes.
+ \returns a pointer to the BR_INFO structure created, or NULL on error.
+*/
+BR_INFO *brinfo_init(void){
+ BR_INFO *bri = NULL;
+ bri = (BR_INFO *)calloc(1,sizeof(BR_INFO));
+ if(bri){
+ if(brinfo_make_insertable(bri)){
+ free(bri);
+ bri=NULL;
+ }
+ }
+ return(bri);
+}
+
+/**
+ \brief Make a BR_INFO structure insertable. Adds storage as needed.
+ \returns 0 on success, !0 on error.
+ \param bri pointer to the BR_INFO structure
+*/
+int brinfo_make_insertable(BR_INFO *bri){
+ int status=0;
+ BRECT_SPECS *tmp;
+ if(!bri)return(2);
+ if(bri->used >= bri->space){
+ bri->space += ALLOCINFO_CHUNK;
+ tmp = (BRECT_SPECS *) realloc(bri->rects, bri->space * sizeof(BRECT_SPECS) );
+ if(tmp){ bri->rects = tmp; }
+ else { status = 1;}
+ }
+ return(status);
+}
+
+/**
+ \brief Insert a copy of a BRECT_SPEC structure into a BR_INFO structure. (Insert a bounding rectangle.)
+ \returns 0 on success, !0 on error.
+ \param bri pointer to the BR_INFO structure
+ \param element pointer to the BRECT_SPECS structure
+*/
+int brinfo_insert(BR_INFO *bri, const BRECT_SPECS *element){
+ int status=1;
+ if(!bri)return(2);
+ if(!(status=brinfo_make_insertable(bri))){
+ memcpy(&(bri->rects[bri->used]),element,sizeof(BRECT_SPECS));
+ bri->used++;
+ }
+ return(status);
+}
+
+/**
+ \brief Merge BRECT_SPEC element src into/with BRECT_SPEC element dst. src is unchanged. (Merge two bounding rectangles.)
+ \returns 0 on success, !0 on error.
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+*/
+int brinfo_merge(BR_INFO *bri, int dst, int src){
+ if(!bri)return(1);
+ if(!bri->used)return(2);
+ if(dst<0 || dst >= (int) bri->used)return(3);
+ if(src<0 || src >= (int) bri->used)return(4);
+ bri->rects[dst].xll = TEREMIN(bri->rects[dst].xll, bri->rects[src].xll);
+ bri->rects[dst].yll = TEREMAX(bri->rects[dst].yll, bri->rects[src].yll); /* MAX because Y is positive DOWN */
+ bri->rects[dst].xur = TEREMAX(bri->rects[dst].xur, bri->rects[src].xur);
+ bri->rects[dst].yur = TEREMIN(bri->rects[dst].yur, bri->rects[src].yur); /* MIN because Y is positive DOWN */
+/*
+printf("bri_Merge into rect:%d (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",dst,
+(bri->rects[dst].xll),
+(bri->rects[dst].yll),
+(bri->rects[dst].xur),
+(bri->rects[dst].yur),
+(bri->rects[src].xll),
+(bri->rects[src].yll),
+(bri->rects[src].xur),
+(bri->rects[src].yur));
+*/
+ return(0);
+}
+
+/**
+ \brief Check for an allowable overlap of two bounding rectangles.
+ Allowable overlap is any area overlap of src and dst bounding rectangles, after
+ they have been expanded (padded) by allowed edge expansions. (For instance, if
+ missing spaces must be accounted for.)
+ The method works backwards: look for all reasons they might not overlap,
+ if none are found, then the rectangles do overlap.
+ An overlap here does not count just a line or a point - area must be involved.
+ \returns 0 on success (overlap detected), 1 on no overlap, anything else is an error.
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+ \param rp_dst Pointer to edge padding values for dst.
+ \param rp_src Pointer to edge padding values for src.
+*/
+int brinfo_overlap(const BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src){
+ int status;
+ BRECT_SPECS *br_dst;
+ BRECT_SPECS *br_src;
+ if(!bri || !rp_dst || !rp_src)return(2);
+ if(!bri->used)return(3);
+ if(dst<0 || dst>= (int) bri->used)return(4);
+ if(src<0 || src>= (int) bri->used)return(5);
+ br_dst=&bri->rects[dst];
+ br_src=&bri->rects[src];
+ if( /* Test all conditions that exclude overlap, if any are true, then no overlap */
+ ((br_dst->xur + rp_dst->right) < (br_src->xll - rp_src->left) ) || /* dst fully to the left */
+ ((br_dst->xll - rp_dst->left) > (br_src->xur + rp_src->right) ) || /* dst fully to the right */
+ ((br_dst->yur - rp_dst->up) > (br_src->yll + rp_src->down) ) || /* dst fully below (Y is positive DOWN) */
+ ((br_dst->yll + rp_dst->down) < (br_src->yur - rp_src->up) ) /* dst fully above (Y is positive DOWN) */
+ ){
+ status = 1;
+ }
+ else {
+ /* overlap not excluded, so it must occur.
+ Only accept overlaps that are mostly at one end or the other, not mostly top or bottom.
+ If the following condition is true then there is no more than a tiny bit of horizontal overlap of src
+ within dist, which suggests that the two pieces of text may be considered part of one line.
+ (For a vertical alphabet the same method could be used for up/down.) */
+ if(
+ (br_src->xll >= br_dst->xur - rp_dst->right) || /* src overlaps just a little on the right (L->R language) */
+ (br_src->xur <= br_dst->xll + rp_dst->left) /* src overlaps just a little on the left (R->L language) */
+ ){
+ status = 0;
+ }
+ else { /* Too much overlap, reject the overlap */
+ status = 1;
+ }
+ }
+/*
+printf("Overlap status:%d\nOverlap trects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+status,
+(br_dst->xll - rp_dst->left ),
+(br_dst->yll - rp_dst->down ),
+(br_dst->xur + rp_dst->right),
+(br_dst->yur + rp_dst->up ),
+(br_src->xll - rp_src->left ),
+(br_src->yll - rp_src->down ),
+(br_src->xur + rp_src->right),
+(br_src->yur + rp_src->up ));
+printf("Overlap brects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+(br_dst->xll),
+(br_dst->yll),
+(br_dst->xur),
+(br_dst->yur),
+(br_src->xll),
+(br_src->yll),
+(br_src->xur),
+(br_src->yur));
+printf("Overlap rprect (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+(rp_dst->left),
+(rp_dst->down),
+(rp_dst->right),
+(rp_dst->up),
+(rp_src->left),
+(rp_src->down),
+(rp_src->right),
+(rp_src->up));
+*/
+ return(status);
+}
+
+/**
+ \brief Check for various sorts of invalid text elements upstream (language dir changes, draw order backwards from language direction)
+ \returns 0 on success (not upstream), 1 if upstream, anything else is an error.
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+ \param ddir direction of dst
+ \param sdir direction of src
+*/
+
+int brinfo_upstream(BR_INFO *bri, int dst, int src, int ddir, int sdir){
+ int status=0;
+ BRECT_SPECS *br_dst;
+ BRECT_SPECS *br_src;
+ if(!bri)return(2);
+ if(!bri->used)return(3);
+ if(dst<0 || dst>= (int) bri->used)return(4);
+ if(src<0 || src>= (int) bri->used)return(5);
+ br_dst=&bri->rects[dst];
+ br_src=&bri->rects[src];
+ if( ddir == LDIR_RL && sdir == LDIR_LR){
+ if(br_dst->xur <= (br_src->xll + br_src->xur)/2.0){ status = 1; }
+ }
+ else if( ddir == LDIR_LR && sdir == LDIR_RL){
+ if((br_src->xll + br_src->xur)/2.0 <= br_dst->xll ){ status = 1; }
+ }
+ else if( ddir == LDIR_RL && sdir == LDIR_RL){
+ if(br_dst->xur <= (br_src->xll + br_src->xur)/2.0){ status = 1; }
+ }
+ else if( ddir == LDIR_LR && sdir == LDIR_LR){
+ if((br_src->xll + br_src->xur)/2.0 <= br_dst->xll ){ status = 1; }
+ }
+ return(status);
+}
+
+
+/**
+ \brief Try to deduce justification of a paragraph from the bounding rectangles for two successive lines.
+ \returns one of TR_PARA_ UJ (unknown justified), LJ, CJ, or RJ (left, center, or right justified).
+ \param bri pointer to the BR_INFO structure
+ \param dst index of the destination bounding rectangle.
+ \param src index of the source bounding rectangle.
+ \param slop allowed error in edge alignment.
+ \param type Preexisting justification for dst, if any. Justification of dst and src must match this or
+ TR_PARA_UJ is returned even if dst and src have some (other) alignment.
+*/
+enum tr_classes brinfo_pp_alignment(const BR_INFO *bri, int dst, int src, double slop, enum tr_classes type){
+ enum tr_classes newtype;
+ BRECT_SPECS *br_dst = & bri->rects[dst];
+ BRECT_SPECS *br_src = & bri->rects[src];
+ if((br_dst->yur >= br_src->yur) || (br_dst->yll >= br_src->yll)){ /* Y is positive DOWN */
+ /* lines in the wrong vertical order, no paragraph possible (Y is positive down) */
+ newtype = TR_PARA_UJ;
+ }
+ else if(fabs(br_dst->xll - br_src->xll) < slop){
+ /* LJ (might also be CJ but LJ takes precedence) */
+ newtype = TR_PARA_LJ;
+ }
+ else if(fabs(br_dst->xur - br_src->xur) < slop){
+ /* RJ */
+ newtype = TR_PARA_RJ;
+ }
+ else if(fabs( (br_dst->xur + br_dst->xll)/2.0 - (br_src->xur + br_src->xll)/2.0 ) < slop){
+ /* CJ */
+ newtype = TR_PARA_CJ;
+ }
+ else {
+ /* not aligned */
+ newtype = TR_PARA_UJ;
+ }
+ /* within a paragraph type can change from unknown to known, but not from one known type to another*/
+ if((type != TR_PARA_UJ) && (newtype != type)){
+ newtype = TR_PARA_UJ;
+ }
+/*
+printf("pp_align newtype:%d brects (LL,UR) dst:(%f,%f),(%f,%f) src:(%f,%f),(%f,%f)\n",
+newtype,
+(br_dst->xll),
+(br_dst->yll),
+(br_dst->xur),
+(br_dst->yur),
+(br_src->xll),
+(br_src->yll),
+(br_src->xur),
+(br_src->yur));
+*/
+ return(newtype);
+}
+
+/**
+ \brief Release a BR_INFO structure. Release all associated memory.
+ use like: bri = brinfo_release(bri);
+ \param bri pointer to the BR_INFO structure.
+ \returns NULL.
+*/
+BR_INFO *brinfo_release(BR_INFO *bri){
+ if(bri){
+ free(bri->rects);
+ free(bri); /* release the overall brinfo structure */
+ }
+ return NULL;
+}
+
+
+
+/**
+ \brief Initialize an TR_INFO structure. Holds all data for text reassembly.
+ \returns a pointer to the TR_INFO structure created, or NULL on error.
+*/
+TR_INFO *trinfo_init(TR_INFO *tri){
+ if(tri)return(tri); /* tri is already set, double initialization is not allowed */
+ if(!(tri = (TR_INFO *)calloc(1,sizeof(TR_INFO))) ||
+ !(tri->fti = ftinfo_init()) ||
+ !(tri->tpi = tpinfo_init()) ||
+ !(tri->bri = brinfo_init()) ||
+ !(tri->cxi = cxinfo_init())
+ ){ tri = trinfo_release(tri); }
+ tri->out = NULL; /* This will allocate as needed, it might not ever be needed. */
+ tri->qe = 0.0;
+ tri->esc = 0.0;
+ tri->x = DBL_MAX;
+ tri->y = DBL_MAX;
+ tri->dirty = 0;
+ tri->use_kern = 1;
+ tri->load_flags = FT_LOAD_NO_SCALE;
+ tri->kern_mode = FT_KERNING_UNSCALED;
+ tri->outspace = 0;
+ tri->outused = 0;
+ tri->usebk = BKCLR_NONE;
+ memset(&(tri->bkcolor),0,sizeof(TRCOLORREF));
+ return(tri);
+}
+
+/**
+ \brief Release a TR_INFO structure completely.
+ Release all associated memory, including FontConfig.
+ See also trinfo_clear() and trinfo_release_except_FC().
+ use like: tri = trinfo_release(tri);
+ \param tri pointer to the TR_INFO structure.
+ \returns NULL.
+*/
+TR_INFO *trinfo_release(TR_INFO *tri){
+ if(tri){
+ if(tri->bri)tri->bri=brinfo_release(tri->bri);
+ if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi);
+ if(tri->fti)tri->fti=ftinfo_release(tri->fti);
+ if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi);
+ if(tri->out){ free(tri->out); tri->out=NULL; };
+ free(tri);
+ }
+ return(NULL);
+}
+
+/**
+ \brief Release a TR_INFO structure mostly.
+ Release all associated memory EXCEPT Fontconfig.
+ Fontconfig may still be needed elsewhere in a program and there is no way to figure that out here.
+ See also trinfo_clear() and trinfo_release().
+ use like: tri = trinfo_release_except_FC(tri);
+ \param tri pointer to the TR_INFO structure.
+ \returns NULL.
+*/
+TR_INFO *trinfo_release_except_FC(TR_INFO *tri){
+ if(tri){
+ if(tri->bri)tri->bri=brinfo_release(tri->bri);
+ if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi);
+ if(tri->fti)tri->fti=ftinfo_clear(tri->fti);
+ if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi);
+ if(tri->out){ free(tri->out); tri->out=NULL; };
+ free(tri);
+ }
+ return(NULL);
+}
+
+/**
+ \brief Clear a TR_INFO structure.
+ Releases text and rectangle information, but retains font information, both
+ Freetype information and Fontconfig information.
+ See also trinfo_release() and trinfo_release_except_FC().
+ Use like: tri = trinfo_clear(tri);
+ \param tri pointer to the TR_INFO structure.
+ \returns NULL.
+*/
+TR_INFO *trinfo_clear(TR_INFO *tri){
+ if(tri){
+
+ if(tri->bri)tri->bri=brinfo_release(tri->bri);
+ if(tri->tpi)tri->tpi=tpinfo_release(tri->tpi);
+ if(tri->cxi)tri->cxi=cxinfo_release(tri->cxi);
+ if(tri->out){
+ free(tri->out);
+ tri->out = NULL;
+ tri->outused = 0;
+ tri->outspace = 0;
+ };
+ /* Do NOT modify: qe, use_kern, usebk, load_flags, kern_mode, or bkcolor. Set the rest back to their defaults */
+ tri->esc = 0.0;
+ tri->x = DBL_MAX;
+ tri->y = DBL_MAX;
+ tri->dirty = 0;
+ if(!(tri->tpi = tpinfo_init()) || /* re-init the pieces just released */
+ !(tri->bri = brinfo_init()) ||
+ !(tri->cxi = cxinfo_init())
+ ){
+ tri = trinfo_release(tri); /* something horrible happened, clean out tri and return NULL */
+ }
+ }
+ return(tri);
+}
+
+
+/**
+ \brief Set the quantization error value for a TR_INFO structure.
+ If coordinates have passed through an integer form limits
+ in accuracy may have been imposed. For instance, if the X coordinate of a point in such a file
+ is 1000, and the conversion factor from those coordinates to points is .04, then eq is .04. This
+ just says that single coordinates are only good to within .04, and two coordinates may differ by as much
+ as .08, just due to quantization error. So if some calculation shows a difference of
+ .02 it may be interpreted as this sort of error and set to 0.0.
+ \returns 0 on success, !0 on error.
+ \param tri pointer to TR_INFO structure
+ \param qe quantization error.
+*/
+int trinfo_load_qe(TR_INFO *tri, double qe){
+ if(!tri)return(1);
+ if(qe<0.0)return(2);
+ tri->qe=qe;
+ return(0);
+}
+
+/**
+ \brief Set the background color and whether or not to use it.
+ When background color is turned on each line of text is underwritten with a rectangle
+ of the specified color. The rectangle is the merged bounding rectangle for that line.
+ \returns 0 on success but nothing changed, >0 on error, <0 on success and a value changed.
+ \param tri pointer to TR_INFO structure
+ \param usebk 0 for no background, anything else uses background color
+ \param bkcolor background color to use
+*/
+int trinfo_load_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor){
+ int status=0;
+ if(!tri){ status = 1; }
+ else {
+ if((usebk < BKCLR_NONE) || (usebk > BKCLR_ALL)){ status = 2; }
+ else {
+ status = trinfo_check_bk(tri, usebk, bkcolor);
+ tri->usebk = usebk;
+ tri->bkcolor = bkcolor;
+ }
+ }
+ return(status);
+}
+
+/**
+ \brief Are the proposed new background and background color a change?
+ \returns 0 if they are the same, -1 if either is different
+ \param tri pointer to TR_INFO structure
+ \param usebk 0 for no background, anything else uses background color
+ \param bkcolor background color to use
+*/
+int trinfo_check_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor){
+ int status = 0;
+ if( (tri->usebk != usebk) || memcmp(&tri->bkcolor,&bkcolor,sizeof(TRCOLORREF))){ status = -1; }
+ return(status);
+}
+
+/**
+ \brief Set Freetype parameters and kerning mode (if any) in a TRI_INFO structure.
+ \returns 0 on success, !0 on error.
+ \param tri pointer to a TR_INFO structure
+ \param use_kern 0 if kerning is to be employed, !0 otherwise.
+ \param load_flags Controls internal advance:
+ FT_LOAD_NO_SCALE, internal advance is in 1/64th of a point. (kerning values are still scaled)
+ FT_LOAD_TARGET_NORMAL internal advance is in 1/64th of a point. The scale
+ factor seems to be (Font Size in points)*(DPI)/(32.0 pnts)*(72 dpi).
+ \param kern_mode FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED. Set to match calling application.
+*/
+int trinfo_load_ft_opts(TR_INFO *tri, int use_kern, int load_flags, int kern_mode){
+ if(!tri)return(1);
+ tri->use_kern = use_kern;
+ tri->load_flags = load_flags;
+ tri->kern_mode = kern_mode;
+ return(0);
+}
+
+/**
+ \brief Append text to a TR_INFO struct's output buffer, expanding it if necessary.
+ \returns 0 on success, !0 on error.
+ \param tri pointer to a TR_INFO structure
+ \param src Pointer to a text string.
+*/
+int trinfo_append_out(TR_INFO *tri, const char *src){
+ size_t slen;
+ uint8_t *tmp;
+ if(!src)return(-1);
+ slen = strlen(src);
+ if(tri->outused + (int) slen + 1 >= tri->outspace){
+ tri->outspace += TEREMAX(ALLOCOUT_CHUNK,slen+1);
+ tmp = realloc(tri->out, tri->outspace * sizeof(uint8_t) );
+ if(tmp){ tri->out = tmp; }
+ else { return(-1); }
+ }
+ memcpy(tri->out + tri->outused, src, slen+1); /* copy the terminator */
+ tri->outused += slen; /* do not count the terminator in the length */
+ return(0);
+}
+
+
+/**
+ \brief Load a text object into a TR_INFO struct.
+ \returns 0 on success, !0 on error. -1 means that the escapement is different from the objects already loaded.
+ \param tri pointer to a TR_INFO structure
+ \param tsp pointer to a TCHUNK_SPECS structure (text object to load)
+ \param escapement angle in degrees of the text object.
+ \param flags special processing flags:
+ TR_EMFBOT calculate Y coordinates of ALIBOT object compatible with EMF files TA_BOTTOM alignment.
+*/
+int trinfo_load_textrec(TR_INFO *tri, const TCHUNK_SPECS *tsp, double escapement, int flags){
+
+ int status;
+ double x,y,xe;
+ double asc,dsc; /* these are the ascender/descender for the actual text */
+ int ymin,ymax;
+ double fasc,fdsc; /* these are the ascender/descender for the font as a whole (text independent) */
+ TP_INFO *tpi;
+ FT_INFO *fti;
+ BR_INFO *bri;
+ int current,idx,taln;
+ uint32_t prev;
+ uint32_t *text32,*tptr;
+ FNT_SPECS *fsp;
+ BRECT_SPECS bsp;
+
+ /* check incoming parameters */
+ if(!tri)return(1);
+ if(!tsp)return(2);
+ if(!tsp->string)return(3);
+ fti = tri->fti;
+ tpi = tri->tpi;
+ bri = tri->bri;
+ idx = tsp->fi_idx;
+ taln = tsp->taln;
+ if(!fti->used)return(4);
+ if(idx <0 || idx >= (int) fti->used)return(5);
+ fsp = &(fti->fonts[idx]);
+
+ if(!tri->dirty){
+ tri->x = tsp->x;
+ tri->y = tsp->y;
+ tri->esc = escapement;
+ tri->dirty = 1;
+ }
+ else {
+ if(tri->esc != escapement)return(-1);
+ }
+
+
+ tpinfo_insert(tpi,tsp);
+ current=tpi->used-1;
+ ymin = 64000;
+ ymax = -64000;
+
+ /* The geometry model has origin Y at the top of screen, positive Y is down, maximum positive
+ Y is at the bottom of the screen. That makes "top" (by positive Y) actually the bottom
+ (as viewed on the screen.) */
+
+ escapement *= 2.0 * M_PI / 360.0; /* degrees to radians */
+ x = tpi->chunks[current].x - tri->x; /* convert to internal orientation */
+ y = tpi->chunks[current].y - tri->y;
+ tpi->chunks[current].x = x * cos(escapement) - y * sin(escapement); /* coordinate transformation */
+ tpi->chunks[current].y = x * sin(escapement) + y * cos(escapement);
+
+/* Careful! face bbox does NOT scale with FT_Set_Char_Size
+printf("Face idx:%d bbox: xMax/Min:%ld,%ld yMax/Min:%ld,%ld UpEM:%d asc/des:%d,%d height:%d size:%f\n",
+ idx,
+ fsp->face->bbox.xMax,fsp->face->bbox.xMin,
+ fsp->face->bbox.yMax,fsp->face->bbox.yMin,
+ fsp->face->units_per_EM,fsp->face->ascender,fsp->face->descender,fsp->face->height,fsp->fsize);
+*/
+
+ text32 = U_Utf8ToUtf32le((char *) tsp->string,0,NULL);
+ if(!text32){ // LATIN1 encoded >128 are generally not valid UTF, so the first will fail
+ text32 = U_Latin1ToUtf32le((char *) tsp->string,0,NULL);
+ if(!text32)return(5);
+ }
+ /* baseline advance is independent of character orientation */
+ for(xe=0.0, prev=0, tptr=text32; *tptr; tptr++){
+ status = TR_getadvance(fti, fsp, *tptr, (tri->use_kern ? prev: 0), tri->load_flags, tri->kern_mode, &ymin, &ymax);
+ if(status>=0){
+ xe += ((double) status)/64.0;
+ }
+ else { return(6); }
+ prev=*tptr;
+ }
+
+ /* Some glyphs in fonts have no vertical extent, for instance, Hebrew glyphs in Century Schoolbook L.
+ Use the 3/4 of the font size as a (very bad) approximation for the actual values. */
+ if(ymin==0 && ymax==0){
+ ymax = 0.75 * fsp->fsize * 64.0;
+ }
+
+ asc = ((double) (ymax))/64.0;
+ dsc = ((double) (ymin))/64.0; /* This is negative */
+/* This did not work very well because the ascender/descender went well beyond the actual characters, causing
+ overlaps on lines that did not actually overlap (vertically).
+ asc = ((double) (fsp->face->ascender) )/64.0;
+ dsc = ((double) (fsp->face->descender))/64.0;
+*/
+
+ free(text32);
+
+ /* find the font ascender descender (general one, not specific for current text) */
+ fasc = ((double) (fsp->face->ascender) )/64.0;
+ fdsc = ((double) (fsp->face->descender))/64.0;
+
+ /* originally the denominator was just 32.0, but it broke when units_per_EM wasn't 2048 */
+ double fixscale = tsp->fs/(((double) fsp->face->units_per_EM)/64.0);
+ if(tri->load_flags & FT_LOAD_NO_SCALE) xe *= fixscale;
+
+ /* now place the rectangle using ALN information */
+ if( taln & ALIHORI & ALILEFT ){
+ bsp.xll = tpi->chunks[current].x;
+ bsp.xur = tpi->chunks[current].x + xe;
+ }
+ else if( taln & ALIHORI & ALICENTER){
+ bsp.xll = tpi->chunks[current].x - xe/2.0;
+ bsp.xur = tpi->chunks[current].x + xe/2.0;
+ }
+ else{ /* taln & ALIHORI & ALIRIGHT */
+ bsp.xll = tpi->chunks[current].x - xe;
+ bsp.xur = tpi->chunks[current].x;
+ }
+ tpi->chunks[current].ldir = tsp->ldir;
+
+ if(tri->load_flags & FT_LOAD_NO_SCALE){
+ asc *= fixscale;
+ dsc *= fixscale;
+ fasc *= fixscale;
+ fdsc *= fixscale;
+ }
+
+
+ /* From this point forward y is on the baseline, so need to correct it in chunks. The asc/dsc are the general
+ ones for the font, else the text content will muck around with the baseline in BAD ways. */
+ if( taln & ALIVERT & ALITOP ){ tpi->chunks[current].y += fasc; }
+ else if( taln & ALIVERT & ALIBASE){ } /* no correction required */
+ else{ /* taln & ALIVERT & ALIBOT */
+ if(flags & TR_EMFBOT){ tpi->chunks[current].y -= 0.35 * tsp->fs; } /* compatible with EMF implementations */
+ else { tpi->chunks[current].y += fdsc; }
+ }
+ tpi->chunks[current].boff = -dsc;
+
+ /* since y is always on the baseline, the lower left and upper right are easy. These use asc/dsc for the particular text,
+ so that the bounding box will fit it tightly. */
+ bsp.yll = tpi->chunks[current].y - dsc;
+ bsp.yur = tpi->chunks[current].y - asc;
+ brinfo_insert(bri,&bsp);
+ tpi->chunks[current].rt_tidx = bri->used - 1; /* index of rectangle that contains it */
+
+ return(0);
+}
+
+/**
+ \brief Fontweight conversion. Fontconfig units to SVG units.
+ Anything not recognized becomes "normal" == 400.
+ There is no interpolation because a value that mapped to 775, for instance, most
+ likely would not display properly because it is intermediate between 700 and 800, and
+ only those need be supported in SVG viewers.
+ \returns SVG font weight
+ \param weight Fontconfig font weight.
+*/
+int TR_weight_FC_to_SVG(int weight){
+ int ret=400;
+ if( weight == 0){ ret = 100; }
+ else if(weight == 40){ ret = 200; }
+ else if(weight == 50){ ret = 300; }
+ else if(weight == 80){ ret = 400; }
+ else if(weight == 100){ ret = 500; }
+ else if(weight == 180){ ret = 600; }
+ else if(weight == 200){ ret = 700; }
+ else if(weight == 205){ ret = 800; }
+ else if(weight == 210){ ret = 900; }
+ else { ret = 400; }
+ return(ret);
+}
+
+/**
+ \brief Set the padding that will be added to bounding rectangles before checking for overlaps in brinfo_overlap().
+ \returns void
+ \param rt_pad pointer to an RT_PAD structure.
+ \param up padding for the top of a bounding rectangle.
+ \param down padding for the bottom of a bounding rectangle.
+ \param left padding for the left of a bounding rectangle.
+ \param right padding for the right of a bounding rectangle.
+*/
+void TR_rt_pad_set(RT_PAD *rt_pad, double up, double down, double left, double right){
+ rt_pad->up = up;
+ rt_pad->down = down;
+ rt_pad->left = left;
+ rt_pad->right = right;
+}
+
+/**
+ \brief Convert from analyzed complexes to SVG format.
+ \returns void
+ \param tri pointer to a TR_INFO struct which will be analyzed. Result is stored in its "out" buffer.
+*/
+void TR_layout_2_svg(TR_INFO *tri){
+ double x = tri->x;
+ double y = tri->y;
+ double dx,dy;
+ double esc;
+ double recenter; /* horizontal offset to set things up correctly for CJ and RJ text, is 0 for LJ*/
+ double lineheight=1.25;
+ int cutat;
+ FT_INFO *fti=tri->fti; /* Font info storage */
+ TP_INFO *tpi=tri->tpi; /* Text Info/Position Info storage */
+ BR_INFO *bri=tri->bri; /* bounding Rectangle Info storage */
+ CX_INFO *cxi=tri->cxi; /* Complexes deduced for this text */
+ TCHUNK_SPECS *tsp; /* current text object */
+ CX_SPECS *csp;
+ CX_SPECS *cline_sp;
+ unsigned int i,j,k,jdx,kdx;
+ int ldir;
+ char obuf[1024]; /* big enough for style and so forth */
+ char cbuf[16]; /* big enough for one hex color */
+
+ char stransform[128];
+ double newx,newy,tmpx;
+ uint32_t utmp;
+
+ /* copy the current numeric locale, make a copy because setlocale may stomp on
+ the memory it points to. Then change it because SVG needs decimal points,
+ not commas, in floats. Restore on exit from this routine.
+ */
+ char *prev_locale = setlocale(LC_NUMERIC,NULL);
+ char *hold_locale = malloc(sizeof(char) * (strlen(prev_locale) + 1));
+ strcpy(hold_locale,prev_locale);
+ (void) setlocale(LC_NUMERIC,"POSIX");
+
+/*
+#define DBG_TR_PARA 0
+#define DBG_TR_INPUT 1
+*/
+ /* The debug section below is difficult to see if usebk is anything other than BKCLR_NONE */
+#if DBG_TR_PARA || DBG_TR_INPUT /* enable debugging code, writes extra information into SVG */
+ /* put rectangles down for each text string - debugging!!! This will not work properly for any Narrow fonts */
+ esc = tri->esc;
+ esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */
+ sprintf(stransform,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y);
+ for(i=cxi->phase1; i<cxi->used;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */
+ csp = &(cxi->cx[i]);
+ for(j=0; j<csp->kids.used; j++){ /* over all members of these complexes, which are phase1 complexes */
+ jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */
+ for(k=0; k<cxi->cx[jdx].kids.used; k++){ /* over all members of the phase1 complex */
+ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */
+ tsp = &tpi->chunks[kdx];
+ ldir = tsp->ldir;
+ if(!j && !k){
+#if DBG_TR_PARA
+ TRPRINT(tri, "<rect\n");
+ TRPRINT(tri, "style=\"color:#0000FF;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:none;stroke:#000000;stroke-width:0.8;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n");
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].xll),1.25*(bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+#endif /* DBG_TR_PARA */
+ }
+#if DBG_TR_INPUT /* debugging code, this section writes the original text objects */
+ newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll);
+ newy = 1.25*(bri->rects[tsp->rt_tidx].yur);
+ TRPRINT(tri, "<rect\n");
+ TRPRINT(tri, "style=\"color:#000000;color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:none;stroke:#00FF00;stroke-width:0.3;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n");
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].xll),newy);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+
+ newy = 1.25*(bri->rects[tsp->rt_tidx].yll - tsp->boff);
+ sprintf(obuf,"<text x=\"%f\" y=\"%f\"\n",newx, newy );
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"xml:space=\"preserve\"\n");
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "style=\"fill:#FF0000;");
+ sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal"));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "font-variant:normal;");
+ sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed"));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"text-anchor:%s;",(tsp->ldir == LDIR_RL ? "end" : "start"));
+ TRPRINT(tri, obuf);
+ cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":");
+ sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"\n\">%s</text>\n",&tsp->string[tsp->spaces]);
+ TRPRINT(tri, obuf);
+#endif /* DBG_TR_INPUT debugging code, original text objects */
+ }
+ }
+ }
+#endif /* DBG_TR_PARA and/or DBG_TR_INPUT */
+
+
+ if(tri->usebk){
+ esc = tri->esc;
+ esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */
+ sprintf(stransform,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc), 1.25*x,1.25*y);
+
+ for(i=cxi->phase1; i<cxi->used;i++){ /* over all complex members from phase2 == TR_PARA_* complexes */
+ TRPRINT(tri, "<g>\n"); /* group backgrounds for each <text> object in the SVG */
+ csp = &(cxi->cx[i]);
+ for(j=0; j<csp->kids.used; j++){ /* over all members of these complexes, which are phase1 complexes */
+ jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */
+ cline_sp = &(cxi->cx[jdx]);
+ if(tri->usebk == BKCLR_LINE){
+ TRPRINT(tri, "<rect\n");
+ sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[cline_sp->rt_cidx].xur - bri->rects[cline_sp->rt_cidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[cline_sp->rt_cidx].yll - bri->rects[cline_sp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[cline_sp->rt_cidx].xll),1.25*(bri->rects[cline_sp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+ }
+
+ for(k=0; k<cxi->cx[jdx].kids.used; k++){ /* over all members of the phase1 complex */
+ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi */
+ tsp = &tpi->chunks[kdx];
+ ldir = tsp->ldir;
+ if(!j && !k){
+ if(tri->usebk == BKCLR_ALL){
+ TRPRINT(tri, "<rect\n");
+ sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[csp->rt_cidx].xur - bri->rects[csp->rt_cidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].yll - bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",1.25*(bri->rects[csp->rt_cidx].xll),1.25*(bri->rects[csp->rt_cidx].yur));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+ }
+ }
+ if(tri->usebk == BKCLR_FRAG){
+ newx = 1.25*(ldir == LDIR_RL ? bri->rects[tsp->rt_tidx].xur : bri->rects[tsp->rt_tidx].xll);
+ newy = 1.25*(bri->rects[tsp->rt_tidx].yur);
+ TRPRINT(tri, "<rect\n");
+ sprintf(obuf,"style=\"color-interpolation:sRGB;color-interpolation-filters:linearRGB;fill:#%2.2X%2.2X%2.2X;;stroke:none;;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;clip-rule:nonzero\"\n",tri->bkcolor.Red,tri->bkcolor.Green,tri->bkcolor.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"width=\"%f\"\n", 1.25*(bri->rects[tsp->rt_tidx].xur - bri->rects[tsp->rt_tidx].xll));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"height=\"%f\"\n",1.25*(bri->rects[tsp->rt_tidx].yll - bri->rects[tsp->rt_tidx].yur));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n",newx,newy);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, stransform);
+ TRPRINT(tri, "/>\n");
+ }
+ }
+ }
+ TRPRINT(tri, "</g>\n"); /* end of grouping for backgrounds for each <text> object in the SVG */
+ }
+ }
+
+
+ /* over all complex members from phase2. Paragraphs == TR_PARA_* */
+ for(i=cxi->phase1; i<cxi->used;i++){
+ csp = &(cxi->cx[i]);
+ esc = tri->esc;
+ esc *= 2.0 * M_PI / 360.0; /* degrees to radians and change direction of rotation */
+
+ /* over all members of the present Paragraph. Each of these is a line and a phase 1 complex.
+ It may be either TR_TEXT or TR_LINE */
+ for(j=0; j<csp->kids.used; j++){
+ if(j){
+ sprintf(obuf,"</tspan>");
+ TRPRINT(tri, obuf);
+ }
+ jdx = csp->kids.members[j]; /* index of phase1 complex (all are TR_TEXT or TR_LINE) */
+ recenter = 0; /* mostly to quiet a compiler warning, should always be set below */
+
+
+ /* over all members of the present Line. These are the original text objects which were reassembled.
+ There will be one for TR_TEXT, more than one for TR_LINE */
+ for(k=0; k<cxi->cx[jdx].kids.used; k++){
+ kdx = cxi->cx[jdx].kids.members[k]; /* index for text objects in tpi, for this k */
+ tsp = &tpi->chunks[kdx]; /* text chunk for this k */
+ ldir = tsp->ldir; /* language direction for this k */
+ if(!k){ /* first iteration */
+ switch(csp->type){ /* set up the alignment, if there is one */
+ case TR_TEXT:
+ case TR_LINE:
+ /* these should never occur, this section quiets a compiler warning */
+ break;
+ case TR_PARA_UJ:
+ case TR_PARA_LJ:
+ if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); }
+ else { recenter = 0.0; }
+ break;
+ case TR_PARA_CJ:
+ if(ldir == LDIR_RL){ recenter = -(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; }
+ else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll)/2.0; }
+ break;
+ case TR_PARA_RJ:
+ if(ldir == LDIR_RL){ recenter = 0.0; }
+ else { recenter = +(bri->rects[cxi->cx[jdx].rt_cidx].xur - bri->rects[cxi->cx[jdx].rt_cidx].xll); }
+ break;
+ }
+ if(!j){
+ TRPRINT(tri, "<text\n");
+ TRPRINT(tri, "xml:space=\"preserve\"\n");
+ TRPRINT(tri, "style=\"");
+ sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal"));
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "font-variant:normal;");
+ sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed"));
+ TRPRINT(tri, obuf);
+ if(tsp->vadvance){ lineheight = tsp->vadvance *100.0; }
+ else { lineheight = 125.0; }
+ sprintf(obuf,"line-height:%f%%;",lineheight);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "letter-spacing:0px;");
+ TRPRINT(tri, "word-spacing:0px;");
+ TRPRINT(tri, "fill:#000000;");
+ TRPRINT(tri, "fill-opacity:1;");
+ TRPRINT(tri, "stroke:none;");
+ cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":");
+ sprintf(obuf,"font-family:%.*s;",cutat,fti->fonts[tsp->fi_idx].fontspec);
+ TRPRINT(tri, obuf);
+ switch(csp->type){ /* set up the alignment, if there is one */
+ case TR_TEXT:
+ case TR_LINE:
+ /* these should never occur, this section quiets a compiler warning */
+ break;
+ case TR_PARA_UJ:
+ case TR_PARA_LJ:
+ sprintf(obuf,"text-align:start;text-anchor:start;");
+ break;
+ case TR_PARA_CJ:
+ sprintf(obuf,"text-align:center;text-anchor:middle;");
+ break;
+ case TR_PARA_RJ:
+ sprintf(obuf,"text-align:end;text-anchor:end;");
+ break;
+ }
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "\"\n"); /* End of style specification */
+ sprintf(obuf,"transform=\"matrix(%f,%f,%f,%f,%f,%f)\"\n",cos(esc),-sin(esc),sin(esc),cos(esc),1.25*x,1.25*y);
+ TRPRINT(tri, obuf);
+ tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter);
+ sprintf(obuf,"x=\"%f\" y=\"%f\"\n>",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff));
+ TRPRINT(tri, obuf);
+ }
+ tmpx = 1.25*((ldir == LDIR_RL ? bri->rects[kdx].xur : bri->rects[kdx].xll) + recenter);
+ sprintf(obuf,"<tspan sodipodi:role=\"line\"\nx=\"%f\" y=\"%f\"\n>",tmpx,1.25*(bri->rects[kdx].yll - tsp->boff));
+ TRPRINT(tri, obuf);
+ }
+ TRPRINT(tri, "<tspan\n");
+
+ /* Scale kerning and make any other necessary adjustments
+
+ */
+ dx = 1.25 * tsp->xkern;
+ dy = 1.25 * tsp->ykern;
+
+ sprintf(obuf,"dx=\"%f\" dy=\"%f\" ",dx, dy);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"style=\"fill:#%2.2X%2.2X%2.2X;",tsp->color.Red,tsp->color.Green,tsp->color.Blue);
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-size:%fpx;",tsp->fs*1.25); /*IMPORTANT, if the FS is given in pt it looks like crap in browsers. As if px != 1.25 pt, maybe 96 dpi not 90?*/
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-style:%s;",(tsp->italics ? "italic" : "normal"));
+ TRPRINT(tri, obuf);
+ if(tsp->decoration & TXTDECOR_TMASK){
+ sprintf(obuf,"text-decoration:");
+ /* multiple text decoration styles may be set */
+ utmp = tsp->decoration & TXTDECOR_TMASK;
+ if(utmp & TXTDECOR_UNDER ){ strcat(obuf," underline"); }
+ if(utmp & TXTDECOR_OVER ){ strcat(obuf," overline"); }
+ if(utmp & TXTDECOR_BLINK ){ strcat(obuf," blink"); }
+ if(utmp & TXTDECOR_STRIKE){ strcat(obuf," line-through");}
+ if(*obuf){
+ /* only a single text decoration line type may be set */
+ switch(tsp->decoration & TXTDECOR_LMASK){
+ case TXTDECOR_SOLID: break; // "solid" is the CSS 3 default, omitting it remains CSS 2 compatible
+ case TXTDECOR_DOUBLE: strcat(obuf," double"); break; // these are all CSS3
+ case TXTDECOR_DOTTED: strcat(obuf," dotted"); break;
+ case TXTDECOR_DASHED: strcat(obuf," dashed"); break;
+ case TXTDECOR_WAVY: strcat(obuf," wavy" ); break;
+ default: break;
+ }
+ if((tsp->decoration & TXTDECOR_CLRSET) && memcmp(&(tsp->decColor),&(tsp->color),sizeof(TRCOLORREF))){
+ /* CSS 3, CSS 2 implementations may choke on it. If the specified color matches text color omit, for better CSS 2 compatitiblity. */
+ sprintf(cbuf," #%2.2X%2.2X%2.2X",tsp->decColor.Red,tsp->decColor.Green,tsp->decColor.Blue);
+ strcat(obuf,cbuf);
+ }
+ }
+ strcat(obuf,";");
+ TRPRINT(tri,obuf);
+ }
+ TRPRINT(tri, "font-variant:normal;");
+ sprintf(obuf,"font-weight:%d;",TR_weight_FC_to_SVG(tsp->weight));
+ TRPRINT(tri, obuf);
+ sprintf(obuf,"font-stretch:%s;",(tsp->condensed==100 ? "Normal" : "Condensed"));
+ TRPRINT(tri, obuf);
+ cutat=strcspn((char *)fti->fonts[tsp->fi_idx].fontspec,":");
+ sprintf(obuf,"font-family:%.*s;\"",cutat,fti->fonts[tsp->fi_idx].fontspec);
+ TRPRINT(tri, obuf);
+ TRPRINT(tri, "\n>");
+ TRPRINT(tri, (char *) tsp->string);
+ TRPRINT(tri, "</tspan>");
+ } /* end of k loop */
+ } /* end of j loop */
+ TRPRINT(tri,"</tspan></text>\n");
+ } /* end of i loop */
+
+ /* restore locale and free memory. */
+ (void) setlocale(LC_NUMERIC,hold_locale);
+ free(hold_locale);
+}
+
+/**
+ \brief Attempt to figure out the original organization, in lines and paragraphs, of the text objects.
+ The method is:
+ 1. Generate complexes from the text objects (strings) by overlaps (optionally allowing up to two spaces to be
+ added) to produce larger rectangles. Complexes that are more or less sequential and have 2 or more text objects
+ are TR_LINEs, therwise they are TR_TEXT.
+ 2. Group sequential complexes (TR_LINE or TR_TEXT) into TR_PARA_UJ (paragraphs,by smooth progression in vertical
+ position down page).
+ 3. Analyze the paragraphs to classify them as Left/Center/Right justified (possibly with indentation.) If
+ they do not fall into any of these categories break that one back down into TR_LINE/TR_TEXT.
+ 4. Return the number of complex text objects.
+ \returns Number of complexes. (>=1, <= number of text objects.) <0 is an error.
+ \param tri pointer to the TR_INFO structure holding the data, which will also hold the results.
+*/
+int TR_layout_analyze(TR_INFO *tri){
+ unsigned int i,j,k;
+ int ok;
+ int cxidx;
+ int src_rt;
+ int dst_rt;
+ TP_INFO *tpi;
+ BR_INFO *bri;
+ CX_INFO *cxi;
+ FT_INFO *fti;
+ BRECT_SPECS bsp;
+ RT_PAD rt_pad_i;
+ RT_PAD rt_pad_j;
+ double ratio;
+ double qsp,dx,dy;
+ double spcadv;
+ enum tr_classes type;
+ TCHUNK_SPECS *tspi;
+ TCHUNK_SPECS *tspj;
+ TCHUNK_SPECS *tspRevEnd=NULL;
+ TCHUNK_SPECS *tspRevStart=NULL;
+ CX_SPECS *csp;
+ CHILD_SPECS *kidp; /* used with preceding complex (see below) */
+ CHILD_SPECS *kidc; /* used with current complex (see below) */
+ int lastldir,ldir,rev;
+
+ if(!tri)return(-1);
+ if(!tri->cxi)return(-2);
+ if(!tri->tpi)return(-3);
+ if(!tri->bri)return(-4);
+ if(!tri->fti)return(-5);
+ tpi=tri->tpi;
+ cxi=tri->cxi;
+ bri=tri->bri;
+ fti=tri->fti;
+ cxi->lines = 0;
+ cxi->paras = 0;
+ cxi->phase1 = 0;
+
+/* When debugging
+ ftinfo_dump(fti);
+*/
+ /* Phase 1. Working sequentially, insert text. Initially as TR_TEXT and then try to extend to TR_LINE by checking
+ overlaps. When done the complexes will contain a mix of TR_LINE and TR_TEXT. */
+
+ for(i=0; i<tpi->used; i++){
+ tspi = &(tpi->chunks[i]);
+ memcpy(&bsp,&(bri->rects[tspi->rt_tidx]),sizeof(BRECT_SPECS)); /* Must make a copy as next call may reallocate rects! */
+ (void) brinfo_insert(bri,&bsp);
+ dst_rt = bri->used-1;
+ (void) cxinfo_insert(cxi, i, dst_rt, TR_TEXT);
+ cxidx = cxi->used-1;
+
+ spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */
+ /* for the leading text: pad with no leading and two trailing spaces, leading and trailing depend on direction */
+ if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); }
+ else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); }
+
+ for(j=i+1; j<tpi->used; j++){
+ tspj = &(tpi->chunks[j]);
+ /* Reject font size changes of greater than 50%, these are almost certainly not continuous text. These happen
+ in math formulas, for instance, where a sum or integral is much larger than the other symbols. */
+ ratio = (double)(tspj->fs)/(double)(tspi->fs);
+ if(ratio >2.0 || ratio <0.5)break;
+
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */
+ /* for the trailing text: pad with one leading and trailing spaces (so it should work L->R and R->L) */
+ TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv);
+ src_rt = tspj->rt_tidx;
+
+ /* Reject direction changes like [1 <- Hebrew][2 -> English], that is where the direction changes AND the
+ next logical piece of text is "upstream" positionally of its logical predecessor. The meaning of such
+ a construct is at best ambiguous. The test is only applied with respect to the first text chunk. This sort
+ of construct may appear when a valid initial construct like [1->English][2<-Hebrew][3->English] is edited
+ and the leading chunk of text removed.
+
+ Also reject reversed order text as in (English) <A><B><C> (draw order) arranged as <C><B><A>. This happens
+ if the language direction field is incorrect, perhaps due to a corrupt or malformed input file.
+ */
+ if(brinfo_upstream(bri,
+ dst_rt, /* index into bri for dst */
+ src_rt, /* index into bri for src */
+ tspi->ldir,tspj->ldir))break;
+
+ if(!brinfo_overlap(bri,
+ dst_rt, /* index into bri for dst */
+ src_rt, /* index into bri for src */
+ &rt_pad_i,&rt_pad_j)){
+ (void) cxinfo_append(cxi,j,TR_LINE);
+ (void) brinfo_merge(bri,dst_rt,src_rt);
+ /* for the leading text: pad with two leading and trailing spaces (so it should work L->R and R->L */
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0; /* spcadv was always FT_LOAD_NO_SCALE */
+ TR_rt_pad_set(&rt_pad_i, tri->qe, tri->qe,
+ tri->qe + 2.0 * spcadv, tri->qe + 2.0 * spcadv);
+ }
+ else { /* either alignment ge*/
+ break;
+ }
+ }
+
+ /* Bidirectional text will cause complexes to not assemble in one pass.
+ This happens whenever a change of direction occurs with 2 or more sequential elements in
+ the opposite direction,
+
+ Let + = LR and - = RL.
+
+ Reading left to right, this happens with +-- or -++.
+ For instance, the sequence ++-+ ---+ would break into the two complexes shown.
+ Not until the last element in the second complex is added will the bounding rectangles for the complexes overlap.
+
+ Check for this effect now if there is a preceding complex and the first element of the current complex is
+ reversed from the last in the preceding. */
+ if(cxidx >= 1){
+ kidp = &(cxi->cx[cxidx-1].kids);
+ kidc = &(cxi->cx[cxidx ].kids);
+ tspi = &(tpi->chunks[ kidp->members[kidp->used - 1] ]); /* here, the last text element in preceding complex */
+ tspj = &(tpi->chunks[ kidc->members[0 ] ]); /* here, tge first text element in current complex */
+ if(tspi->ldir != tspj->ldir){
+ spcadv = fti->fonts[tspi->fi_idx].spcadv * tspi->fs/32.0;
+ if(tspi->ldir == LDIR_RL){ TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, tri->qe + 2.0 * spcadv, 0.0); }
+ else { TR_rt_pad_set(&rt_pad_i,tri->qe, tri->qe, 0.0, tri->qe + 2.0 * spcadv); }
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0;
+ TR_rt_pad_set(&rt_pad_j,tri->qe, tri->qe, spcadv, spcadv);
+ if(!brinfo_overlap(bri,
+ cxi->cx[cxidx-1].rt_cidx, /* index into rt for dst cx */
+ cxi->cx[cxidx].rt_cidx, /* index into rt for src cx */
+ &rt_pad_i,&rt_pad_j)){
+ /* Merge the current complex into the preceding one*/
+ (void) cxinfo_merge(cxi, cxidx-1, cxidx, TR_LINE);
+ (void) brinfo_merge(bri,cxi->cx[cxidx-1].rt_cidx,cxi->cx[cxidx].rt_cidx); /* merge the bounding boxes*/
+ (void) cxinfo_trim(cxi);
+ cxi->lines--; /* else the normal line count value is one too high */
+ /* remove the current complex */
+ }
+ }
+ }
+
+ if(cxi->cx[cxidx].type == TR_LINE)cxi->lines++;
+ i=j-1; /* start up after the last merged entry (there may not be any) */
+ }
+ cxi->phase1 = cxi->used; /* total complexes defined in this phase, all TR_LINE or TR_TEXT */
+
+ /* phase 1.5, calculate kerning. This is as good a place to do it as any. At this point all kern values
+ are zero. Each of these pieces is strictly unidirectional, but each piece can have a different direction.
+ The direction of the line is set by the first text element. The ends of runs of elements which are
+ reversed with respect to the line direction are special, everything else is simple:
+ Let: + == L->R, - == R->L, $ == end of text, the rules for kerning on B are:
+ A B others xkern
+ [+|$] + + [+|$] Bll - Aur
+ [-|$] - - [-|$] All - Bur (chs)
+ + - + [-|$] Bll - Aur (chs)
+ - + - [+|$] All - Bur
+ + - -...[-=C] [+|$] All - Cur (chs)
+ - + +...[+=C] [-|$] Cll - Aur
+
+ chs = change sign, because dx is an absolute direction, and direction of text on RTL is in -x.
+
+ Kerning calculations currently seems unstable for R->L if the kerning extends to the end of the line. If
+ the first and last characters are back in sync there are no issues. When things go south R->L left justified
+ text is not justified when read in.
+ */
+
+ for(i=0; i < cxi->phase1; i++){ /* over all lines */
+ csp = &(cxi->cx[i]);
+ if(csp->kids.used < 2)continue; /* no kerning possible */
+ tspi = &tpi->chunks[csp->kids.members[0]]; /* used here as last tsp. no kerning is applied to the first element */
+ lastldir = ldir = tspi->ldir;
+ rev = 0; /* the first ldir defines forward and reverse */
+ for(j=1; j<csp->kids.used; j++){
+ tspj = &tpi->chunks[csp->kids.members[j]];
+ ldir = tspj->ldir;
+ if(ldir != lastldir){ /* direction change */
+ rev = !rev; /* reverse direction tracker */
+ if(!rev){ /* back in original orientation */
+ if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspRevStart->rt_tidx].xll; }
+ else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspRevStart->rt_tidx].xur; }
+ tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) -
+ (bri->rects[tspRevStart->rt_tidx].yll - tspRevStart->boff);
+ }
+ else { /* now in reversed orientation */
+ tspRevStart = tspj; /* Save the beginning of this run (length >=1 ) */
+ /* scan forward for the last text object in this orientation, include the first */
+ for(k=j; k <csp->kids.used; k++){
+ if(tpi->chunks[csp->kids.members[k]].ldir == ldir){ tspRevEnd = &tpi->chunks[csp->kids.members[k]]; }
+ else { break; }
+ }
+ if(lastldir == LDIR_RL){ tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; }
+ else { tspj->xkern = bri->rects[tspRevEnd->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; }
+ tspj->ykern = (bri->rects[tspRevEnd->rt_tidx].yll - tspRevEnd->boff) -
+ (bri->rects[ tspi->rt_tidx].yll - tspi->boff );
+ }
+ }
+ else {
+ if(ldir == LDIR_RL){ tspj->xkern = bri->rects[tspj->rt_tidx].xur - bri->rects[tspi->rt_tidx].xll; }
+ else { tspj->xkern = bri->rects[tspj->rt_tidx].xll - bri->rects[tspi->rt_tidx].xur; }
+ tspj->ykern = (bri->rects[tspj->rt_tidx].yll - tspj->boff) -
+ (bri->rects[tspi->rt_tidx].yll - tspi->boff);
+ }
+
+
+ /*
+ Sometimes a font substitution was absolutely terrible, for instance, for Arial Narrow on (most) Linux systems,
+ The resulting advance (xkern) may be much too large so that it overruns the next text chunk. Since
+ overlapping text on the same line is almost never encountered, this may be used to detect the bad
+ substitution so that a more appropriate offset can be used.
+ Detect this situation as a negative dx < 1/2 a space character's width while |dy| < an entire space width.
+ The y constraints allow super and subscripts, which overlap in x but are shifted above/below in y.
+ */
+ spcadv = fti->fonts[tspj->fi_idx].spcadv * tspj->fs/32.0;
+ qsp = 0.25 * spcadv;
+ dx = tspj->xkern;
+ dy = tspj->ykern;
+ if(dy <=qsp && dy >= -qsp){
+ if(ldir==LDIR_RL){
+ if(dx > 2*qsp)tspj->xkern = 0.0;
+ }
+ else {
+ if(dx < -2*qsp)tspj->xkern = 0.0;
+ }
+ }
+
+ /* if x or y kern is less than twice the quantization error it is probably noise, set it to zero */
+ if(fabs(tspj->xkern) <= 2.0*tri->qe)tspj->xkern = 0.0;
+ if(fabs(tspj->ykern) <= 2.0*tri->qe)tspj->ykern = 0.0;
+
+ /* reintroduce spaces on the leading edge of text "j" if the kerning can be in part or in whole replaced
+ with 1 or 2 spaces */
+ if(tspj->ykern == 0.0){
+ double spaces = tspj->xkern/spcadv; /* negative on RL language, positive on LR */
+ if((ldir == LDIR_RL && (spaces <= -0.9 && spaces >= -2.1)) ||
+ (ldir == LDIR_LR && (spaces >= 0.9 && spaces <= 2.1)) ){
+ int ispaces = lround(spaces);
+ tspj->xkern -= ((double)ispaces*spcadv);
+ if(ispaces<0)ispaces=-ispaces;
+ size_t slen = strlen((char *)tspj->string);
+ uint8_t *newstring = malloc(1 + ispaces + slen);
+ sprintf((char *)newstring," "); /* start with two spaces, possibly overwrite one in the next line */
+ memcpy(newstring+ispaces,tspj->string,slen+1); /* copy existing string to proper position */
+ free(tspj->string);
+ tspj->string = newstring;
+ tspj->spaces = ispaces; // only needed to fix optional debugging SVG output later
+ }
+ }
+
+ tspi = tspj;
+ lastldir = ldir;
+ }
+ }
+
+
+ /* Phase 2, try to group sequential lines. There may be "lines" that are still TR_TEXT, as in:
+
+ ... this is a sentence that wraps by one
+ word.
+
+ And some paragrahs might be single word lines (+ = bullet in the following)
+
+ +verbs
+ +nouns
+ +adjectives
+
+ Everything starts out as TR_PARA_UJ and if the next one can be lined up, the type changes to
+ an aligned paragraph and complexes are appended to the existing one.
+ */
+
+ for(i=0; i < cxi->phase1; i++){
+ type = TR_PARA_UJ; /* any paragraph alignment will be acceptable */
+ /* Must make a copy as next call may reallocate rects, so if we just passed a pointer to something in the structure
+ it would vaporize part way through the call. */
+ memcpy(&bsp,&(bri->rects[cxi->cx[i].rt_cidx]),sizeof(BRECT_SPECS));
+ (void) brinfo_insert(bri,&bsp);
+ dst_rt = bri->used-1;
+ (void) cxinfo_insert(cxi, i, dst_rt, type);
+
+ cxi->paras++;
+ ok = 1;
+ for(j=i+1; ok && (j < cxi->phase1); j++){
+ type = brinfo_pp_alignment(bri, cxi->cx[i].rt_cidx, cxi->cx[j].rt_cidx, 3*tri->qe, type);
+ switch (type){
+ case TR_PARA_UJ: /* paragraph type was set and j line does not fit, or no paragraph alignment matched */
+ ok = 0; /* force exit from j loop */
+ j--; /* this will increment at loop bottom */
+ break;
+ case TR_PARA_LJ:
+ case TR_PARA_CJ:
+ case TR_PARA_RJ:
+ /* two successive lines have been identified (possible following others already in the paragraph */
+ if(TR_check_set_vadvance(tri,j,i)){ /* check for compatibility with vadvance if set, set it if it isn't. */
+ ok = 0; /* force exit from j loop */
+ j--; /* this will increment at loop bottom */
+ }
+ else {
+ src_rt = cxi->cx[j].rt_cidx;
+ (void) cxinfo_append(cxi, j, type);
+ (void) brinfo_merge(bri, dst_rt, src_rt);
+ }
+ break;
+ default:
+ return(-6); /* programming error */
+ }
+ }
+ if(j>=cxi->phase1)break;
+ i=j-1;
+ }
+
+
+/* When debugging
+ cxinfo_dump(tri);
+*/
+
+ return(cxi->used);
+}
+
+
+/* no doxygen documentation below this point, these pieces are for the text program, not the library. */
+
+#if TEST
+#define MAXLINE 2048 /* big enough for testing */
+enum OP_TYPES {OPCOM,OPOOPS,OPFONT,OPESC,OPORI,OPXY,OPFS,OPTEXT,OPALN,OPLDIR,OPMUL,OPITA,OPWGT,OPDEC,OPCND,OPBKG,OPCLR,OPDCLR,OPBCLR,OPFLAGS,OPEMIT,OPDONE};
+
+int parseit(char *buffer,char **data){
+ int pre;
+ pre = strcspn(buffer,":");
+ if(!pre)return(OPOOPS);
+ *data=&buffer[pre+1];
+ buffer[pre]='\0';
+ if(*buffer=='#' )return(OPCOM );
+ if(0==strcmp("FONT",buffer))return(OPFONT);
+ if(0==strcmp("ESC" ,buffer))return(OPESC );
+ if(0==strcmp("ORI", buffer))return(OPORI );
+ if(0==strcmp("XY", buffer))return(OPXY );
+ if(0==strcmp("FS", buffer))return(OPFS );
+ if(0==strcmp("TEXT",buffer))return(OPTEXT);
+ if(0==strcmp("ALN", buffer))return(OPALN );
+ if(0==strcmp("LDIR",buffer))return(OPLDIR);
+ if(0==strcmp("MUL", buffer))return(OPMUL );
+ if(0==strcmp("ITA", buffer))return(OPITA );
+ if(0==strcmp("WGT", buffer))return(OPWGT );
+ if(0==strcmp("DEC", buffer))return(OPDEC );
+ if(0==strcmp("CND", buffer))return(OPCND );
+ if(0==strcmp("BKG", buffer))return(OPBKG );
+ if(0==strcmp("CLR", buffer))return(OPCLR );
+ if(0==strcmp("DCLR", buffer))return(OPDCLR );
+ if(0==strcmp("BCLR",buffer))return(OPBCLR );
+ if(0==strcmp("FLAG",buffer))return(OPFLAGS);
+ if(0==strcmp("EMIT",buffer))return(OPEMIT);
+ if(0==strcmp("DONE",buffer))return(OPDONE);
+ return(OPOOPS);
+}
+
+void boom(char *string,int lineno){
+ fprintf(stderr,"Fatal error at line %d %s\n",lineno,string);
+ exit(EXIT_FAILURE);
+}
+
+
+void init_as_svg(TR_INFO *tri){
+ TRPRINT(tri,"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
+ TRPRINT(tri,"<!-- Created with Inkscape (http://www.inkscape.org/) -->\n");
+ TRPRINT(tri,"\n");
+ TRPRINT(tri,"<svg\n");
+ TRPRINT(tri," xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n");
+ TRPRINT(tri," xmlns:cc=\"http://creativecommons.org/ns#\"\n");
+ TRPRINT(tri," xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n");
+ TRPRINT(tri," xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
+ TRPRINT(tri," xmlns=\"http://www.w3.org/2000/svg\"\n");
+ TRPRINT(tri," xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n");
+ TRPRINT(tri," xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\"\n");
+ TRPRINT(tri," width=\"900\"\n");
+ TRPRINT(tri," height=\"675\"\n");
+ TRPRINT(tri," id=\"svg4122\"\n");
+ TRPRINT(tri," version=\"1.1\"\n");
+ TRPRINT(tri," inkscape:version=\"0.48+devel r11679 custom\"\n");
+ TRPRINT(tri," sodipodi:docname=\"simplest_text.svg\">\n");
+ TRPRINT(tri," <defs\n");
+ TRPRINT(tri," id=\"defs4124\" />\n");
+ TRPRINT(tri," <sodipodi:namedview\n");
+ TRPRINT(tri," id=\"base\"\n");
+ TRPRINT(tri," pagecolor=\"#ffffff\"\n");
+ TRPRINT(tri," bordercolor=\"#666666\"\n");
+ TRPRINT(tri," borderopacity=\"1.0\"\n");
+ TRPRINT(tri," inkscape:pageopacity=\"0.0\"\n");
+ TRPRINT(tri," inkscape:pageshadow=\"2\"\n");
+ TRPRINT(tri," inkscape:zoom=\"0.98994949\"\n");
+ TRPRINT(tri," inkscape:cx=\"309.88761\"\n");
+ TRPRINT(tri," inkscape:cy=\"482.63995\"\n");
+ TRPRINT(tri," inkscape:document-units=\"px\"\n");
+ TRPRINT(tri," inkscape:current-layer=\"layer1\"\n");
+ TRPRINT(tri," showgrid=\"false\"\n");
+ TRPRINT(tri," width=\"0px\"\n");
+ TRPRINT(tri," height=\"0px\"\n");
+ TRPRINT(tri," fit-margin-top=\"0\"\n");
+ TRPRINT(tri," fit-margin-left=\"0\"\n");
+ TRPRINT(tri," fit-margin-right=\"0\"\n");
+ TRPRINT(tri," fit-margin-bottom=\"0\"\n");
+ TRPRINT(tri," units=\"in\"\n");
+ TRPRINT(tri," inkscape:window-width=\"1200\"\n");
+ TRPRINT(tri," inkscape:window-height=\"675\"\n");
+ TRPRINT(tri," inkscape:window-x=\"26\"\n");
+ TRPRINT(tri," inkscape:window-y=\"51\"\n");
+ TRPRINT(tri," inkscape:window-maximized=\"0\" />\n");
+ TRPRINT(tri," <metadata\n");
+ TRPRINT(tri," id=\"metadata4127\">\n");
+ TRPRINT(tri," <rdf:RDF>\n");
+ TRPRINT(tri," <cc:Work\n");
+ TRPRINT(tri," rdf:about=\"\">\n");
+ TRPRINT(tri," <dc:format>image/svg+xml</dc:format>\n");
+ TRPRINT(tri," <dc:type\n");
+ TRPRINT(tri," rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" />\n");
+ TRPRINT(tri," <dc:title></dc:title>\n");
+ TRPRINT(tri," </cc:Work>\n");
+ TRPRINT(tri," </rdf:RDF>\n");
+ TRPRINT(tri," </metadata>\n");
+ TRPRINT(tri," <g\n");
+ TRPRINT(tri," inkscape:label=\"Layer 1\"\n");
+ TRPRINT(tri," inkscape:groupmode=\"layer\"\n");
+ TRPRINT(tri," id=\"layer1\"\n");
+ TRPRINT(tri," transform=\"translate(0,0)\">\n");
+ TRPRINT(tri,"\n");
+}
+
+
+void flush_as_svg(TR_INFO *tri, FILE *fp){
+ fwrite(tri->out,tri->outused,1,fp);
+}
+
+FILE *close_as_svg(TR_INFO *tri, FILE *fp){
+ TRPRINT(tri, " </g>\n");
+ TRPRINT(tri, "</svg>\n");
+ flush_as_svg(tri,fp);
+ fclose(fp);
+ return(NULL);
+}
+
+
+int main(int argc, char *argv[]){
+ char *data;
+ char inbuf[MAXLINE];
+ FILE *fpi = NULL;
+ FILE *fpo = NULL;
+ int op;
+ double fact = 1.0; /* input units to points */
+ double escapement = 0.0; /* degrees */
+ int lineno = 0;
+ int ok = 1;
+ int status;
+ TCHUNK_SPECS tsp;
+ TR_INFO *tri=NULL;
+ int flags=0;
+ char *infile;
+ uint32_t utmp32;
+ TRCOLORREF bkcolor;
+ int bkmode;
+ char *fontspec;
+
+ infile=malloc(strlen(argv[1])+1);
+ strcpy(infile,argv[1]);
+
+ if(argc < 2 || !(fpi = fopen(infile,"r"))){
+ printf("Usage: text_reassemble input_file\n");
+ printf(" Test program reads an input file containing lines like:\n");
+ printf(" FONT:(font for next text)\n");
+ printf(" ESC:(escapement angle degrees of text line, up from X axis)\n");
+ printf(" ORI:(angle degrees of character orientation, up from X axis)\n");
+ printf(" FS:(font size, units)\n");
+ printf(" XY:(x,y) X 0 is at left, N is at right, Y 0 is at top, N is at bottom, as page is viewed.\n");
+ printf(" TEXT:(UTF8 text)\n");
+ printf(" ALN:combination of {LCR}{BLT} = Text is placed on {X,Y} at Left/Center/Right of text, at Bottom,baseLine,Top of text.\n");
+ printf(" LDIR:{LR|RL|TB) Left to Right, Right to Left, and Top to Bottom \n");
+ printf(" MUL:(float, multiplicative factor to convert FS,XY units to points).\n");
+ printf(" ITA:(Italics, 0=normal, 100=italics, 110=oblique).\n");
+ printf(" WGT:(Weight, 0-215: 80=normal, 200=bold, 215=ultrablack, 0=thin)).\n");
+ printf(" DEC:(this is a bit field. For color see DCLR\n");
+ printf(" style: 000 none, 001 underline,002 overline, 004 blink, 008 strike-through\n");
+ printf(" line: 000 solid, 010 double, 020 dotted, 040 dashed, 080 wavy)\n");
+ printf(" CND:(Condensed 50-200: 100=normal, 50=ultracondensed, 75=condensed, 200=expanded).\n");
+ printf(" BKG:(Background color: 0 none, 1 by input fragment, 2 by assembled line, 3 by entire assembly. Use BCLR, THEN BKG) \n");
+ printf(" CLR:(Text RGB color, as 6 HEX digits, like: FF0000 (red) or 0000FF (blue)) \n");
+ printf(" DCLR:(Decoration color, specify like CLR, except 1000000 or higher disables.)\n");
+ printf(" BCLR:(Background RGB color, specify like CLR.) \n");
+ printf(" FLAG: Special processing options. 1 EMF compatible text alignment.\n");
+ printf(" EMIT:(Process everything up to this point, then start clean for remaining input).\n");
+ printf(" DONE:(no more input, process it).\n");
+ printf(" # comment\n");
+ printf("\n");
+ printf(" The output is a summary of how the pieces are to be assembled into complex text.\n");
+ printf("\n");
+ printf(" egrep pattern: '^LOAD:|^FONT:|^ESC:|^ORI:|^FS:|^XY:|^TEXT:|^ALN:|^LDIR:|^MUL:|^ITA:|^WGT:|^DEC:|^CND:|^BKG:|^CLR:|^BCLR:|^DCLR:|^FLAG:|^EMIT:^DONE:'\n");
+ exit(EXIT_FAILURE);
+ }
+
+ tri = trinfo_init(tri); /* If it loops the trinfo_clear at the end will reset tri to the proper state, do NOT call trinfo_init twice! */
+
+#ifdef DBG_LOOP
+ int ldx;
+ for(ldx=0;ldx<5;ldx++){
+ if(fpi)fclose(fpi);
+ fpi = fopen(infile,"r");
+#endif
+ tsp.string = NULL;
+ tsp.ori = 0.0; /* degrees */
+ tsp.fs = 12.0; /* font size */
+ tsp.x = 0.0;
+ tsp.y = 0.0;
+ tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/
+ tsp.vadvance = 0.0; /* meaningful only when a complex contains two or more lines */
+ tsp.taln = ALILEFT + ALIBASE;
+ tsp.ldir = LDIR_LR;
+ tsp.color.Red = tsp.decColor.Red = 0; /* RGBA Black */
+ tsp.color.Green = tsp.decColor.Green = 0; /* RGBA Black */
+ tsp.color.Blue = tsp.decColor.Blue = 0; /* RGBA Black */
+ tsp.color.Reserved = tsp.decColor.Reserved = 0; /* unused */
+ tsp.italics = 0;
+ tsp.weight = 80;
+ tsp.condensed = 100;
+ tsp.decoration = 0; /* none */
+ tsp.spaces = 0; /* none */
+ tsp.fi_idx = -1; /* set to an invalid */
+ tsp.rt_tidx = -1; /* set to an invalid */
+ tsp.xkern = tsp.ykern = 0.0;
+ /* no need to set rt_tidx */
+
+
+
+ if(!tri){
+ fprintf(stderr,"Fatal error, could not initialize data structures\n");
+ exit(EXIT_FAILURE);
+ }
+ (void) trinfo_load_ft_opts(tri, 1,
+ FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP,
+ FT_KERNING_UNSCALED);
+
+ fpo=fopen("dump.svg","wb");
+ init_as_svg(tri);
+
+ while(ok){
+ lineno++;
+ if(!fgets(inbuf,MAXLINE,fpi))boom("Unexpected end of file - no DONE:",lineno);
+ inbuf[strlen(inbuf)-1]='\0'; /* step on the EOL character */
+ op = parseit(inbuf,&data);
+ switch(op){
+ case OPCOM: /* ignore comments*/
+ break;
+ case OPFONT:
+ /* If the font name includes "Narrow" condensed may not have been set */
+ if(0<= TR_findcasesub(data, "Narrow")){
+ tsp.co=1;
+ }
+ else {
+ tsp.co=0;
+ }
+ fontspec = TR_construct_fontspec(&tsp, data);
+ if((tsp.fi_idx = ftinfo_load_fontname(tri->fti, fontspec)) < 0 )boom("Font load failed",lineno);
+ free(fontspec);
+ break;
+ case OPESC:
+ if(1 != sscanf(data,"%lf",&escapement))boom("Invalid ESC:",lineno);
+ break;
+ case OPORI:
+ if(1 != sscanf(data,"%lf",&tsp.ori))boom("Invalid ORI:",lineno);
+ break;
+ case OPFS:
+ if(1 != sscanf(data,"%lf",&tsp.fs) || tsp.fs <= 0.0)boom("Invalid FS:",lineno);
+ tsp.fs *= fact;
+ break;
+ case OPXY:
+ if(2 != sscanf(data,"%lf,%lf",&tsp.x,&tsp.y) )boom("Invalid XY:",lineno);
+ tsp.x *= fact;
+ tsp.y *= fact;
+ break;
+ case OPTEXT:
+ tsp.string = (uint8_t *) U_strdup(data);
+ /* FreeType parameters match inkscape*/
+ status = trinfo_load_textrec(tri, &tsp, escapement,flags);
+ if(status==-1){ // change of escapement, emit what we have and reset
+ TR_layout_analyze(tri);
+ TR_layout_2_svg(tri);
+ flush_as_svg(tri, fpo);
+ tri = trinfo_clear(tri);
+ if(trinfo_load_textrec(tri, &tsp, escapement,flags)){ boom("Text load failed",lineno); }
+ }
+ else if(status){ boom("Text load failed",lineno); }
+ break;
+ case OPALN:
+ tsp.taln=0;
+ switch (*data++){
+ case 'L': tsp.taln |= ALILEFT; break;
+ case 'C': tsp.taln |= ALICENTER; break;
+ case 'R': tsp.taln |= ALIRIGHT; break;
+ default: boom("Invalid ALN:",lineno);
+ }
+ switch (*data++){
+ case 'T': tsp.taln |= ALITOP; break;
+ case 'L': tsp.taln |= ALIBASE; break;
+ case 'B': tsp.taln |= ALIBOT; break;
+ default: boom("Invalid ALN:",lineno);
+ }
+ break;
+ case OPLDIR:
+ tsp.ldir=0;
+ if(0==strcmp("LR",data)){ tsp.ldir=LDIR_LR; break;}
+ if(0==strcmp("RL",data)){ tsp.ldir=LDIR_RL; break;}
+ if(0==strcmp("TB",data)){ tsp.ldir=LDIR_TB; break;}
+ boom("Invalid LDIR:",lineno);
+ break;
+ case OPMUL:
+ if(1 != sscanf(data,"%lf",&fact) || fact <= 0.0)boom("Invalid MUL:",lineno);
+ (void) trinfo_load_qe(tri,fact);
+ break;
+ case OPITA:
+ if(1 != sscanf(data,"%d",&tsp.italics) || tsp.italics < 0 || tsp.italics>110)boom("Invalid ITA:",lineno);
+ break;
+ case OPWGT:
+ if(1 != sscanf(data,"%d",&tsp.weight) || tsp.weight < 0 || tsp.weight > 215)boom("Invalid WGT:",lineno);
+ break;
+ case OPDEC:
+ if(1 != sscanf(data,"%X",(unsigned int *) &tsp.decoration))boom("Invalid DEC:",lineno);
+ break;
+ case OPCND:
+ if(1 != sscanf(data,"%d",&tsp.condensed) || tsp.condensed < 50 || tsp.condensed > 200)boom("Invalid CND:",lineno);
+ break;
+ case OPBKG:
+ if(1 != sscanf(data,"%d",&bkmode) )boom("Invalid BKG:",lineno);
+ (void) trinfo_load_bk(tri,bkmode,bkcolor);
+ break;
+ case OPCLR:
+ if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid CLR:",lineno);
+ tsp.color.Red = (utmp32 >> 16) & 0xFF;
+ tsp.color.Green = (utmp32 >> 8) & 0xFF;
+ tsp.color.Blue = (utmp32 >> 0) & 0xFF;
+ tsp.color.Reserved = 0;
+ break;
+ case OPDCLR:
+ if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid DCLR:",lineno);
+ if(utmp32 >= 0x1000000){
+ tsp.decColor.Red = tsp.decColor.Green = tsp.decColor.Blue = tsp.decColor.Reserved = 0;
+ tsp.decoration &= ~TXTDECOR_CLRSET;
+ }
+ else {
+ tsp.decColor.Red = (utmp32 >> 16) & 0xFF;
+ tsp.decColor.Green = (utmp32 >> 8) & 0xFF;
+ tsp.decColor.Blue = (utmp32 >> 0) & 0xFF;
+ tsp.decColor.Reserved = 0;
+ tsp.decoration |= TXTDECOR_CLRSET;
+ }
+ break;
+ case OPBCLR:
+ if(1 != sscanf(data,"%x",&utmp32) )boom("Invalid BCLR:",lineno);
+ bkcolor.Red = (utmp32 >> 16) & 0xFF;
+ bkcolor.Green = (utmp32 >> 8) & 0xFF;
+ bkcolor.Blue = (utmp32 >> 0) & 0xFF;
+ bkcolor.Reserved = 0;
+ break;
+ case OPFLAGS:
+ if(1 != sscanf(data,"%d",&flags) )boom("Invalid FLAG:",lineno);
+ break;
+ case OPEMIT:
+ TR_layout_analyze(tri);
+ TR_layout_2_svg(tri);
+ flush_as_svg(tri, fpo);
+ tri = trinfo_clear(tri);
+ break;
+ case OPDONE:
+ TR_layout_analyze(tri);
+ TR_layout_2_svg(tri);
+ flush_as_svg(tri, fpo);
+ tri = trinfo_clear(tri);
+ ok = 0;
+ break;
+ case OPOOPS:
+ default:
+ boom("Input line cannot be parsed",lineno);
+ break;
+ }
+
+ }
+
+ if(fpo){
+ fpo=close_as_svg(tri, fpo);
+ }
+
+
+#ifdef DBG_LOOP
+ tri = trinfo_clear(tri);
+ ok = 1;
+ }
+#endif /* DBG_LOOP */
+
+ fclose(fpi);
+ tri = trinfo_release(tri);
+ free(infile);
+
+ exit(EXIT_SUCCESS);
+}
+#endif /* TEST */
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/extension/internal/text_reassemble.h b/src/extension/internal/text_reassemble.h
new file mode 100644
index 0000000..25b556a
--- /dev/null
+++ b/src/extension/internal/text_reassemble.h
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * text_reassemble.h from libTERE
+ *//*
+ * Authors: see below
+ *
+ *
+ * Copyright (C) 2017 Authors
+ * Released under GNU GPL v2.0+, read the file 'COPYING' for more information.
+ */
+/**
+ @file text_reassemble.h libTERE headers.
+
+See text_reassemble.c for notes
+
+File: text_reassemble.h
+Version: 0.0.13
+Date: 06-FEB-2014
+Author: David Mathog, Biology Division, Caltech
+email: mathog@caltech.edu
+Copyright: 2014 David Mathog and California Institute of Technology (Caltech)
+*/
+
+#ifndef _TEXT_REASSEMBLE_
+#define _TEXT_REASSEMBLE_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+#include <stdlib.h> //NOLINT
+#include <stdio.h> //NOLINT
+#include <math.h> //NOLINT
+#include <stdint.h> //NOLINT
+#include <ctype.h> //NOLINT
+#include <fontconfig/fontconfig.h>
+#include <ft2build.h>
+#include <iconv.h>
+#include FT_FREETYPE_H
+#include FT_GLYPH_H
+
+/** \cond */
+#define TEREMIN(A,B) (A < B ? A : B)
+#define TEREMAX(A,B) (A > B ? A : B)
+
+#ifndef M_PI
+# define M_PI 3.14159265358979323846 /* pi */
+#endif
+#define ALLOCINFO_CHUNK 32
+#define ALLOCOUT_CHUNK 8192
+#define TRPRINT trinfo_append_out
+/** \endcond */
+
+/** \defgroup color background options
+ Text is underwritten with the background color not at all,
+ by reassembled line, or by full assembly .
+ @{
+*/
+#define BKCLR_NONE 0x00 /**< text is not underwritten with background color (default) */
+#define BKCLR_FRAG 0x01 /**< each fragment of text is underwritten with background color */
+#define BKCLR_LINE 0x02 /**< each line of text is underwritten with background color */
+#define BKCLR_ALL 0x03 /**< entire assembly is underwritten with background color */
+/** @} */
+
+/** \defgroup decoration options
+ One of these values may be present in the decoration field.
+ Unused bits may be used by end user code.
+ These values are SVG specific. Other applications could use the text
+ decoration field for a different set of bits, so long as it provided its own
+ output function.
+ @{
+*/
+#define TXTDECOR_NONE 0x000 /**< text is not decorated (default) */
+#define TXTDECOR_UNDER 0x001 /**< underlined */
+#define TXTDECOR_OVER 0x002 /**< overlined */
+#define TXTDECOR_BLINK 0x004 /**< blinking text */
+#define TXTDECOR_STRIKE 0x008 /**< strike through */
+#define TXTDECOR_TMASK 0x00F /**< Mask for selecting bits above */
+
+#define TXTDECOR_SOLID 0x000 /**< draw as single solid line */
+#define TXTDECOR_DOUBLE 0x010 /**< draw as double solid line */
+#define TXTDECOR_DOTTED 0x020 /**< draw as single dotted line */
+#define TXTDECOR_DASHED 0x040 /**< draw as single dashed line */
+#define TXTDECOR_WAVY 0x080 /**< draw as single wavy line */
+#define TXTDECOR_LMASK 0x0F0 /**< Mask for selecting these bits */
+
+#define TXTDECOR_CLRSET 0x100 /**< decoration has its own color */
+
+/** @} */
+
+
+
+
+
+/** \defgroup text alignment types
+ Location of text's {X,Y} coordinate on bounding rectangle.
+ Values are compatible with Fontconfig.
+ @{
+*/
+#define ALILEFT 0x01 /**< text object horizontal alignment = left */
+#define ALICENTER 0x02 /**< text object horizontal alignment = center */
+#define ALIRIGHT 0x04 /**< text object horizontal alignment = right */
+#define ALIHORI 0x07 /**< text object horizontal alignment mask */
+#define ALITOP 0x08 /**< text object vertical alignment = top */
+#define ALIBASE 0x10 /**< text object vertical alignment = baseline */
+#define ALIBOT 0x20 /**< text object vertical alignment = bottom */
+#define ALIVERT 0x38 /**< text object vertical alignment mask */
+/** @} */
+
+/** \defgroup language direction types
+ @{
+*/
+#define LDIR_LR 0x00 /**< left to right */
+#define LDIR_RL 0x01 /**< right to left */
+#define LDIR_TB 0x02 /**< top to bottom */
+/** @} */
+
+/** \defgroup special processing flags
+ @{
+*/
+#define TR_EMFBOT 0x01 /**< use an approximation compatible with EMF file's "BOTTOM" text orientation, which is not the "bottom" for Freetype fonts */
+/** @} */
+
+/** \enum tr_classes
+classification of complexes
+ @{
+*/
+enum tr_classes {
+ TR_TEXT, /**< simple text object */
+ TR_LINE, /**< linear assembly of TR_TEXTs */
+ TR_PARA_UJ, /**< sequential assembly of TR_LINEs and TR_TEXTs into a paragraph -
+ unknown justification properties */
+ TR_PARA_LJ, /**< ditto, left justified */
+ TR_PARA_CJ, /**< ditto, center justified */
+ TR_PARA_RJ /**< ditto, right justified */
+ };
+/** @} */
+
+/**
+ \brief alt font entries.
+*/
+typedef struct {
+ uint32_t fi_idx; /**< index into FT_INFO fonts, for fonts added for missing glyphs */
+ uint32_t weight; /**< integer weight for alt fonts, kept sorted into descending order */
+} ALT_SPECS;
+
+/**
+ \brief Information for a font instance.
+*/
+typedef struct {
+ FcFontSet *fontset; /**< all matching fonts (for fallback on missing glyphs) */
+ ALT_SPECS *alts; /**< index into FT_INFO fonts, for fonts added for missing glyphs */
+ uint32_t space; /**< alts storage slots allocated */
+ uint32_t used; /**< alts storage slots in use */
+ FT_Face face; /**< font face structures (FT_FACE is a pointer!) */
+ uint8_t *file; /**< pointer to font paths to files */
+ uint8_t *fontspec; /**< pointer to a font specification (name:italics, etc.) */
+ FcPattern *fpat; /**< current font, must hang onto this or faces operations break */
+ double spcadv; /**< advance equal to a space, in points at font's face size */
+ double fsize; /**< font's face size in points */
+} FNT_SPECS;
+
+/**
+ \brief Information for all font instances.
+*/
+typedef struct {
+ FT_Library library; /**< Fontconfig handle */
+ FNT_SPECS *fonts; /**< Array of fontinfo structures */
+ uint32_t space; /**< storage slots allocated */
+ uint32_t used; /**< storage slots in use */
+} FT_INFO;
+
+typedef struct {
+ uint8_t Red; //!< Red color (0-255)
+ uint8_t Green; //!< Green color (0-255)
+ uint8_t Blue; //!< Blue color (0-255)
+ uint8_t Reserved; //!< Not used
+} TRCOLORREF;
+
+/**
+ \brief Information for a single text object
+*/
+typedef struct {
+ uint8_t *string; /**< UTF-8 text */
+ double ori; /**< Orientation, angle of characters with respect to baseline in degrees */
+ double fs; /**< font size of text */
+ double x; /**< x coordinate, relative to TR_INFO x,y, in points */
+ double y; /**< y coordinate, relative to TR_INFO x,y, in points */
+ double xkern; /**< x kern relative to preceding text chunk in complex (if any) */
+ double ykern; /**< y kern relative to preceding text chunk in complex (if any) */
+ double boff; /**< Y LL corner - boff finds baseline */
+ double vadvance; /**< Line spacing typically 1.25 or 1.2, only set on the first text
+ element in a complex */
+ TRCOLORREF color; /**< RGB */
+ int taln; /**< text alignment with respect to x,y */
+ int ldir; /**< language direction LDIR_* */
+ int italics; /**< italics, as in FontConfig */
+ int weight; /**< weight, as in FontConfig */
+ int condensed; /**< condensed, as in FontConfig */
+ int decoration; /**< text decorations, ignored during assembly, used during output */
+ int spaces; /**< count of spaces converted from wide kerning (1 or 2) */
+ TRCOLORREF decColor; /**< text decoration color, ignored during assembly, used during output */
+ int co; /**< condensed override, if set Font name included narrow */
+ int rt_tidx; /**< index of rectangle that contains it */
+ int fi_idx; /**< index of the font it uses */
+} TCHUNK_SPECS;
+
+/**
+ \brief Information for all text objects.
+ Coordinates here are INTERNAL, after offset/rotate using values in TR_INFO.
+*/
+typedef struct {
+ TCHUNK_SPECS *chunks; /**< text chunks */
+ uint32_t space; /**< storage slots allocated */
+ uint32_t used; /**< storage slots in use */
+} TP_INFO;
+
+/**
+ \brief Information for a single bounding rectangle.
+ Coordinates here are INTERNAL, after offset/rotate using values in TR_INFO.
+*/
+typedef struct {
+ double xll; /**< x rectangle lower left corner */
+ double yll; /**< y " */
+ double xur; /**< x upper right corner */
+ double yur; /**< y " */
+ double xbearing; /**< x bearing of the leftmost character */
+} BRECT_SPECS;
+
+/**
+ \brief Information for all bounding rectangles.
+*/
+typedef struct {
+ BRECT_SPECS *rects; /**< bounding rectangles */
+ uint32_t space; /**< storage slots allocated */
+ uint32_t used; /**< storage slots in use */
+} BR_INFO;
+
+/**
+ \brief List of all members of a single complex.
+*/
+typedef struct {
+ int *members; /**< array of immediate children (for TR_PARA_* these are indices
+ for TR_TEXT or TR_LINE complexes also in cxi. For TR_TEXT
+ and TR_LINE these are indices to the actual text in tpi.) */
+ uint32_t space; /**< storage slots allocated */
+ uint32_t used; /**< storage slots in use */
+} CHILD_SPECS;
+
+/**
+ \brief Information for a single complex.
+*/
+typedef struct {
+ int rt_cidx; /**< index of rectangle that contains all members */
+ enum tr_classes type; /**< classification of the complex */
+ CHILD_SPECS kids; /**< immediate child nodes of this complex, for type TR_TEXT the
+ idx refers to the tpi data. otherwise, cxi data */
+} CX_SPECS;
+
+/**
+ \brief Information for all complexes.
+*/
+typedef struct {
+ CX_SPECS *cx; /**< complexes */
+ uint32_t space; /**< storage slots allocated */
+ uint32_t used; /**< storage slots in use */
+ uint32_t phase1; /**< Number of complexes (lines + text fragments) entered in phase 1 */
+ uint32_t lines; /**< Number of lines in phase 1 */
+ uint32_t paras; /**< Number of complexes (paras) entered in phase 2 */
+} CX_INFO;
+
+/**
+ \brief Information for the entire text reassembly system.
+*/
+typedef struct {
+ FT_INFO *fti; /**< Font info storage */
+ TP_INFO *tpi; /**< Text Info/Position Info storage */
+ BR_INFO *bri; /**< Bounding Rectangle Info storage */
+ CX_INFO *cxi; /**< Complex Info storage */
+ uint8_t *out; /**< buffer to hold formatted output */
+ double qe; /**< quantization error in points. */
+ double esc; /**< escapement angle in DEGREES */
+ double x; /**< x coordinate of first text object, in points */
+ double y; /**< y coordinate of first text object, in points */
+ int dirty; /**< 1 if text records are loaded */
+ int use_kern; /**< 1 if kerning is used, 0 if not */
+ int load_flags; /**< FT_LOAD_NO_SCALE or FT_LOAD_TARGET_NORMAL */
+ int kern_mode; /**< FT_KERNING_DEFAULT, FT_KERNING_UNFITTED, or FT_KERNING_UNSCALED */
+ uint32_t outspace; /**< storage in output buffer allocated */
+ uint32_t outused; /**< storage in output buffer in use */
+ int usebk; /**< On output write the background color under the text */
+ TRCOLORREF bkcolor; /**< RGB background color */
+} TR_INFO;
+
+/* padding added to rectangles before overlap test */
+/**
+ \brief Information for one padding record. (Padding is added to bounding rectangles before overlap tests.)
+*/
+typedef struct {
+ double up; /**< to top */
+ double down; /**< to bottom */
+ double left; /**< to left */
+ double right; /**< to right */
+} RT_PAD;
+
+/** \cond */
+/*
+ iconv() has a funny cast on some older systems, on most recent ones
+ it is just char **. This tries to work around the issue. If you build this
+ on another funky system this code may need to be modified, or define ICONV_CAST
+ on the compile line(but it may be tricky).
+*/
+#ifdef SOL8
+#define ICONV_CAST (const char **)
+#endif //SOL8
+#if !defined(ICONV_CAST)
+#define ICONV_CAST (char **)
+#endif //ICONV_CAST
+/** \endcond */
+
+/* Prototypes */
+int TR_findcasesub(const char *string, const char *sub);
+char *TR_construct_fontspec(const TCHUNK_SPECS *tsp, const char *fontname);
+char *TR_reconstruct_fontspec(const char *fontspec, const char *fontname);
+int TR_find_alternate_font(FT_INFO *fti, FNT_SPECS **efsp, uint32_t wc);
+int TR_getadvance(FT_INFO *fti, FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int load_flags, int kern_mode, int *ymin, int *ymax);
+int TR_getkern2(FNT_SPECS *fsp, uint32_t wc, uint32_t pc, int kern_mode);
+int TR_kern_gap(FNT_SPECS *fsp, TCHUNK_SPECS *tsp, TCHUNK_SPECS *ptsp, int kern_mode);
+void TR_rt_pad_set(RT_PAD *rt_pad, double up, double down, double left, double right);
+double TR_baseline(TR_INFO *tri, int src, double *AscMax, double *DscMax);
+int TR_check_set_vadvance(TR_INFO *tri, int src, int lines);
+int TR_layout_analyze(TR_INFO *tri);
+void TR_layout_2_svg(TR_INFO *tri);
+int TR_weight_FC_to_SVG(int weight);
+
+FT_INFO *ftinfo_init(void);
+int ftinfo_make_insertable(FT_INFO *fti);
+int ftinfo_insert(FT_INFO *fti, FNT_SPECS *fsp);
+FT_INFO *ftinfo_release(FT_INFO *fti);
+FT_INFO *ftinfo_clear(FT_INFO *fti);
+int ftinfo_find_loaded_by_spec(const FT_INFO *fti, const uint8_t *fname);
+int ftinfo_find_loaded_by_src(const FT_INFO *fti, const uint8_t *filename);
+int ftinfo_load_fontname(FT_INFO *fti, const char *fontspec);
+void ftinfo_dump(const FT_INFO *fti);
+
+int fsp_alts_make_insertable(FNT_SPECS *fsp);
+int fsp_alts_insert(FNT_SPECS *fsp, uint32_t fi_idx);
+int fsp_alts_weight(FNT_SPECS *fsp, uint32_t a_idx);
+
+int csp_make_insertable(CHILD_SPECS *csp);
+int csp_insert(CHILD_SPECS *csp, int src);
+int csp_merge(CHILD_SPECS *dst, CHILD_SPECS *src);
+void csp_release(CHILD_SPECS *csp);
+void csp_clear(CHILD_SPECS *csp);
+
+CX_INFO *cxinfo_init(void);
+int cxinfo_make_insertable(CX_INFO *cxi);
+int cxinfo_insert(CX_INFO *cxi, int src, int src_rt_idx, enum tr_classes type);
+int cxinfo_append(CX_INFO *cxi, int src, enum tr_classes type);
+int cxinfo_merge(CX_INFO *cxi, int dst, int src, enum tr_classes type);
+int cxinfo_trim(CX_INFO *cxi);
+CX_INFO *cxinfo_release(CX_INFO *cxi);
+void cxinfo_dump(const TR_INFO *tri);
+
+TP_INFO *tpinfo_init(void);
+int tpinfo_make_insertable(TP_INFO *tpi);
+int tpinfo_insert(TP_INFO *tpi, const TCHUNK_SPECS *tsp);
+TP_INFO *tpinfo_release(TP_INFO *tpi);
+
+BR_INFO *brinfo_init(void);
+int brinfo_make_insertable(BR_INFO *bri);
+int brinfo_insert(BR_INFO *bri, const BRECT_SPECS *element);
+int brinfo_merge(BR_INFO *bri, int dst, int src);
+enum tr_classes
+ brinfo_pp_alignment(const BR_INFO *bri, int dst, int src, double slop, enum tr_classes type);
+int brinfo_overlap(const BR_INFO *bri, int dst, int src, RT_PAD *rp_dst, RT_PAD *rp_src);
+BR_INFO *brinfo_release(BR_INFO *bri);
+
+TR_INFO *trinfo_init(TR_INFO *tri);
+TR_INFO *trinfo_release(TR_INFO *tri);
+TR_INFO *trinfo_release_except_FC(TR_INFO *tri);
+TR_INFO *trinfo_clear(TR_INFO *tri);
+int trinfo_load_qe(TR_INFO *tri, double qe);
+int trinfo_load_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor);
+int trinfo_load_ft_opts(TR_INFO *tri, int use_kern, int load_flags, int kern_mode);
+int trinfo_load_textrec(TR_INFO *tri, const TCHUNK_SPECS *tsp, double escapement, int flags);
+int trinfo_check_bk(TR_INFO *tri, int usebk, TRCOLORREF bkcolor);
+int trinfo_append_out(TR_INFO *tri, const char *src);
+
+int is_mn_unicode(int test);
+
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _TEXT_REASSEMBLE_ */
diff --git a/src/extension/internal/vsd-input.cpp b/src/extension/internal/vsd-input.cpp
new file mode 100644
index 0000000..57763d5
--- /dev/null
+++ b/src/extension/internal/vsd-input.cpp
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This file came from libwpg as a source, their utility wpg2svg
+ * specifically. It has been modified to work as an Inkscape extension.
+ * The Inkscape extension code is covered by this copyright, but the
+ * rest is covered by the one below.
+ *
+ * Authors:
+ * Fridrich Strba (fridrich.strba@bluewin.ch)
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <cstdio>
+
+#include "vsd-input.h"
+
+#ifdef WITH_LIBVISIO
+
+#include <string>
+#include <cstring>
+
+#include <libvisio/libvisio.h>
+
+#include <librevenge-stream/librevenge-stream.h>
+
+using librevenge::RVNGString;
+using librevenge::RVNGFileStream;
+using librevenge::RVNGStringVector;
+
+#include <gtkmm/spinbutton.h>
+
+#include "extension/system.h"
+#include "extension/input.h"
+
+#include "document.h"
+#include "inkscape.h"
+
+#include "ui/dialog-events.h"
+#include <glibmm/i18n.h>
+
+#include "ui/view/svg-view-widget.h"
+
+#include "object/sp-root.h"
+
+#include "util/units.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+
+class VsdImportDialog : public Gtk::Dialog {
+public:
+ VsdImportDialog(const std::vector<RVNGString> &vec);
+ ~VsdImportDialog() override;
+
+ bool showDialog();
+ unsigned getSelectedPage();
+ void getImportSettings(Inkscape::XML::Node *prefs);
+
+private:
+ void _setPreviewPage();
+
+ // Signal handlers
+ void _onPageNumberChanged();
+ void _onSpinButtonPress(GdkEventButton* button_event);
+ void _onSpinButtonRelease(GdkEventButton* button_event);
+
+ class Gtk::Box * vbox1;
+ class Inkscape::UI::View::SVGViewWidget * _previewArea;
+ class Gtk::Button * cancelbutton;
+ class Gtk::Button * okbutton;
+
+ class Gtk::Box * _page_selector_box;
+ class Gtk::Label * _labelSelect;
+ class Gtk::Label * _labelTotalPages;
+ class Gtk::SpinButton * _pageNumberSpin;
+
+ const std::vector<RVNGString> &_vec; // Document to be imported
+ unsigned _current_page; // Current selected page
+ bool _spinning; // whether SpinButton is pressed (i.e. we're "spinning")
+};
+
+VsdImportDialog::VsdImportDialog(const std::vector<RVNGString> &vec)
+ : _previewArea(nullptr)
+ , _vec(vec)
+ , _current_page(1)
+ , _spinning(false)
+{
+ int num_pages = _vec.size();
+ if ( num_pages <= 1 )
+ return;
+
+
+ // Dialog settings
+ this->set_title(_("Page Selector"));
+ this->set_modal(true);
+ sp_transientize(GTK_WIDGET(this->gobj())); //Make transient
+ this->property_window_position().set_value(Gtk::WIN_POS_NONE);
+ this->set_resizable(true);
+ this->property_destroy_with_parent().set_value(false);
+
+ // Preview area
+ vbox1 = Gtk::manage(new class Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ this->get_content_area()->pack_start(*vbox1);
+
+ // CONTROLS
+ _page_selector_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+
+ // Labels
+ _labelSelect = Gtk::manage(new class Gtk::Label(_("Select page:")));
+ _labelTotalPages = Gtk::manage(new class Gtk::Label());
+ _labelSelect->set_line_wrap(false);
+ _labelSelect->set_use_markup(false);
+ _labelSelect->set_selectable(false);
+ _page_selector_box->pack_start(*_labelSelect, Gtk::PACK_SHRINK);
+
+ // Adjustment + spinner
+ auto _pageNumberSpin_adj = Gtk::Adjustment::create(1, 1, _vec.size(), 1, 10, 0);
+ _pageNumberSpin = Gtk::manage(new Gtk::SpinButton(_pageNumberSpin_adj, 1, 0));
+ _pageNumberSpin->set_can_focus();
+ _pageNumberSpin->set_update_policy(Gtk::UPDATE_ALWAYS);
+ _pageNumberSpin->set_numeric(true);
+ _pageNumberSpin->set_wrap(false);
+ _page_selector_box->pack_start(*_pageNumberSpin, Gtk::PACK_SHRINK);
+
+ _labelTotalPages->set_line_wrap(false);
+ _labelTotalPages->set_use_markup(false);
+ _labelTotalPages->set_selectable(false);
+ gchar *label_text = g_strdup_printf(_("out of %i"), num_pages);
+ _labelTotalPages->set_label(label_text);
+ g_free(label_text);
+ _page_selector_box->pack_start(*_labelTotalPages, Gtk::PACK_SHRINK);
+
+ vbox1->pack_end(*_page_selector_box, Gtk::PACK_SHRINK);
+
+ // Buttons
+ cancelbutton = Gtk::manage(new Gtk::Button(_("_Cancel"), true));
+ okbutton = Gtk::manage(new Gtk::Button(_("_OK"), true));
+ this->add_action_widget(*cancelbutton, Gtk::RESPONSE_CANCEL);
+ this->add_action_widget(*okbutton, Gtk::RESPONSE_OK);
+
+ // Show all widgets in dialog
+ this->show_all();
+
+ // Connect signals
+ _pageNumberSpin->signal_value_changed().connect(sigc::mem_fun(*this, &VsdImportDialog::_onPageNumberChanged));
+ _pageNumberSpin->signal_button_press_event().connect_notify(sigc::mem_fun(*this, &VsdImportDialog::_onSpinButtonPress));
+ _pageNumberSpin->signal_button_release_event().connect_notify(sigc::mem_fun(*this, &VsdImportDialog::_onSpinButtonRelease));
+
+ _setPreviewPage();
+}
+
+VsdImportDialog::~VsdImportDialog() = default;
+
+bool VsdImportDialog::showDialog()
+{
+ show();
+ gint b = run();
+ hide();
+ if (b == Gtk::RESPONSE_OK || b == Gtk::RESPONSE_ACCEPT) {
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
+
+unsigned VsdImportDialog::getSelectedPage()
+{
+ return _current_page;
+}
+
+void VsdImportDialog::_onPageNumberChanged()
+{
+ unsigned page = static_cast<unsigned>(_pageNumberSpin->get_value_as_int());
+ _current_page = CLAMP(page, 1U, _vec.size());
+ _setPreviewPage();
+}
+
+void VsdImportDialog::_onSpinButtonPress(GdkEventButton* /*button_event*/)
+{
+ _spinning = true;
+}
+
+void VsdImportDialog::_onSpinButtonRelease(GdkEventButton* /*button_event*/)
+{
+ _spinning = false;
+ _setPreviewPage();
+}
+
+/**
+ * \brief Renders the given page's thumbnail
+ */
+void VsdImportDialog::_setPreviewPage()
+{
+ if (_spinning) {
+ return;
+ }
+
+ SPDocument *doc = SPDocument::createNewDocFromMem(_vec[_current_page-1].cstr(), strlen(_vec[_current_page-1].cstr()), false);
+ if(!doc) {
+ g_warning("VSD import: Could not create preview for page %d", _current_page);
+ gchar const *no_preview_template = R"A(
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'>
+ <path d='M 82,10 18,74 m 0,-64 64,64' style='fill:none;stroke:#ff0000;stroke-width:2px;'/>
+ <rect x='18' y='10' width='64' height='64' style='fill:none;stroke:#000000;stroke-width:1.5px;'/>
+ <text x='50' y='92' style='font-size:10px;text-anchor:middle;font-family:sans-serif;'>%s</text>
+ </svg>
+ )A";
+ gchar * no_preview = g_strdup_printf(no_preview_template, _("No preview"));
+ doc = SPDocument::createNewDocFromMem(no_preview, strlen(no_preview), false);
+ g_free(no_preview);
+ }
+
+ if (!doc) {
+ std::cerr << "VsdImportDialog::_setPreviewPage: No document!" << std::endl;
+ return;
+ }
+
+ if (_previewArea) {
+ _previewArea->setDocument(doc);
+ } else {
+ _previewArea = Gtk::manage(new Inkscape::UI::View::SVGViewWidget(doc));
+ vbox1->pack_start(*_previewArea, Gtk::PACK_EXPAND_WIDGET, 0);
+ }
+
+ _previewArea->setResize(400, 400);
+ _previewArea->show_all();
+}
+
+SPDocument *VsdInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri)
+{
+ #ifdef _WIN32
+ // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows
+ // therefore attempt to convert uri to the system codepage
+ // even if this is not possible the alternate short (8.3) file name will be used if available
+ gchar * converted_uri = g_win32_locale_filename_from_utf8(uri);
+ RVNGFileStream input(converted_uri);
+ g_free(converted_uri);
+ #else
+ RVNGFileStream input(uri);
+ #endif
+
+ if (!libvisio::VisioDocument::isSupported(&input)) {
+ return nullptr;
+ }
+
+ RVNGStringVector output;
+ librevenge::RVNGSVGDrawingGenerator generator(output, "svg");
+
+ if (!libvisio::VisioDocument::parse(&input, &generator)) {
+ return nullptr;
+ }
+
+ if (output.empty()) {
+ return nullptr;
+ }
+
+ std::vector<RVNGString> tmpSVGOutput;
+ for (unsigned i=0; i<output.size(); ++i) {
+ RVNGString tmpString("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
+ tmpString.append(output[i]);
+ tmpSVGOutput.push_back(tmpString);
+ }
+
+ unsigned page_num = 1;
+
+ // If only one page is present, import that one without bothering user
+ if (tmpSVGOutput.size() > 1) {
+ VsdImportDialog *dlg = nullptr;
+ if (INKSCAPE.use_gui()) {
+ dlg = new VsdImportDialog(tmpSVGOutput);
+ if (!dlg->showDialog()) {
+ delete dlg;
+ throw Input::open_cancelled();
+ }
+ }
+
+ // Get needed page
+ if (dlg) {
+ page_num = dlg->getSelectedPage();
+ if (page_num < 1)
+ page_num = 1;
+ if (page_num > tmpSVGOutput.size())
+ page_num = tmpSVGOutput.size();
+ }
+ }
+
+ SPDocument * doc = SPDocument::createNewDocFromMem(tmpSVGOutput[page_num-1].cstr(), strlen(tmpSVGOutput[page_num-1].cstr()), TRUE);
+
+ // Set viewBox if it doesn't exist
+ if (doc && !doc->getRoot()->viewBox_set) {
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit())));
+ }
+ return doc;
+}
+
+#include "clear-n_.h"
+
+void VsdInput::init()
+{
+ // clang-format off
+ /* 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());
+ // clang-format on
+
+ return;
+
+} // init
+
+} } } /* namespace Inkscape, Extension, Implementation */
+#endif /* WITH_LIBVISIO */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/vsd-input.h b/src/extension/internal/vsd-input.h
new file mode 100644
index 0000000..f30c905
--- /dev/null
+++ b/src/extension/internal/vsd-input.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This code abstracts the libwpg interfaces into the Inkscape
+ * input extension interface.
+ *
+ * Authors:
+ * Fridrich Strba (fridrich.strba@bluewin.ch)
+ *
+ * Copyright (C) 2012 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __EXTENSION_INTERNAL_VSDOUTPUT_H__
+#define __EXTENSION_INTERNAL_VSDOUTPUT_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef WITH_LIBVISIO
+
+#include <gtkmm/dialog.h>
+
+#include "../implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class VsdInput : public Inkscape::Extension::Implementation::Implementation {
+ VsdInput () = default;;
+public:
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+ static void init( );
+
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* WITH_LIBVISIO */
+#endif /* __EXTENSION_INTERNAL_VSDOUTPUT_H__ */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/wmf-inout.cpp b/src/extension/internal/wmf-inout.cpp
new file mode 100644
index 0000000..048295d
--- /dev/null
+++ b/src/extension/internal/wmf-inout.cpp
@@ -0,0 +1,3262 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Windows-only Enhanced Metafile input and output.
+ */
+/* Authors:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * David Mathog
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ * References:
+ * - How to Create & Play Enhanced Metafiles in Win32
+ * http://support.microsoft.com/kb/q145999/
+ * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles
+ * http://support.microsoft.com/kb/q66949/
+ * - Metafile Functions
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp
+ * - Metafile Structures
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp
+ */
+
+//#include <png.h> //This must precede text_reassemble.h or it blows up in pngconf.h when compiling
+#include <cstdio>
+#include <cstdlib>
+#include <cstdint>
+#include <3rdparty/libuemf/symbol_convert.h>
+
+#include "document.h"
+#include "object/sp-root.h" // even though it is included indirectly by wmf-inout.h
+#include "object/sp-path.h"
+#include "print.h"
+#include "extension/system.h"
+#include "extension/print.h"
+#include "extension/db.h"
+#include "extension/input.h"
+#include "extension/output.h"
+#include "display/drawing.h"
+#include "display/drawing-item.h"
+#include "clear-n_.h"
+#include "path/path-boolop.h"
+#include "svg/svg.h"
+#include "util/units.h" // even though it is included indirectly by wmf-inout.h
+#include "inkscape.h" // even though it is included indirectly by wmf-inout.h
+
+
+#include "wmf-inout.h"
+#include "wmf-print.h"
+
+#define PRINT_WMF "org.inkscape.print.wmf"
+
+#ifndef U_PS_JOIN_MASK
+#define U_PS_JOIN_MASK (U_PS_JOIN_BEVEL|U_PS_JOIN_MITER|U_PS_JOIN_ROUND)
+#endif
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+
+static bool clipset = false;
+static uint32_t BLTmode=0;
+
+Wmf::Wmf () // The null constructor
+{
+ return;
+}
+
+
+Wmf::~Wmf () //The destructor
+{
+ return;
+}
+
+
+bool
+Wmf::check (Inkscape::Extension::Extension * /*module*/)
+{
+ if (nullptr == Inkscape::Extension::db.get(PRINT_WMF))
+ return FALSE;
+ return TRUE;
+}
+
+
+void
+Wmf::print_document_to_file(SPDocument *doc, const gchar *filename)
+{
+ Inkscape::Extension::Print *mod;
+ SPPrintContext context;
+ const gchar *oldconst;
+ gchar *oldoutput;
+
+ doc->ensureUpToDate();
+
+ mod = Inkscape::Extension::get_print(PRINT_WMF);
+ oldconst = mod->get_param_string("destination");
+ oldoutput = g_strdup(oldconst);
+ mod->set_param_string("destination", filename);
+
+/* Start */
+ context.module = mod;
+ /* fixme: This has to go into module constructor somehow */
+ /* Create new arena */
+ mod->base = doc->getRoot();
+ Inkscape::Drawing drawing;
+ mod->dkey = SPItem::display_key_new(1);
+ mod->root = mod->base->invoke_show(drawing, mod->dkey, SP_ITEM_SHOW_DISPLAY);
+ drawing.setRoot(mod->root);
+ /* Print document */
+ if (mod->begin(doc)) {
+ g_free(oldoutput);
+ mod->base->invoke_hide(mod->dkey);
+ mod->base = nullptr;
+ mod->root = nullptr;
+ throw Inkscape::Extension::Output::save_failed();
+ }
+ mod->base->invoke_print(&context);
+ mod->finish();
+ /* Release arena */
+ mod->base->invoke_hide(mod->dkey);
+ mod->base = nullptr;
+ mod->root = nullptr; // deleted by invoke_hide
+/* end */
+
+ mod->set_param_string("destination", oldoutput);
+ g_free(oldoutput);
+
+ return;
+}
+
+
+void
+Wmf::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filename)
+{
+ Inkscape::Extension::Extension * ext;
+
+ ext = Inkscape::Extension::db.get(PRINT_WMF);
+ if (ext == nullptr)
+ return;
+
+ bool new_val = mod->get_param_bool("textToPath");
+ bool new_FixPPTCharPos = mod->get_param_bool("FixPPTCharPos"); // character position bug
+ // reserve FixPPT2 for opacity bug. Currently WMF does not export opacity values
+ bool new_FixPPTDashLine = mod->get_param_bool("FixPPTDashLine"); // dashed line bug
+ bool new_FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys"); // gradient bug
+ bool new_FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch"); // force all patterns as standard WMF hatch
+
+ TableGen( //possibly regenerate the unicode-convert tables
+ mod->get_param_bool("TnrToSymbol"),
+ mod->get_param_bool("TnrToWingdings"),
+ mod->get_param_bool("TnrToZapfDingbats"),
+ mod->get_param_bool("UsePUA")
+ );
+
+ ext->set_param_bool("FixPPTCharPos",new_FixPPTCharPos); // Remember to add any new ones to PrintWmf::init or a mysterious failure will result!
+ ext->set_param_bool("FixPPTDashLine",new_FixPPTDashLine);
+ ext->set_param_bool("FixPPTGrad2Polys",new_FixPPTGrad2Polys);
+ ext->set_param_bool("FixPPTPatternAsHatch",new_FixPPTPatternAsHatch);
+ ext->set_param_bool("textToPath", new_val);
+
+ // ensure usage of dot as decimal separator in scanf/printf functions (independently of current locale)
+ char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr));
+ setlocale(LC_NUMERIC, "C");
+
+ print_document_to_file(doc, filename);
+
+ // restore decimal separator used in scanf/printf functions to initial value
+ setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ return;
+}
+
+
+/* WMF has no worldTransform, so this always returns 1.0. Retain it to keep WMF and WMF in sync as much as possible.*/
+double Wmf::current_scale(PWMF_CALLBACK_DATA /*d*/){
+ return 1.0;
+}
+
+/* WMF has no worldTransform, so this always returns an Identity rotation matrix, but the offsets may have values.*/
+std::string Wmf::current_matrix(PWMF_CALLBACK_DATA d, double x, double y, int useoffset){
+ SVGOStringStream cxform;
+ double scale = current_scale(d);
+ cxform << "\"matrix(";
+ cxform << 1.0/scale; cxform << ",";
+ cxform << 0.0; cxform << ",";
+ cxform << 0.0; cxform << ",";
+ cxform << 1.0/scale; cxform << ",";
+ if(useoffset){ cxform << x; cxform << ","; cxform << y; }
+ else { cxform << "0,0"; }
+ cxform << ")\"";
+ return(cxform.str());
+}
+
+/* WMF has no worldTransform, so this always returns 0. Retain it to keep WMF and WMF in sync as much as possible.*/
+double Wmf::current_rotation(PWMF_CALLBACK_DATA /*d*/){
+ return 0.0;
+}
+
+/* Add another 100 blank slots to the hatches array.
+*/
+void Wmf::enlarge_hatches(PWMF_CALLBACK_DATA d){
+ d->hatches.size += 100;
+ d->hatches.strings = (char **) realloc(d->hatches.strings,d->hatches.size * sizeof(char *));
+}
+
+/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1)
+*/
+int Wmf::in_hatches(PWMF_CALLBACK_DATA d, char *test){
+ int i;
+ for(i=0; i<d->hatches.count; i++){
+ if(strcmp(test,d->hatches.strings[i])==0)return(i+1);
+ }
+ return(0);
+}
+
+class TagEmitter
+{
+public:
+ TagEmitter(Glib::ustring & p_defs, char * p_tmpcolor, char * p_hpathname):
+ defs(p_defs), tmpcolor(p_tmpcolor), hpathname(p_hpathname)
+ {
+ };
+ void append(const char *prefix, const char * inner)
+ {
+ defs += " ";
+ defs += prefix;
+ defs += hpathname;
+ defs += inner;
+ defs += tmpcolor;
+ defs += "\" />\n";
+ }
+
+protected:
+ Glib::ustring & defs;
+ char * tmpcolor, * hpathname;
+};
+
+/* (Conditionally) add a hatch. If a matching hatch already exists nothing happens. If one
+ does not exist it is added to the hatches list and also entered into <defs>.
+ This is also used to add the path part of the hatches, which they reference with a xlink:href
+*/
+uint32_t Wmf::add_hatch(PWMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor){
+ char hatchname[64]; // big enough
+ char hpathname[64]; // big enough
+ char hbkname[64]; // big enough
+ char tmpcolor[8];
+ char bkcolor[8];
+ uint32_t idx;
+
+ switch(hatchType){
+ case U_HS_SOLIDTEXTCLR:
+ case U_HS_DITHEREDTEXTCLR:
+ sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].textColor));
+ break;
+ case U_HS_SOLIDBKCLR:
+ case U_HS_DITHEREDBKCLR:
+ sprintf(tmpcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor));
+ break;
+ default:
+ sprintf(tmpcolor,"%6.6X",sethexcolor(hatchColor));
+ break;
+ }
+ auto & defs = d->defs;
+ TagEmitter a(defs, tmpcolor, hpathname);
+
+ /* For both bkMode types set the PATH + FOREGROUND COLOR for the indicated standard hatch.
+ This will be used late to compose, or recompose the transparent or opaque final hatch.*/
+
+ std::string refpath; // used to reference later the path pieces which are about to be created
+ sprintf(hpathname,"WMFhpath%d_%s",hatchType,tmpcolor);
+ idx = in_hatches(d,hpathname);
+ if(!idx){ // add path/color if not already present
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hpathname);
+
+ defs += "\n";
+ switch(hatchType){
+ case U_HS_HORIZONTAL:
+ a.append("<path id=\"",
+ "\" d=\"M 0 0 6 0\" style=\"fill:none;stroke:#");
+ break;
+ case U_HS_VERTICAL:
+ a.append("<path id=\"",
+ "\" d=\"M 0 0 0 6\" style=\"fill:none;stroke:#");
+ break;
+ case U_HS_FDIAGONAL:
+ a.append("<line id=\"sub",
+ "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#");
+ break;
+ case U_HS_BDIAGONAL:
+ a.append("<line id=\"sub",
+ "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#");
+ break;
+ case U_HS_CROSS:
+ a.append("<path id=\"",
+ "\" d=\"M 0 0 6 0 M 0 0 0 6\" style=\"fill:none;stroke:#");
+ break;
+ case U_HS_DIAGCROSS:
+ a.append("<line id=\"subfd",
+ "\" x1=\"-1\" y1=\"-1\" x2=\"7\" y2=\"7\" stroke=\"#");
+ a.append("<line id=\"subbd",
+ "\" x1=\"-1\" y1=\"7\" x2=\"7\" y2=\"-1\" stroke=\"#");
+ break;
+ case U_HS_SOLIDCLR:
+ case U_HS_DITHEREDCLR:
+ case U_HS_SOLIDTEXTCLR:
+ case U_HS_DITHEREDTEXTCLR:
+ case U_HS_SOLIDBKCLR:
+ case U_HS_DITHEREDBKCLR:
+ default:
+ a.append("<path id=\"",
+ "\" d=\"M 0 0 6 0 6 6 0 6 z\" style=\"stroke:none;fill:#");
+ break;
+ }
+ }
+
+ // References to paths possibly just created above. These will be used in the actual patterns.
+ switch(hatchType){
+ case U_HS_HORIZONTAL:
+ case U_HS_VERTICAL:
+ case U_HS_CROSS:
+ case U_HS_SOLIDCLR:
+ case U_HS_DITHEREDCLR:
+ case U_HS_SOLIDTEXTCLR:
+ case U_HS_DITHEREDTEXTCLR:
+ case U_HS_SOLIDBKCLR:
+ case U_HS_DITHEREDBKCLR:
+ default:
+ refpath += " <use xlink:href=\"#";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ break;
+ case U_HS_FDIAGONAL:
+ case U_HS_BDIAGONAL:
+ refpath += " <use xlink:href=\"#sub";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ refpath += " <use xlink:href=\"#sub";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(6,0)\" />\n";
+ refpath += " <use xlink:href=\"#sub";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(-6,0)\" />\n";
+ break;
+ case U_HS_DIAGCROSS:
+ refpath += " <use xlink:href=\"#subfd";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ refpath += " <use xlink:href=\"#subfd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(6,0)\"/>\n";
+ refpath += " <use xlink:href=\"#subfd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(-6,0)\"/>\n";
+ refpath += " <use xlink:href=\"#subbd";
+ refpath += hpathname;
+ refpath += "\" />\n";
+ refpath += " <use xlink:href=\"#subbd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(6,0)\"/>\n";
+ refpath += " <use xlink:href=\"#subbd";
+ refpath += hpathname;
+ refpath += "\" transform=\"translate(-6,0)\"/>\n";
+ break;
+ }
+
+ if(d->dc[d->level].bkMode == U_TRANSPARENT || hatchType >= U_HS_SOLIDCLR){
+ sprintf(hatchname,"WMFhatch%d_%s",hatchType,tmpcolor);
+ sprintf(hpathname,"WMFhpath%d_%s",hatchType,tmpcolor);
+ idx = in_hatches(d,hatchname);
+ if(!idx){ // add it if not already present
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hatchname);
+ defs += "\n";
+ defs += " <pattern id=\"";
+ defs += hatchname;
+ defs += "\" xlink:href=\"#WMFhbasepattern\">\n";
+ defs += refpath;
+ defs += " </pattern>\n";
+ idx = d->hatches.count;
+ }
+ }
+ else { // bkMode==U_OPAQUE
+ /* Set up an object in the defs for this background, if there is not one already there */
+ sprintf(bkcolor,"%6.6X",sethexcolor(d->dc[d->level].bkColor));
+ sprintf(hbkname,"WMFhbkclr_%s",bkcolor);
+ idx = in_hatches(d,hbkname);
+ if(!idx){ // add path/color if not already present. Hatchtype is not needed in the name.
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hbkname);
+
+ defs += "\n";
+ defs += " <rect id=\"";
+ defs += hbkname;
+ defs += "\" x=\"0\" y=\"0\" width=\"6\" height=\"6\" fill=\"#";
+ defs += bkcolor;
+ defs += "\" />\n";
+ }
+
+ // this is the pattern, its name will show up in Inkscape's pattern selector
+ sprintf(hatchname,"WMFhatch%d_%s_%s",hatchType,tmpcolor,bkcolor);
+ idx = in_hatches(d,hatchname);
+ if(!idx){ // add it if not already present
+ if(d->hatches.count == d->hatches.size){ enlarge_hatches(d); }
+ d->hatches.strings[d->hatches.count++]=strdup(hatchname);
+ defs += "\n";
+ defs += " <pattern id=\"";
+ defs += hatchname;
+ defs += "\" xlink:href=\"#WMFhbasepattern\">\n";
+ defs += " <use xlink:href=\"#";
+ defs += hbkname;
+ defs += "\" />\n";
+ defs += refpath;
+ defs += " </pattern>\n";
+ idx = d->hatches.count;
+ }
+ }
+ return(idx-1);
+}
+
+/* Add another 100 blank slots to the images array.
+*/
+void Wmf::enlarge_images(PWMF_CALLBACK_DATA d){
+ d->images.size += 100;
+ d->images.strings = (char **) realloc(d->images.strings,d->images.size * sizeof(char *));
+}
+
+/* See if the image string is already in the list. If it is return its position (1->n, not 1-n-1)
+*/
+int Wmf::in_images(PWMF_CALLBACK_DATA d, char *test){
+ int i;
+ for(i=0; i<d->images.count; i++){
+ if(strcmp(test,d->images.strings[i])==0)return(i+1);
+ }
+ return(0);
+}
+
+/* (Conditionally) add an image from a DIB. If a matching image already exists nothing happens. If one
+ does not exist it is added to the images list and also entered into <defs>.
+
+*/
+uint32_t Wmf::add_dib_image(PWMF_CALLBACK_DATA d, const char *dib, uint32_t iUsage){
+
+ uint32_t idx;
+ char imagename[64]; // big enough
+ char xywh[64]; // big enough
+ int dibparams = U_BI_UNKNOWN; // type of image not yet determined
+
+ MEMPNG mempng; // PNG in memory comes back in this
+ mempng.buffer = nullptr;
+
+ char *rgba_px = nullptr; // RGBA pixels
+ const char *px = nullptr; // DIB pixels
+ const U_RGBQUAD *ct = nullptr; // DIB color table
+ uint32_t numCt;
+ int32_t width, height, colortype, invert; // if needed these values will be set by wget_DIB_params
+ if(iUsage == U_DIB_RGB_COLORS){
+ // next call returns pointers and values, but allocates no memory
+ dibparams = wget_DIB_params(dib, &px, &ct, &numCt, &width, &height, &colortype, &invert);
+ if(dibparams == U_BI_RGB){
+ if(!DIB_to_RGBA(
+ px, // DIB pixel array
+ ct, // DIB color table
+ numCt, // DIB color table number of entries
+ &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free.
+ width, // Width of pixel array in record
+ height, // Height of pixel array in record
+ colortype, // DIB BitCount Enumeration
+ numCt, // Color table used if not 0
+ invert // If DIB rows are in opposite order from RGBA rows
+ )){
+ toPNG( // Get the image from the RGBA px into mempng
+ &mempng,
+ width, height, // of the SRC bitmap
+ rgba_px
+ );
+ free(rgba_px);
+ }
+ }
+ }
+
+ gchar *base64String=nullptr;
+ if(dibparams == U_BI_JPEG || dibparams==U_BI_PNG){ // image was binary png or jpg in source file
+ base64String = g_base64_encode((guchar*) px, numCt );
+ }
+ else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine
+ base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size );
+ free(mempng.buffer);
+ }
+ else { // failed conversion, insert the common bad image picture
+ width = 3;
+ height = 4;
+ base64String = bad_image_png();
+ }
+ idx = in_images(d, (char *) base64String);
+ auto & defs = d->defs;
+ if(!idx){ // add it if not already present - we looked at the actual data for comparison
+ if(d->images.count == d->images.size){ enlarge_images(d); }
+ idx = d->images.count;
+ d->images.strings[d->images.count++]=strdup(base64String);
+
+ sprintf(imagename,"WMFimage%d",idx++);
+ sprintf(xywh," x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" ",width,height); // reuse this buffer
+
+ defs += "\n";
+ defs += " <image id=\"";
+ defs += imagename;
+ defs += "\"\n ";
+ defs += xywh;
+ defs += "\n";
+ if(dibparams == U_BI_JPEG){ defs += " xlink:href=\"data:image/jpeg;base64,"; }
+ else { defs += " xlink:href=\"data:image/png;base64,"; }
+ defs += base64String;
+ defs += "\"\n";
+ defs += " preserveAspectRatio=\"none\"\n";
+ defs += " />\n";
+
+
+ defs += "\n";
+ defs += " <pattern id=\"";
+ defs += imagename;
+ defs += "_ref\"\n ";
+ defs += xywh;
+ defs += "\n patternUnits=\"userSpaceOnUse\"";
+ defs += " >\n";
+ defs += " <use id=\"";
+ defs += imagename;
+ defs += "_ign\" ";
+ defs += " xlink:href=\"#";
+ defs += imagename;
+ defs += "\" />\n";
+ defs += " ";
+ defs += " </pattern>\n";
+ }
+ g_free(base64String); //wait until this point to free because it might be a duplicate image
+ return(idx-1);
+}
+
+/* (Conditionally) add an image from a Bitmap16. If a matching image already exists nothing happens. If one
+ does not exist it is added to the images list and also entered into <defs>.
+
+*/
+uint32_t Wmf::add_bm16_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px){
+
+ uint32_t idx;
+ char imagename[64]; // big enough
+ char xywh[64]; // big enough
+
+ MEMPNG mempng; // PNG in memory comes back in this
+ mempng.buffer = nullptr;
+
+ char *rgba_px = nullptr; // RGBA pixels
+ const U_RGBQUAD *ct = nullptr; // color table, always NULL here
+ int32_t width, height, colortype, numCt, invert;
+ numCt = 0;
+ width = Bm16.Width; // bitmap width in pixels.
+ height = Bm16.Height; // bitmap height in scan lines.
+ colortype = Bm16.BitsPixel; // seems to be BitCount Enumeration
+ invert = 0;
+ if(colortype < 16)return(U_WMR_INVALID); // these would need a colortable if they were a dib, no idea what bm16 is supposed to do instead.
+
+ if(!DIB_to_RGBA(// This is not really a dib, but close enough so that it still works.
+ px, // DIB pixel array
+ ct, // DIB color table (always NULL here)
+ numCt, // DIB color table number of entries (always 0)
+ &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free.
+ width, // Width of pixel array
+ height, // Height of pixel array
+ colortype, // DIB BitCount Enumeration
+ numCt, // Color table used if not 0
+ invert // If DIB rows are in opposite order from RGBA rows
+ )){
+ toPNG( // Get the image from the RGBA px into mempng
+ &mempng,
+ width, height, // of the SRC bitmap
+ rgba_px
+ );
+ free(rgba_px);
+ }
+
+ gchar *base64String=nullptr;
+ if(mempng.buffer){ // image was Bm16 in source file, converted to png in this routine
+ base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size );
+ free(mempng.buffer);
+ }
+ else { // failed conversion, insert the common bad image picture
+ width = 3;
+ height = 4;
+ base64String = bad_image_png();
+ }
+
+ idx = in_images(d, (char *) base64String);
+ auto & defs = d->defs;
+ if(!idx){ // add it if not already present - we looked at the actual data for comparison
+ if(d->images.count == d->images.size){ enlarge_images(d); }
+ idx = d->images.count;
+ d->images.strings[d->images.count++]=g_strdup(base64String);
+
+ sprintf(imagename,"WMFimage%d",idx++);
+ sprintf(xywh," x=\"0\" y=\"0\" width=\"%d\" height=\"%d\" ",width,height); // reuse this buffer
+
+ defs += "\n";
+ defs += " <image id=\"";
+ defs += imagename;
+ defs += "\"\n ";
+ defs += xywh;
+ defs += "\n";
+ defs += " xlink:href=\"data:image/png;base64,";
+ defs += base64String;
+ defs += "\"\n";
+ defs += " preserveAspectRatio=\"none\"\n";
+ defs += " />\n";
+
+
+ defs += "\n";
+ defs += " <pattern id=\"";
+ defs += imagename;
+ defs += "_ref\"\n ";
+ defs += xywh;
+ defs += "\n patternUnits=\"userSpaceOnUse\"";
+ defs += " >\n";
+ defs += " <use id=\"";
+ defs += imagename;
+ defs += "_ign\" ";
+ defs += " xlink:href=\"#";
+ defs += imagename;
+ defs += "\" />\n";
+ defs += " </pattern>\n";
+ }
+ g_free(base64String); //wait until this point to free because it might be a duplicate image
+ return(idx-1);
+}
+
+/* Add another 100 blank slots to the clips array.
+*/
+void Wmf::enlarge_clips(PWMF_CALLBACK_DATA d){
+ d->clips.size += 100;
+ d->clips.strings = (char **) realloc(d->clips.strings,d->clips.size * sizeof(char *));
+}
+
+/* See if the pattern name is already in the list. If it is return its position (1->n, not 1-n-1)
+*/
+int Wmf::in_clips(PWMF_CALLBACK_DATA d, const char *test){
+ int i;
+ for(i=0; i<d->clips.count; i++){
+ if(strcmp(test,d->clips.strings[i])==0)return(i+1);
+ }
+ return(0);
+}
+
+/* (Conditionally) add a clip.
+ If a matching clip already exists nothing happens
+ If one does exist it is added to the clips list, entered into <defs>.
+*/
+void Wmf::add_clips(PWMF_CALLBACK_DATA d, const char *clippath, unsigned int logic){
+ int op = combine_ops_to_livarot(logic);
+ Geom::PathVector combined_vect;
+ std::string combined;
+ if (op >= 0 && d->dc[d->level].clip_id) {
+ unsigned int real_idx = d->dc[d->level].clip_id - 1;
+ Geom::PathVector old_vect = sp_svg_read_pathv(d->clips.strings[real_idx]);
+ Geom::PathVector new_vect = sp_svg_read_pathv(clippath);
+ combined_vect = sp_pathvector_boolop(new_vect, old_vect, (bool_op) op , (FillRule) fill_oddEven, (FillRule) fill_oddEven);
+ combined = sp_svg_write_path(combined_vect);
+ }
+ else {
+ combined = clippath; // COPY operation, erases everything and starts a new one
+ }
+
+ uint32_t idx = in_clips(d, combined.c_str());
+ if(!idx){ // add clip if not already present
+ if(d->clips.count == d->clips.size){ enlarge_clips(d); }
+ d->clips.strings[d->clips.count++] = strdup(combined.c_str());
+ d->dc[d->level].clip_id = d->clips.count; // one more than the slot where it is actually stored
+ SVGOStringStream tmp_clippath;
+ tmp_clippath << "\n<clipPath";
+ tmp_clippath << "\n\tclipPathUnits=\"userSpaceOnUse\" ";
+ tmp_clippath << "\n\tid=\"clipWmfPath" << d->dc[d->level].clip_id << "\"";
+ tmp_clippath << " >";
+ tmp_clippath << "\n\t<path d=\"";
+ tmp_clippath << combined;
+ tmp_clippath << "\"";
+ tmp_clippath << "\n\t/>";
+ tmp_clippath << "\n</clipPath>";
+ d->outdef += tmp_clippath.str().c_str();
+ }
+ else {
+ d->dc[d->level].clip_id = idx;
+ }
+}
+
+
+
+void
+Wmf::output_style(PWMF_CALLBACK_DATA d)
+{
+// SVGOStringStream tmp_id;
+ SVGOStringStream tmp_style;
+ char tmp[1024] = {0};
+
+ float fill_rgb[3];
+ d->dc[d->level].style.fill.value.color.get_rgb_floatv(fill_rgb);
+ float stroke_rgb[3];
+ d->dc[d->level].style.stroke.value.color.get_rgb_floatv(stroke_rgb);
+
+ // for U_WMR_BITBLT with no image, try to approximate some of these operations/
+ // Assume src color is "white"
+ if(d->dwRop3){
+ switch(d->dwRop3){
+ case U_PATINVERT: // treat all of these as black
+ case U_SRCINVERT:
+ case U_DSTINVERT:
+ case U_BLACKNESS:
+ case U_SRCERASE:
+ case U_NOTSRCCOPY:
+ fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=0.0;
+ break;
+ case U_SRCCOPY: // treat all of these as white
+ case U_NOTSRCERASE:
+ case U_PATCOPY:
+ case U_WHITENESS:
+ fill_rgb[0]=fill_rgb[1]=fill_rgb[2]=1.0;
+ break;
+ case U_SRCPAINT: // use the existing color
+ case U_SRCAND:
+ case U_MERGECOPY:
+ case U_MERGEPAINT:
+ case U_PATPAINT:
+ default:
+ break;
+ }
+ d->dwRop3 = 0; // might as well reset it here, it must be set for each BITBLT
+ }
+
+ // Implement some of these, the ones where the original screen color does not matter.
+ // The options that merge screen and pen colors cannot be done correctly because we
+ // have no way of knowing what color is already on the screen. For those just pass the
+ // pen color through.
+ switch(d->dwRop2){
+ case U_R2_BLACK:
+ fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 0.0;
+ stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 0.0;
+ break;
+ case U_R2_NOTMERGEPEN:
+ case U_R2_MASKNOTPEN:
+ break;
+ case U_R2_NOTCOPYPEN:
+ fill_rgb[0] = 1.0 - fill_rgb[0];
+ fill_rgb[1] = 1.0 - fill_rgb[1];
+ fill_rgb[2] = 1.0 - fill_rgb[2];
+ stroke_rgb[0] = 1.0 - stroke_rgb[0];
+ stroke_rgb[1] = 1.0 - stroke_rgb[1];
+ stroke_rgb[2] = 1.0 - stroke_rgb[2];
+ break;
+ case U_R2_MASKPENNOT:
+ case U_R2_NOT:
+ case U_R2_XORPEN:
+ case U_R2_NOTMASKPEN:
+ case U_R2_NOTXORPEN:
+ case U_R2_NOP:
+ case U_R2_MERGENOTPEN:
+ case U_R2_COPYPEN:
+ case U_R2_MASKPEN:
+ case U_R2_MERGEPENNOT:
+ case U_R2_MERGEPEN:
+ break;
+ case U_R2_WHITE:
+ fill_rgb[0] = fill_rgb[1] = fill_rgb[2] = 1.0;
+ stroke_rgb[0]= stroke_rgb[1]= stroke_rgb[2] = 1.0;
+ break;
+ default:
+ break;
+ }
+
+
+// tmp_id << "\n\tid=\"" << (d->id++) << "\"";
+// d->outsvg += tmp_id.str().c_str();
+ d->outsvg += "\n\tstyle=\"";
+ if (!d->dc[d->level].fill_set || ( d->mask & U_DRAW_NOFILL)) { // nofill are lines and arcs
+ tmp_style << "fill:none;";
+ } else {
+ switch(d->dc[d->level].fill_mode){
+ // both of these use the url(#) method
+ case DRAW_PATTERN:
+ snprintf(tmp, 1023, "fill:url(#%s); ",d->hatches.strings[d->dc[d->level].fill_idx]);
+ tmp_style << tmp;
+ break;
+ case DRAW_IMAGE:
+ snprintf(tmp, 1023, "fill:url(#WMFimage%d_ref); ",d->dc[d->level].fill_idx);
+ tmp_style << tmp;
+ break;
+ case DRAW_PAINT:
+ default: // <-- this should never happen, but just in case...
+ snprintf(
+ tmp, 1023,
+ "fill:#%02x%02x%02x;",
+ SP_COLOR_F_TO_U(fill_rgb[0]),
+ SP_COLOR_F_TO_U(fill_rgb[1]),
+ SP_COLOR_F_TO_U(fill_rgb[2])
+ );
+ tmp_style << tmp;
+ break;
+ }
+ snprintf(
+ tmp, 1023,
+ "fill-rule:%s;",
+ (d->dc[d->level].style.fill_rule.value == SP_WIND_RULE_NONZERO ? "evenodd" : "nonzero")
+ );
+ tmp_style << tmp;
+ tmp_style << "fill-opacity:1;";
+
+ // if the stroke is the same as the fill, and the right size not to change the end size of the object, do not do it separately
+ if(
+ (d->dc[d->level].fill_set ) &&
+ (d->dc[d->level].stroke_set ) &&
+ (d->dc[d->level].style.stroke_width.value == 1 ) &&
+ (d->dc[d->level].fill_mode == d->dc[d->level].stroke_mode) &&
+ (
+ (d->dc[d->level].fill_mode != DRAW_PAINT) ||
+ (
+ (fill_rgb[0]==stroke_rgb[0]) &&
+ (fill_rgb[1]==stroke_rgb[1]) &&
+ (fill_rgb[2]==stroke_rgb[2])
+ )
+ )
+ ){
+ d->dc[d->level].stroke_set = false;
+ }
+ }
+
+ if (!d->dc[d->level].stroke_set) {
+ tmp_style << "stroke:none;";
+ } else {
+ switch(d->dc[d->level].stroke_mode){
+ // both of these use the url(#) method
+ case DRAW_PATTERN:
+ snprintf(tmp, 1023, "stroke:url(#%s); ",d->hatches.strings[d->dc[d->level].stroke_idx]);
+ tmp_style << tmp;
+ break;
+ case DRAW_IMAGE:
+ snprintf(tmp, 1023, "stroke:url(#WMFimage%d_ref); ",d->dc[d->level].stroke_idx);
+ tmp_style << tmp;
+ break;
+ case DRAW_PAINT:
+ default: // <-- this should never happen, but just in case...
+ snprintf(
+ tmp, 1023,
+ "stroke:#%02x%02x%02x;",
+ SP_COLOR_F_TO_U(stroke_rgb[0]),
+ SP_COLOR_F_TO_U(stroke_rgb[1]),
+ SP_COLOR_F_TO_U(stroke_rgb[2])
+ );
+ tmp_style << tmp;
+ break;
+ }
+ if(d->dc[d->level].style.stroke_width.value){
+ tmp_style << "stroke-width:" <<
+ MAX( 0.001, d->dc[d->level].style.stroke_width.value ) << "px;";
+ }
+ else { // In a WMF a 0 width pixel means "1 pixel"
+ tmp_style << "stroke-width:" << pix_to_abs_size( d, 1 ) << "px;";
+ }
+
+ tmp_style << "stroke-linecap:" <<
+ (
+ d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_BUTT ? "butt" :
+ d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_ROUND ? "round" :
+ d->dc[d->level].style.stroke_linecap.computed == SP_STROKE_LINECAP_SQUARE ? "square" :
+ "unknown"
+ ) << ";";
+
+ tmp_style << "stroke-linejoin:" <<
+ (
+ d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_MITER ? "miter" :
+ d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_ROUND ? "round" :
+ d->dc[d->level].style.stroke_linejoin.computed == SP_STROKE_LINEJOIN_BEVEL ? "bevel" :
+ "unknown"
+ ) << ";";
+
+ // Set miter limit if known, even if it is not needed immediately (not miter)
+ tmp_style << "stroke-miterlimit:" <<
+ MAX( 2.0, d->dc[d->level].style.stroke_miterlimit.value ) << ";";
+
+ if (d->dc[d->level].style.stroke_dasharray.set &&
+ !d->dc[d->level].style.stroke_dasharray.values.empty())
+ {
+ tmp_style << "stroke-dasharray:";
+ for (unsigned i=0; i<d->dc[d->level].style.stroke_dasharray.values.size(); i++) {
+ if (i)
+ tmp_style << ",";
+ tmp_style << d->dc[d->level].style.stroke_dasharray.values[i].value;
+ }
+ tmp_style << ";";
+ tmp_style << "stroke-dashoffset:0;";
+ } else {
+ tmp_style << "stroke-dasharray:none;";
+ }
+ tmp_style << "stroke-opacity:1;";
+ }
+ tmp_style << "\" ";
+ if (d->dc[d->level].clip_id)
+ tmp_style << "\n\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\" ";
+
+ d->outsvg += tmp_style.str().c_str();
+}
+
+
+double
+Wmf::_pix_x_to_point(PWMF_CALLBACK_DATA d, double px)
+{
+ double scale = (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0);
+ double tmp;
+ tmp = ((((double) (px - d->dc[d->level].winorg.x))*scale) + d->dc[d->level].vieworg.x) * d->D2PscaleX;
+ tmp -= d->ulCornerOutX; //The WMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner
+ return(tmp);
+}
+
+double
+Wmf::_pix_y_to_point(PWMF_CALLBACK_DATA d, double py)
+{
+ double scale = (d->dc[d->level].ScaleInY ? d->dc[d->level].ScaleInY : 1.0);
+ double tmp;
+ tmp = ((((double) (py - d->dc[d->level].winorg.y))*scale) * d->E2IdirY + d->dc[d->level].vieworg.y) * d->D2PscaleY;
+ tmp -= d->ulCornerOutY; //The WMF boundary rectangle can be anywhere, place its upper left corner in the Inkscape upper left corner
+ return(tmp);
+}
+
+
+double
+Wmf::pix_to_x_point(PWMF_CALLBACK_DATA d, double px, double /*py*/)
+{
+ double x = _pix_x_to_point(d, px);
+ return x;
+}
+
+double
+Wmf::pix_to_y_point(PWMF_CALLBACK_DATA d, double /*px*/, double py)
+{
+ double y = _pix_y_to_point(d, py);
+ return y;
+
+}
+
+double
+Wmf::pix_to_abs_size(PWMF_CALLBACK_DATA d, double px)
+{
+ double ppx = fabs(px * (d->dc[d->level].ScaleInX ? d->dc[d->level].ScaleInX : 1.0) * d->D2PscaleX * current_scale(d));
+ return ppx;
+}
+
+/* returns "x,y" (without the quotes) in inkscape coordinates for a pair of WMF x,y coordinates
+*/
+std::string Wmf::pix_to_xy(PWMF_CALLBACK_DATA d, double x, double y){
+ SVGOStringStream cxform;
+ cxform << pix_to_x_point(d,x,y);
+ cxform << ",";
+ cxform << pix_to_y_point(d,x,y);
+ return(cxform.str());
+}
+
+
+void
+Wmf::select_pen(PWMF_CALLBACK_DATA d, int index)
+{
+ int width;
+ char *record = nullptr;
+ U_PEN up;
+
+ if (index < 0 && index >= d->n_obj){ return; }
+ record = d->wmf_obj[index].record;
+ if(!record){ return; }
+ d->dc[d->level].active_pen = index;
+
+ (void) U_WMRCREATEPENINDIRECT_get(record, &up);
+ width = up.Widthw[0]; // width is stored in the first 16 bits of the 32.
+
+ switch (up.Style & U_PS_STYLE_MASK) {
+ case U_PS_DASH:
+ case U_PS_DOT:
+ case U_PS_DASHDOT:
+ case U_PS_DASHDOTDOT:
+ {
+ int penstyle = (up.Style & U_PS_STYLE_MASK);
+ SPILength spilength(1.f);
+ if (!d->dc[d->level].style.stroke_dasharray.values.empty() &&
+ (d->level == 0 || (d->level > 0 && d->dc[d->level].style.stroke_dasharray !=
+ d->dc[d->level - 1].style.stroke_dasharray)))
+ d->dc[d->level].style.stroke_dasharray.values.clear();
+ if (penstyle==U_PS_DASH || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) {
+ spilength.setDouble(3);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ spilength.setDouble(1);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+ if (penstyle==U_PS_DOT || penstyle==U_PS_DASHDOT || penstyle==U_PS_DASHDOTDOT) {
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+ if (penstyle==U_PS_DASHDOTDOT) {
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ d->dc[d->level].style.stroke_dasharray.values.push_back(spilength);
+ }
+
+ d->dc[d->level].style.stroke_dasharray.set = true;
+ break;
+ }
+
+ case U_PS_SOLID:
+ default:
+ {
+ d->dc[d->level].style.stroke_dasharray.set = false;
+ break;
+ }
+ }
+
+ switch (up.Style & U_PS_ENDCAP_MASK) {
+ case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND; break; }
+ case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; break; }
+ case U_PS_ENDCAP_FLAT:
+ default: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT; break; }
+ }
+
+ switch (up.Style & U_PS_JOIN_MASK) {
+ case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL; break; }
+ case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; break; }
+ case U_PS_JOIN_ROUND:
+ default: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND; break; }
+ }
+
+
+ double pen_width;
+ if (up.Style == U_PS_NULL) {
+ d->dc[d->level].stroke_set = false;
+ pen_width =0.0;
+ } else if (width) {
+ d->dc[d->level].stroke_set = true;
+ int cur_level = d->level;
+ d->level = d->wmf_obj[index].level; // this object may have been defined in some other DC.
+ pen_width = pix_to_abs_size( d, width );
+ d->level = cur_level;
+ } else { // this stroke should always be rendered as 1 pixel wide, independent of zoom level (can that be done in SVG?)
+ d->dc[d->level].stroke_set = true;
+ int cur_level = d->level;
+ d->level = d->wmf_obj[index].level; // this object may have been defined in some other DC.
+ pen_width = pix_to_abs_size( d, 1 );
+ d->level = cur_level;
+ }
+ d->dc[d->level].style.stroke_width.value = pen_width;
+
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(up.Color) );
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(up.Color) );
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(up.Color) );
+ d->dc[d->level].style.stroke.value.color.set( r, g, b );
+}
+
+
+void
+Wmf::select_brush(PWMF_CALLBACK_DATA d, int index)
+{
+ uint8_t iType;
+ char *record;
+ const char *membrush;
+
+ if (index < 0 || index >= d->n_obj)return;
+ record = d->wmf_obj[index].record;
+ if(!record)return;
+ d->dc[d->level].active_brush = index;
+
+ iType = *(uint8_t *)(record + offsetof(U_METARECORD, iType ) );
+ if(iType == U_WMR_CREATEBRUSHINDIRECT){
+ U_WLOGBRUSH lb;
+ (void) U_WMRCREATEBRUSHINDIRECT_get(record, &membrush);
+ memcpy(&lb, membrush, U_SIZE_WLOGBRUSH);
+ if(lb.Style == U_BS_SOLID){
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(lb.Color) );
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(lb.Color) );
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(lb.Color) );
+ d->dc[d->level].style.fill.value.color.set( r, g, b );
+ d->dc[d->level].fill_mode = DRAW_PAINT;
+ d->dc[d->level].fill_set = true;
+ }
+ else if(lb.Style == U_BS_HATCHED){
+ d->dc[d->level].fill_idx = add_hatch(d, lb.Hatch, lb.Color);
+ d->dc[d->level].fill_recidx = index; // used if the hatch needs to be redone due to bkMode, textmode, etc. changes
+ d->dc[d->level].fill_mode = DRAW_PATTERN;
+ d->dc[d->level].fill_set = true;
+ }
+ else if(lb.Style == U_BS_NULL){
+ d->dc[d->level].fill_mode = DRAW_PAINT; // set it to something
+ d->dc[d->level].fill_set = false;
+ }
+ }
+ else if(iType == U_WMR_DIBCREATEPATTERNBRUSH){
+ uint32_t tidx;
+ uint16_t Style;
+ uint16_t cUsage;
+ const char *Bm16h = nullptr; // Pointer to Bitmap16 header (px follows)
+ const char *dib = nullptr; // Pointer to DIB
+ (void) U_WMRDIBCREATEPATTERNBRUSH_get(record, &Style, &cUsage, &Bm16h, &dib);
+ if(dib || Bm16h){
+ if(dib){ tidx = add_dib_image(d, dib, cUsage); }
+ else {
+ U_BITMAP16 Bm16;
+ const char *px;
+ memcpy(&Bm16, Bm16h, U_SIZE_BITMAP16);
+ px = Bm16h + U_SIZE_BITMAP16;
+ tidx = add_bm16_image(d, Bm16, px);
+ }
+ if(tidx == U_WMR_INVALID){ // Problem with the image, for instance, an unsupported bitmap16 type
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor));
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor));
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor));
+ d->dc[d->level].style.fill.value.color.set( r, g, b );
+ d->dc[d->level].fill_mode = DRAW_PAINT;
+ }
+ else {
+ d->dc[d->level].fill_idx = tidx;
+ d->dc[d->level].fill_mode = DRAW_IMAGE;
+ }
+ d->dc[d->level].fill_set = true;
+ }
+ else {
+ g_message("Please send WMF file to developers - select_brush U_WMR_DIBCREATEPATTERNBRUSH not bm16 or dib, not handled");
+ }
+ }
+ else if(iType == U_WMR_CREATEPATTERNBRUSH){
+ uint32_t tidx;
+ int cbPx;
+ U_BITMAP16 Bm16h;
+ const char *px;
+ if(U_WMRCREATEPATTERNBRUSH_get(record, &Bm16h, &cbPx, &px)){
+ tidx = add_bm16_image(d, Bm16h, px);
+ if(tidx == 0xFFFFFFFF){ // Problem with the image, for instance, an unsupported bitmap16 type
+ double r, g, b;
+ r = SP_COLOR_U_TO_F( U_RGBAGetR(d->dc[d->level].textColor));
+ g = SP_COLOR_U_TO_F( U_RGBAGetG(d->dc[d->level].textColor));
+ b = SP_COLOR_U_TO_F( U_RGBAGetB(d->dc[d->level].textColor));
+ d->dc[d->level].style.fill.value.color.set( r, g, b );
+ d->dc[d->level].fill_mode = DRAW_PAINT;
+ }
+ else {
+ d->dc[d->level].fill_idx = tidx;
+ d->dc[d->level].fill_mode = DRAW_IMAGE;
+ }
+ d->dc[d->level].fill_set = true;
+ }
+ }
+}
+
+
+void
+Wmf::select_font(PWMF_CALLBACK_DATA d, int index)
+{
+ char *record = nullptr;
+ const char *memfont;
+ const char *facename;
+ U_FONT font;
+
+ if (index < 0 || index >= d->n_obj)return;
+ record = d->wmf_obj[index].record;
+ if (!record)return;
+ d->dc[d->level].active_font = index;
+
+
+ (void) U_WMRCREATEFONTINDIRECT_get(record, &memfont);
+ memcpy(&font,memfont,U_SIZE_FONT_CORE); //make sure it is in a properly aligned structure before touching it
+ facename = memfont + U_SIZE_FONT_CORE;
+
+ /* The logfont information always starts with a U_LOGFONT structure but the U_WMRCREATEFONTINDIRECT
+ is defined as U_LOGFONT_PANOSE so it can handle one of those if that is actually present. Currently only logfont
+ is supported, and the remainder, it it really is a U_LOGFONT_PANOSE record, is ignored
+ */
+ int cur_level = d->level;
+ d->level = d->wmf_obj[index].level;
+ double font_size = pix_to_abs_size( d, font.Height );
+ /* snap the font_size to the nearest 1/32nd of a point.
+ (The size is converted from Pixels to points, snapped, and converted back.)
+ See the notes where d->D2Pscale[XY] are set for the reason why.
+ Typically this will set the font to the desired exact size. If some peculiar size
+ was intended this will, at worst, make it .03125 off, which is unlikely to be a problem. */
+ font_size = round(20.0 * 0.8 * font_size)/(20.0 * 0.8);
+ d->level = cur_level;
+ d->dc[d->level].style.font_size.computed = font_size;
+ d->dc[d->level].style.font_weight.value =
+ font.Weight == U_FW_THIN ? SP_CSS_FONT_WEIGHT_100 :
+ font.Weight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_200 :
+ font.Weight == U_FW_LIGHT ? SP_CSS_FONT_WEIGHT_300 :
+ font.Weight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_400 :
+ font.Weight == U_FW_MEDIUM ? SP_CSS_FONT_WEIGHT_500 :
+ font.Weight == U_FW_SEMIBOLD ? SP_CSS_FONT_WEIGHT_600 :
+ font.Weight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_700 :
+ font.Weight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_800 :
+ font.Weight == U_FW_HEAVY ? SP_CSS_FONT_WEIGHT_900 :
+ font.Weight == U_FW_NORMAL ? SP_CSS_FONT_WEIGHT_NORMAL :
+ font.Weight == U_FW_BOLD ? SP_CSS_FONT_WEIGHT_BOLD :
+ font.Weight == U_FW_EXTRALIGHT ? SP_CSS_FONT_WEIGHT_LIGHTER :
+ font.Weight == U_FW_EXTRABOLD ? SP_CSS_FONT_WEIGHT_BOLDER :
+ SP_CSS_FONT_WEIGHT_NORMAL;
+ d->dc[d->level].style.font_style.value = (font.Italic ? SP_CSS_FONT_STYLE_ITALIC : SP_CSS_FONT_STYLE_NORMAL);
+ d->dc[d->level].style.text_decoration_line.underline = font.Underline;
+ d->dc[d->level].style.text_decoration_line.line_through = font.StrikeOut;
+ d->dc[d->level].style.text_decoration_line.set = true;
+ d->dc[d->level].style.text_decoration_line.inherit = false;
+
+ // malformed WMF with empty filename may exist, ignore font change if encountered
+ if(d->dc[d->level].font_name)free(d->dc[d->level].font_name);
+ if(*facename){
+ d->dc[d->level].font_name = strdup(facename);
+ }
+ else { // Malformed WMF might specify an empty font name
+ d->dc[d->level].font_name = strdup("Arial"); // Default font, WMF spec says device can pick whatever it wants
+ }
+ d->dc[d->level].style.baseline_shift.value = round((double)((font.Escapement + 3600) % 3600) / 10.0); // use baseline_shift instead of text_transform to avoid overflow
+}
+
+/* Find the first free hole where an object may be stored.
+ If there are not any return -1. This is a big error, possibly from a corrupt WMF file.
+*/
+int Wmf::insertable_object(PWMF_CALLBACK_DATA d)
+{
+ int index = d->low_water; // Start looking from here, it may already have been filled
+ while(index < d->n_obj && d->wmf_obj[index].record != nullptr){ index++; }
+ if(index >= d->n_obj)return(-1); // this is a big problem, percolate it back up so the program can get out of this gracefully
+ d->low_water = index; // Could probably be index+1
+ return(index);
+}
+
+void
+Wmf::delete_object(PWMF_CALLBACK_DATA d, int index)
+{
+ if (index >= 0 && index < d->n_obj) {
+ // If the active object is deleted set default draw values
+ if(index == d->dc[d->level].active_pen){ // Use default pen: solid, black, 1 pixel wide
+ d->dc[d->level].active_pen = -1;
+ d->dc[d->level].style.stroke_dasharray.set = false;
+ d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; // U_PS_ENDCAP_SQUARE
+ d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; // U_PS_JOIN_MITER;
+ d->dc[d->level].stroke_set = true;
+ d->dc[d->level].style.stroke_width.value = 1.0;
+ d->dc[d->level].style.stroke.value.color.set( 0, 0, 0 );
+ }
+ else if(index == d->dc[d->level].active_brush){
+ d->dc[d->level].active_brush = -1;
+ d->dc[d->level].fill_set = false;
+ }
+ else if(index == d->dc[d->level].active_font){
+ d->dc[d->level].active_font = -1;
+ if(d->dc[d->level].font_name){ free(d->dc[d->level].font_name);}
+ d->dc[d->level].font_name = strdup("Arial"); // Default font, WMF spec says device can pick whatever it wants
+ d->dc[d->level].style.font_size.computed = 16.0;
+ d->dc[d->level].style.font_weight.value = SP_CSS_FONT_WEIGHT_400;
+ d->dc[d->level].style.font_style.value = SP_CSS_FONT_STYLE_NORMAL;
+ d->dc[d->level].style.text_decoration_line.underline = false;
+ d->dc[d->level].style.text_decoration_line.line_through = false;
+ d->dc[d->level].style.baseline_shift.value = 0;
+ }
+
+
+ d->wmf_obj[index].type = 0;
+// We are keeping a copy of the WMR rather than just a structure. Currently that is not necessary as the entire
+// WMF is read in at once and is stored in a big malloc. However, in past versions it was handled
+// record by record, and we might need to do that again at some point in the future if we start running into WMF
+// files too big to fit into memory.
+ if (d->wmf_obj[index].record)
+ free(d->wmf_obj[index].record);
+ d->wmf_obj[index].record = nullptr;
+ if(index < d->low_water)d->low_water = index;
+ }
+}
+
+
+// returns the new index, or -1 on error.
+int Wmf::insert_object(PWMF_CALLBACK_DATA d, int type, const char *record)
+{
+ int index = insertable_object(d);
+ if(index>=0){
+ d->wmf_obj[index].type = type;
+ d->wmf_obj[index].level = d->level;
+ d->wmf_obj[index].record = wmr_dup(record);
+ }
+ return(index);
+}
+
+
+/**
+ \fn create a UTF-32LE buffer and fill it with UNICODE unknown character
+ \param count number of copies of the Unicode unknown character to fill with
+*/
+uint32_t *Wmf::unknown_chars(size_t count){
+ uint32_t *res = (uint32_t *) malloc(sizeof(uint32_t) * (count + 1));
+ if(!res)throw "Inkscape fatal memory allocation error - cannot continue";
+ for(uint32_t i=0; i<count; i++){ res[i] = 0xFFFD; }
+ res[count]=0;
+ return res;
+}
+
+/**
+ \brief store SVG for an image given the pixmap and various coordinate information
+ \param d
+ \param dib packed DIB in memory
+ \param dx (double) destination x in inkscape pixels
+ \param dy (double) destination y in inkscape pixels
+ \param dw (double) destination width in inkscape pixels
+ \param dh (double) destination height in inkscape pixels
+ \param sx (int) source x in src image pixels
+ \param sy (int) source y in src image pixels
+ \param iUsage
+*/
+void Wmf::common_dib_to_image(PWMF_CALLBACK_DATA d, const char *dib,
+ double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh, uint32_t iUsage){
+
+ SVGOStringStream tmp_image;
+ int dibparams = U_BI_UNKNOWN; // type of image not yet determined
+
+ tmp_image << "\n\t <image\n";
+ if (d->dc[d->level].clip_id){
+ tmp_image << "\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n";
+ }
+ tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n ";
+
+ MEMPNG mempng; // PNG in memory comes back in this
+ mempng.buffer = nullptr;
+
+ char *rgba_px = nullptr; // RGBA pixels
+ char *sub_px = nullptr; // RGBA pixels, subarray
+ const char *px = nullptr; // DIB pixels
+ const U_RGBQUAD *ct = nullptr; // color table
+ uint32_t numCt;
+ int32_t width, height, colortype, invert; // if needed these values will be set in wget_DIB_params
+ if(iUsage == U_DIB_RGB_COLORS){
+ // next call returns pointers and values, but allocates no memory
+ dibparams = wget_DIB_params(dib, &px, &ct, &numCt, &width, &height, &colortype, &invert);
+ if(dibparams == U_BI_RGB){
+ if(sw == 0 || sh == 0){
+ sw = width;
+ sh = height;
+ }
+ if(!DIB_to_RGBA(
+ px, // DIB pixel array
+ ct, // DIB color table
+ numCt, // DIB color table number of entries
+ &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free.
+ width, // Width of pixel array
+ height, // Height of pixel array
+ colortype, // DIB BitCount Enumeration
+ numCt, // Color table used if not 0
+ invert // If DIB rows are in opposite order from RGBA rows
+ )){
+ sub_px = RGBA_to_RGBA( // returns either a subset (side effect: frees rgba_px) or NULL (for subset == entire image)
+ rgba_px, // full pixel array from DIB
+ width, // Width of pixel array
+ height, // Height of pixel array
+ sx,sy, // starting point in pixel array
+ &sw,&sh // columns/rows to extract from the pixel array (output array size)
+ );
+
+ if(!sub_px)sub_px=rgba_px;
+ toPNG( // Get the image from the RGBA px into mempng
+ &mempng,
+ sw, sh, // size of the extracted pixel array
+ sub_px
+ );
+ free(sub_px);
+ }
+ }
+ }
+
+ gchar *base64String=nullptr;
+ if(dibparams == U_BI_JPEG){ // image was binary jpg in source file
+ tmp_image << " xlink:href=\"data:image/jpeg;base64,";
+ base64String = g_base64_encode((guchar*) px, numCt );
+ }
+ else if(dibparams==U_BI_PNG){ // image was binary png in source file
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = g_base64_encode((guchar*) px, numCt );
+ }
+ else if(mempng.buffer){ // image was DIB in source file, converted to png in this routine
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size );
+ free(mempng.buffer);
+ }
+ else { // unknown or unsupported image type or failed conversion, insert the common bad image picture
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = bad_image_png();
+ }
+ tmp_image << base64String;
+ g_free(base64String);
+
+ tmp_image << "\"\n height=\"" << dh << "\"\n width=\"" << dw << "\"\n";
+ tmp_image << " transform=" << current_matrix(d, 0.0, 0.0, 0); // returns an identity matrix, no offsets.
+ tmp_image << " preserveAspectRatio=\"none\"\n";
+ tmp_image << "/> \n";
+
+ d->outsvg += tmp_image.str().c_str();
+ d->path = "";
+}
+
+/**
+ \brief store SVG for an image given the pixmap and various coordinate information
+ \param d
+ \param Bm16 core Bitmap16 header
+ \param px pointer to Bitmap16 image data
+ \param dx (double) destination x in inkscape pixels
+ \param dy (double) destination y in inkscape pixels
+ \param dw (double) destination width in inkscape pixels
+ \param dh (double) destination height in inkscape pixels
+ \param sx (int) source x in src image pixels
+ \param sy (int) source y in src image pixels
+ \param iUsage
+*/
+void Wmf::common_bm16_to_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px,
+ double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh){
+
+ SVGOStringStream tmp_image;
+
+ tmp_image << "\n\t <image\n";
+ if (d->dc[d->level].clip_id){
+ tmp_image << "\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n";
+ }
+ tmp_image << " y=\"" << dy << "\"\n x=\"" << dx <<"\"\n ";
+
+ MEMPNG mempng; // PNG in memory comes back in this
+ mempng.buffer = nullptr;
+
+ char *rgba_px = nullptr; // RGBA pixels
+ char *sub_px = nullptr; // RGBA pixels, subarray
+ const U_RGBQUAD *ct = nullptr; // color table
+ int32_t width, height, colortype, numCt, invert;
+
+ numCt = 0;
+ width = Bm16.Width; // bitmap width in pixels.
+ height = Bm16.Height; // bitmap height in scan lines.
+ colortype = Bm16.BitsPixel; // seems to be BitCount Enumeration
+ invert = 0;
+
+ if(sw == 0 || sh == 0){
+ sw = width;
+ sh = height;
+ }
+
+ if(colortype < 16)return; // these would need a colortable if they were a dib, no idea what bm16 is supposed to do instead.
+
+ if(!DIB_to_RGBA(// This is not really a dib, but close enough so that it still works.
+ px, // DIB pixel array
+ ct, // DIB color table (always NULL here)
+ numCt, // DIB color table number of entries (always 0)
+ &rgba_px, // U_RGBA pixel array (32 bits), created by this routine, caller must free.
+ width, // Width of pixel array
+ height, // Height of pixel array
+ colortype, // DIB BitCount Enumeration
+ numCt, // Color table used if not 0
+ invert // If DIB rows are in opposite order from RGBA rows
+ )){
+ sub_px = RGBA_to_RGBA( // returns either a subset (side effect: frees rgba_px) or NULL (for subset == entire image)
+ rgba_px, // full pixel array from DIB
+ width, // Width of pixel array
+ height, // Height of pixel array
+ sx,sy, // starting point in pixel array
+ &sw,&sh // columns/rows to extract from the pixel array (output array size)
+ );
+
+ if(!sub_px)sub_px=rgba_px;
+ toPNG( // Get the image from the RGBA px into mempng
+ &mempng,
+ sw, sh, // size of the extracted pixel array
+ sub_px
+ );
+ free(sub_px);
+ }
+
+ gchar *base64String=nullptr;
+ if(mempng.buffer){ // image was Bm16 in source file, converted to png in this routine
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = g_base64_encode((guchar*) mempng.buffer, mempng.size );
+ free(mempng.buffer);
+ }
+ else { // unknown or unsupported image type or failed conversion, insert the common bad image picture
+ tmp_image << " xlink:href=\"data:image/png;base64,";
+ base64String = bad_image_png();
+ }
+ tmp_image << base64String;
+ g_free(base64String);
+
+ tmp_image << "\"\n height=\"" << dh << "\"\n width=\"" << dw << "\"\n";
+ tmp_image << " transform=" << current_matrix(d, 0.0, 0.0, 0); // returns an identity matrix, no offsets.
+ tmp_image << " preserveAspectRatio=\"none\"\n";
+ tmp_image << "/> \n";
+
+ d->outsvg += tmp_image.str().c_str();
+ d->path = "";
+}
+
+/**
+ \fn myMetaFileProc(char *contents, unsigned int length, PWMF_CALLBACK_DATA lpData)
+ \returns 1 on success, 0 on error
+ \param contents binary contents of an WMF file
+ \param length length in bytes of contents
+ \param d Inkscape data structures returned by this call
+*/
+//THis was a callback, just build it into a normal function
+int Wmf::myMetaFileProc(const char *contents, unsigned int length, PWMF_CALLBACK_DATA d)
+{
+ uint32_t off=0;
+ uint32_t wmr_mask;
+ int OK =1;
+ int file_status=1;
+ TCHUNK_SPECS tsp;
+ uint8_t iType;
+ int nSize; // size of the current record, in bytes, or an error value if <=0
+ const char *blimit = contents + length; // 1 byte past the end of the last record
+
+ /* variables used to retrieve data from WMF records */
+ uint16_t utmp16;
+ U_POINT16 pt16; // any point
+ U_RECT16 rc; // any rectangle, usually a bounding rectangle
+ U_POINT16 Dst; // Destination coordinates
+ U_POINT16 cDst; // Destination w,h, if different from Src
+ U_POINT16 Src; // Source coordinates
+ U_POINT16 cSrc; // Source w,h, if different from Dst
+ U_POINT16 cwh; // w,h, if Src and Dst use the same values
+ uint16_t cUsage; // colorusage enumeration
+ uint32_t dwRop3; // raster operations, these are only barely supported here
+ const char *dib; // DIB style image structure
+ U_BITMAP16 Bm16; // Bitmap16 style image structure
+ const char *px; // Image for Bm16
+ uint16_t cPts; // number of points in the next variable
+ const char *points; // any list of U_POINT16, may not be aligned
+ int16_t tlen; // length of returned text, in bytes
+ const char *text; // returned text, Latin1 encoded
+ uint16_t Opts;
+ const int16_t *dx; // character spacing for one text mode, inkscape ignores this
+ double left, right, top, bottom; // values used, because a bounding rect can have values reversed L<->R, T<->B
+
+ uint16_t tbkMode = U_TRANSPARENT; // holds proposed change to bkMode, if text is involved saving these to the DC must wait until the text is written
+ U_COLORREF tbkColor = U_RGB(255, 255, 255); // holds proposed change to bkColor
+
+ // code for end user debugging
+ int wDbgRecord=0;
+ int wDbgComment=0;
+ int wDbgFinal=0;
+ char const* wDbgString = getenv( "INKSCAPE_DBG_WMF" );
+ if ( wDbgString != nullptr ) {
+ if(strstr(wDbgString,"RECORD")){ wDbgRecord = 1; }
+ if(strstr(wDbgString,"COMMENT")){ wDbgComment = 1; }
+ if(strstr(wDbgString,"FINAL")){ wDbgFinal = 1; }
+ }
+
+ /* initialize the tsp for text reassembly */
+ tsp.string = nullptr;
+ tsp.ori = 0.0; /* degrees */
+ tsp.fs = 12.0; /* font size */
+ tsp.x = 0.0;
+ tsp.y = 0.0;
+ tsp.boff = 0.0; /* offset to baseline from LL corner of bounding rectangle, changes with fs and taln*/
+ tsp.vadvance = 0.0; /* meaningful only when a complex contains two or more lines */
+ tsp.taln = ALILEFT + ALIBASE;
+ tsp.ldir = LDIR_LR;
+ tsp.spaces = 0; // this field is only used for debugging
+ tsp.color.Red = 0; /* RGB Black */
+ tsp.color.Green = 0; /* RGB Black */
+ tsp.color.Blue = 0; /* RGB Black */
+ tsp.color.Reserved = 0; /* not used */
+ tsp.italics = 0;
+ tsp.weight = 80;
+ tsp.decoration = TXTDECOR_NONE;
+ tsp.condensed = 100;
+ tsp.co = 0;
+ tsp.fi_idx = -1; /* set to an invalid */
+ tsp.rt_tidx = -1; /* set to an invalid */
+
+ SVGOStringStream dbg_str;
+
+ /* There is very little information in WMF headers, get what is there. In many cases pretty much everything will have to
+ default. If there is no placeable header we know pretty much nothing about the size of the page, in which case
+ assume that it is 1440 WMF pixels/inch and make the page A4 landscape. That is almost certainly the wrong page size
+ but it has to be set to something, and nothing horrible happens if the drawing goes off the page. */
+ {
+
+ U_WMRPLACEABLE Placeable;
+ U_WMRHEADER Header;
+ off = 0;
+ nSize = wmfheader_get(contents, blimit, &Placeable, &Header);
+ if (!nSize) {
+ return(0);
+ }
+ if(!Header.nObjects){ Header.nObjects = 256; }// there _may_ be WMF files with no objects, more likely it is corrupt. Try to use it anyway.
+ d->n_obj = Header.nObjects;
+ d->wmf_obj = new WMF_OBJECT[d->n_obj];
+ d->low_water = 0; // completely empty at this point, so start searches at 0
+
+ // Init the new wmf_obj list elements to null, provided the
+ // dynamic allocation succeeded.
+ if ( d->wmf_obj != nullptr )
+ {
+ for( int i=0; i < d->n_obj; ++i )
+ d->wmf_obj[i].record = nullptr;
+ } //if
+
+ if(!Placeable.Inch){ Placeable.Inch= 1440; }
+ if(!Placeable.Dst.right && !Placeable.Dst.left){ // no page size has been supplied
+ // This is gross, scan forward looking for a SETWINDOWEXT record, use the first one found to
+ // define the page size
+ int hold_nSize = off = nSize;
+ Placeable.Dst.left = 0;
+ Placeable.Dst.top = 0;
+ while(OK){
+ nSize = U_WMRRECSAFE_get(contents + off, blimit);
+ if(nSize){
+ iType = *(uint8_t *)(contents + off + offsetof(U_METARECORD, iType ) );
+ if(iType == U_WMR_SETWINDOWEXT){
+ OK=0;
+ (void) U_WMRSETWINDOWEXT_get(contents + off, &Dst);
+ Placeable.Dst.right = Dst.x;
+ Placeable.Dst.bottom = Dst.y;
+ }
+ else if(iType == U_WMR_EOF){
+ OK=0;
+ // Really messed up WMF, have to set the page to something, make it A4 horizontal
+ Placeable.Dst.right = round(((double) Placeable.Inch) * 297.0/25.4);
+ Placeable.Dst.bottom = round(((double) Placeable.Inch) * 210.0/25.4);
+ }
+ else {
+ off += nSize;
+ }
+ }
+ else {
+ return(0);
+ }
+ }
+ off=0;
+ nSize = hold_nSize;
+ OK=1;
+ }
+
+ // drawing size in WMF pixels
+ d->PixelsInX = Placeable.Dst.right - Placeable.Dst.left + 1;
+ d->PixelsInY = Placeable.Dst.bottom - Placeable.Dst.top + 1;
+
+ /*
+ Set values for Window and ViewPort extents to 0 - not defined yet.
+ */
+ d->dc[d->level].sizeView.x = d->dc[d->level].sizeWnd.x = 0;
+ d->dc[d->level].sizeView.y = d->dc[d->level].sizeWnd.y = 0;
+
+ /* Upper left corner in device units, usually both 0, but not always.
+ If a placeable header is used, and later a windoworg/windowext are found, then
+ the placeable information will be ignored.
+ */
+ d->ulCornerInX = Placeable.Dst.left;
+ d->ulCornerInY = Placeable.Dst.top;
+
+ d->E2IdirY = 1.0; // assume MM_ANISOTROPIC, if not, this will be changed later
+ d->D2PscaleX = d->D2PscaleY = Inkscape::Util::Quantity::convert(1, "in", "px")/(double) Placeable.Inch;
+ trinfo_load_qe(d->tri, d->D2PscaleX); /* quantization error that will affect text positions */
+
+ // drawing size in Inkscape pixels
+ d->PixelsOutX = d->PixelsInX * d->D2PscaleX;
+ d->PixelsOutY = d->PixelsInY * d->D2PscaleY;
+
+ // Upper left corner in Inkscape units
+ d->ulCornerOutX = d->ulCornerInX * d->D2PscaleX;
+ d->ulCornerOutY = d->ulCornerInY * d->E2IdirY * d->D2PscaleY;
+
+ d->dc[0].style.stroke_width.value = pix_to_abs_size( d, 1 ); // This could not be set until the size of the WMF was known
+ dbg_str << "<!-- U_WMR_HEADER -->\n";
+
+ d->outdef += "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
+
+ SVGOStringStream tmp_outdef;
+ tmp_outdef << "<svg\n";
+ tmp_outdef << " xmlns:svg=\"http://www.w3.org/2000/svg\"\n";
+ tmp_outdef << " xmlns=\"http://www.w3.org/2000/svg\"\n";
+ tmp_outdef << " xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n";
+ tmp_outdef << " xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\"\n"; // needed for sodipodi:role
+ tmp_outdef << " version=\"1.0\"\n";
+
+ tmp_outdef <<
+ " width=\"" << Inkscape::Util::Quantity::convert(d->PixelsOutX, "px", "mm") << "mm\"\n" <<
+ " height=\"" << Inkscape::Util::Quantity::convert(d->PixelsOutY, "px", "mm") << "mm\">\n";
+ d->outdef += tmp_outdef.str().c_str();
+ d->outdef += "<defs>"; // temporary end of header
+
+ // d->defs holds any defines which are read in.
+
+
+ }
+
+
+
+ while(OK){
+ if (off>=length) {
+ return(0); //normally should exit from while after WMREOF sets OK to false.
+ }
+ contents += nSize; // pointer to the start of the next record
+ off += nSize; // offset from beginning of buffer to the start of the next record
+
+ /* Currently this is a weaker check than for EMF, it only checks the size of the constant part
+ of the record */
+ nSize = U_WMRRECSAFE_get(contents, blimit);
+ if(!nSize) {
+ file_status = 0;
+ break;
+ }
+
+ iType = *(uint8_t *)(contents + offsetof(U_METARECORD, iType ) );
+
+ wmr_mask = U_wmr_properties(iType);
+ if (wmr_mask == U_WMR_INVALID) {
+ file_status = 0;
+ break;
+ }
+// At run time define environment variable INKSCAPE_DBG_WMF to include string RECORD.
+// Users may employ this to track down toxic records
+ if(wDbgRecord){
+ std::cout << "record type: " << iType << " name " << U_wmr_names(iType) << " length: " << nSize << " offset: " << off <<std::endl;
+ }
+
+ SVGOStringStream tmp_path;
+ SVGOStringStream tmp_str;
+
+/* Uncomment the following to track down text problems */
+//std::cout << "tri->dirty:"<< d->tri->dirty << " wmr_mask: " << std::hex << wmr_mask << std::dec << std::endl;
+
+ // incompatible change to text drawing detected (color or background change) forces out existing text
+ // OR
+ // next record is valid type and forces pending text to be drawn immediately
+ if ((d->dc[d->level].dirty & DIRTY_TEXT) || ((wmr_mask != U_WMR_INVALID) && (wmr_mask & U_DRAW_TEXT) && d->tri->dirty)){
+ TR_layout_analyze(d->tri);
+ if (d->dc[d->level].clip_id){
+ SVGOStringStream tmp_clip;
+ tmp_clip << "\n<g\n\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n>";
+ d->outsvg += tmp_clip.str().c_str();
+ }
+ TR_layout_2_svg(d->tri);
+ SVGOStringStream ts;
+ ts << d->tri->out;
+ d->outsvg += ts.str().c_str();
+ d->tri = trinfo_clear(d->tri);
+ if (d->dc[d->level].clip_id){
+ d->outsvg += "\n</g>\n";
+ }
+ }
+ if(d->dc[d->level].dirty){ //Apply the delayed background changes, clear the flag
+ d->dc[d->level].bkMode = tbkMode;
+ memcpy(&(d->dc[d->level].bkColor),&tbkColor, sizeof(U_COLORREF));
+
+ if(d->dc[d->level].dirty & DIRTY_TEXT){
+ // U_COLORREF and TRCOLORREF are exactly the same in memory, but the compiler needs some convincing...
+ if(tbkMode == U_TRANSPARENT){ (void) trinfo_load_bk(d->tri, BKCLR_NONE, *(TRCOLORREF *) &tbkColor); }
+ else { (void) trinfo_load_bk(d->tri, BKCLR_LINE, *(TRCOLORREF *) &tbkColor); } // Opaque
+ }
+
+ /* It is possible to have a series of EMF records that would result in
+ the following creating hash patterns which are never used. For instance, if
+ there were a series of records that changed the background color but did nothing
+ else.
+ */
+ if((d->dc[d->level].fill_mode == DRAW_PATTERN) && (d->dc[d->level].dirty & DIRTY_FILL)){
+ select_brush(d, d->dc[d->level].fill_recidx);
+ }
+
+ d->dc[d->level].dirty = 0;
+ }
+
+//std::cout << "BEFORE DRAW logic d->mask: " << std::hex << d->mask << " wmr_mask: " << wmr_mask << std::dec << std::endl;
+/*
+std::cout << "BEFORE DRAW"
+ << " test0 " << ( d->mask & U_DRAW_VISIBLE)
+ << " test1 " << ( d->mask & U_DRAW_FORCE)
+ << " test2 " << (wmr_mask & U_DRAW_ALTERS)
+ << " test2.5 " << ((d->mask & U_DRAW_NOFILL) != (wmr_mask & U_DRAW_NOFILL) )
+ << " test3 " << (wmr_mask & U_DRAW_VISIBLE)
+ << " test4 " << !(d->mask & U_DRAW_ONLYTO)
+ << " test5 " << ((d->mask & U_DRAW_ONLYTO) && !(wmr_mask & U_DRAW_ONLYTO) )
+ << std::endl;
+*/
+ /* spurious moveto records should not affect the drawing. However, they set the NOFILL
+ bit and that messes up the logic about when to emit a path. So prune out any
+ stray moveto records. That is those which were never followed by a lineto.
+ */
+ if((d->mask & U_DRAW_NOFILL) && !(d->mask & U_DRAW_VISIBLE) &&
+ !(wmr_mask & U_DRAW_ONLYTO) && (wmr_mask & U_DRAW_VISIBLE)){
+ d->mask ^= U_DRAW_NOFILL;
+ }
+
+ if(
+ (wmr_mask != U_WMR_INVALID) && // next record is valid type
+ (d->mask & U_DRAW_VISIBLE) && // This record is drawable
+ (
+ (d->mask & U_DRAW_FORCE) || // This draw is forced by STROKE/FILL/STROKEANDFILL PATH
+ (wmr_mask & U_DRAW_ALTERS) || // Next record would alter the drawing environment in some way
+ ((d->mask & U_DRAW_NOFILL) != (wmr_mask & U_DRAW_NOFILL)) || // Fill<->!Fill requires a draw between
+ ( (wmr_mask & U_DRAW_VISIBLE) && // Next record is visible...
+ (
+ ( !(d->mask & U_DRAW_ONLYTO) ) || // Non *TO records cannot be followed by any Visible
+ ((d->mask & U_DRAW_ONLYTO) && !(wmr_mask & U_DRAW_ONLYTO) )// *TO records can only be followed by other *TO records
+ )
+ )
+ )
+ ){
+// std::cout << "PATH DRAW at TOP <<+++++++++++++++++++++++++++++++++++++" << std::endl;
+ if(!(d->path.empty())){
+ d->outsvg += " <path "; // this is the ONLY place <path should be used!!!!
+ output_style(d);
+ d->outsvg += "\n\t";
+ d->outsvg += "\n\td=\""; // this is the ONLY place d=" should be used!!!!
+ d->outsvg += d->path;
+ d->outsvg += " \" /> \n";
+ d->path = ""; //reset the path
+ }
+ // reset the flags
+ d->mask = 0;
+ d->drawtype = 0;
+ }
+// std::cout << "AFTER DRAW logic d->mask: " << std::hex << d->mask << " wmr_mask: " << wmr_mask << std::dec << std::endl;
+ switch (iType)
+ {
+ case U_WMR_EOF:
+ {
+ dbg_str << "<!-- U_WMR_EOF -->\n";
+
+ d->outsvg = d->outdef + d->defs + "\n</defs>\n\n" + d->outsvg + "</svg>\n";
+ OK=0;
+ break;
+ }
+ case U_WMR_SETBKCOLOR:
+ {
+ dbg_str << "<!-- U_WMR_SETBKCOLOR -->\n";
+ nSize = U_WMRSETBKCOLOR_get(contents, &tbkColor);
+ if(memcmp(&tbkColor, &(d->dc[d->level].bkColor), sizeof(U_COLORREF))){
+ d->dc[d->level].dirty |= DIRTY_TEXT;
+ if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; }
+ tbkMode = d->dc[d->level].bkMode;
+ }
+ break;
+ }
+ case U_WMR_SETBKMODE:{
+ dbg_str << "<!-- U_WMR_SETBKMODE -->\n";
+ nSize = U_WMRSETBKMODE_get(contents, &tbkMode);
+ if(tbkMode != d->dc[d->level].bkMode){
+ d->dc[d->level].dirty |= DIRTY_TEXT;
+ if(tbkMode != d->dc[d->level].bkMode){
+ if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; }
+ }
+ memcpy(&tbkColor,&(d->dc[d->level].bkColor),sizeof(U_COLORREF));
+ }
+ break;
+ }
+ case U_WMR_SETMAPMODE:
+ {
+ dbg_str << "<!-- U_WMR_SETMAPMODE -->\n";
+ nSize = U_WMRSETMAPMODE_get(contents, &utmp16);
+ switch (utmp16){
+ case U_MM_TEXT:
+ default:
+ // Use all values from the header.
+ break;
+ /* For all of the following the indicated scale this will be encoded in WindowExtEx/ViewportExtex
+ and show up in ScaleIn[XY]
+ */
+ case U_MM_LOMETRIC: // 1 LU = 0.1 mm,
+ case U_MM_HIMETRIC: // 1 LU = 0.01 mm
+ case U_MM_LOENGLISH: // 1 LU = 0.1 in
+ case U_MM_HIENGLISH: // 1 LU = 0.01 in
+ case U_MM_TWIPS: // 1 LU = 1/1440 in
+ d->E2IdirY = -1.0;
+ // Use d->D2Pscale[XY] values from the header.
+ break;
+ case U_MM_ISOTROPIC: // ScaleIn[XY] should be set elsewhere by SETVIEWPORTEXTEX and SETWINDOWEXTEX
+ case U_MM_ANISOTROPIC:
+ break;
+ }
+ break;
+ }
+ case U_WMR_SETROP2:
+ {
+ dbg_str << "<!-- U_WMR_SETROP2 -->\n";
+ nSize = U_WMRSETROP2_get(contents, &utmp16);
+ d->dwRop2 = utmp16;
+ break;
+ }
+ case U_WMR_SETRELABS: dbg_str << "<!-- U_WMR_SETRELABS -->\n"; break;
+ case U_WMR_SETPOLYFILLMODE:
+ {
+ dbg_str << "<!-- U_WMR_SETPOLYFILLMODE -->\n";
+ nSize = U_WMRSETPOLYFILLMODE_get(contents, &utmp16);
+ d->dc[d->level].style.fill_rule.value =
+ (utmp16 == U_ALTERNATE ? SP_WIND_RULE_NONZERO
+ : utmp16 == U_WINDING ? SP_WIND_RULE_INTERSECT : SP_WIND_RULE_NONZERO);
+ break;
+ }
+ case U_WMR_SETSTRETCHBLTMODE:
+ {
+ dbg_str << "<!-- U_WMR_SETSTRETCHBLTMODE -->\n";
+ nSize = U_WMRSETSTRETCHBLTMODE_get(contents, &utmp16);
+ BLTmode = utmp16;
+ break;
+ }
+ case U_WMR_SETTEXTCHAREXTRA: dbg_str << "<!-- U_WMR_SETTEXTCHAREXTRA -->\n"; break;
+ case U_WMR_SETTEXTCOLOR:
+ {
+ dbg_str << "<!-- U_WMR_SETTEXTCOLOR -->\n";
+ nSize = U_WMRSETTEXTCOLOR_get(contents, &(d->dc[d->level].textColor));
+ if(tbkMode != d->dc[d->level].bkMode){
+ if(d->dc[d->level].fill_mode == DRAW_PATTERN){ d->dc[d->level].dirty |= DIRTY_FILL; }
+ }
+ // not text_dirty, because multicolored complex text is supported in libTERE
+ break;
+ }
+ case U_WMR_SETTEXTJUSTIFICATION: dbg_str << "<!-- U_WMR_SETTEXTJUSTIFICATION -->\n"; break;
+ case U_WMR_SETWINDOWORG:
+ {
+ dbg_str << "<!-- U_WMR_SETWINDOWORG -->\n";
+ nSize = U_WMRSETWINDOWORG_get(contents, &d->dc[d->level].winorg);
+ d->ulCornerOutX = 0.0; // In the examples seen to date if this record is used with a placeable header, that header is ignored
+ d->ulCornerOutY = 0.0;
+ break;
+ }
+ case U_WMR_SETWINDOWEXT:
+ {
+ dbg_str << "<!-- U_WMR_SETWINDOWEXT -->\n";
+
+ nSize = U_WMRSETWINDOWEXT_get(contents, &d->dc[d->level].sizeWnd);
+
+ if (!d->dc[d->level].sizeWnd.x || !d->dc[d->level].sizeWnd.y) {
+ d->dc[d->level].sizeWnd = d->dc[d->level].sizeView;
+ if (!d->dc[d->level].sizeWnd.x || !d->dc[d->level].sizeWnd.y) {
+ d->dc[d->level].sizeWnd.x = d->PixelsOutX;
+ d->dc[d->level].sizeWnd.y = d->PixelsOutY;
+ }
+ }
+ else {
+ /* There are a lot WMF files in circulation with the x,y values in the setwindowext reversed. If this is detected, swap them.
+ There is a remote possibility that the strange scaling this implies was intended, and those will be rendered incorrectly */
+ double Ox = d->PixelsOutX;
+ double Oy = d->PixelsOutY;
+ double Wx = d->dc[d->level].sizeWnd.x;
+ double Wy = d->dc[d->level].sizeWnd.y;
+ if(Wx != Wy && Geom::are_near(Ox/Wy, Oy/Wx, 1.01/MIN(Wx,Wy)) ){
+ int tmp;
+ tmp = d->dc[d->level].sizeWnd.x;
+ d->dc[d->level].sizeWnd.x = d->dc[d->level].sizeWnd.y;
+ d->dc[d->level].sizeWnd.y = tmp;
+ }
+ }
+
+ if (!d->dc[d->level].sizeView.x || !d->dc[d->level].sizeView.y) {
+ /* Previously it used sizeWnd, but that always resulted in scale = 1 if no viewport ever appeared, and in most files, it did not */
+ d->dc[d->level].sizeView.x = d->PixelsInX - 1;
+ d->dc[d->level].sizeView.y = d->PixelsInY - 1;
+ }
+
+ /* scales logical to WMF pixels, transfer a negative sign on Y, if any */
+ if (d->dc[d->level].sizeWnd.x && d->dc[d->level].sizeWnd.y) {
+ d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.x / (double) d->dc[d->level].sizeWnd.x;
+ d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.y / (double) d->dc[d->level].sizeWnd.y;
+ if(d->dc[d->level].ScaleInY < 0){
+ d->dc[d->level].ScaleInY *= -1.0;
+ d->E2IdirY = -1.0;
+ }
+ }
+ else {
+ d->dc[d->level].ScaleInX = 1;
+ d->dc[d->level].ScaleInY = 1;
+ }
+ break;
+ }
+ case U_WMR_SETVIEWPORTORG:
+ {
+ dbg_str << "<!-- U_WMR_SETWINDOWORG -->\n";
+ nSize = U_WMRSETVIEWPORTORG_get(contents, &d->dc[d->level].vieworg);
+ break;
+ }
+ case U_WMR_SETVIEWPORTEXT:
+ {
+ dbg_str << "<!-- U_WMR_SETVIEWPORTEXTEX -->\n";
+
+ nSize = U_WMRSETVIEWPORTEXT_get(contents, &d->dc[d->level].sizeView);
+
+ if (!d->dc[d->level].sizeView.x || !d->dc[d->level].sizeView.y) {
+ d->dc[d->level].sizeView = d->dc[d->level].sizeWnd;
+ if (!d->dc[d->level].sizeView.x || !d->dc[d->level].sizeView.y) {
+ d->dc[d->level].sizeView.x = d->PixelsOutX;
+ d->dc[d->level].sizeView.y = d->PixelsOutY;
+ }
+ }
+
+ if (!d->dc[d->level].sizeWnd.x || !d->dc[d->level].sizeWnd.y) {
+ d->dc[d->level].sizeWnd = d->dc[d->level].sizeView;
+ }
+
+ /* scales logical to WMF pixels, transfer a negative sign on Y, if any */
+ if (d->dc[d->level].sizeWnd.x && d->dc[d->level].sizeWnd.y) {
+ d->dc[d->level].ScaleInX = (double) d->dc[d->level].sizeView.x / (double) d->dc[d->level].sizeWnd.x;
+ d->dc[d->level].ScaleInY = (double) d->dc[d->level].sizeView.y / (double) d->dc[d->level].sizeWnd.y;
+ if(d->dc[d->level].ScaleInY < 0){
+ d->dc[d->level].ScaleInY *= -1.0;
+ d->E2IdirY = -1.0;
+ }
+ }
+ else {
+ d->dc[d->level].ScaleInX = 1;
+ d->dc[d->level].ScaleInY = 1;
+ }
+ break;
+ }
+ case U_WMR_OFFSETWINDOWORG: dbg_str << "<!-- U_WMR_OFFSETWINDOWORG -->\n"; break;
+ case U_WMR_SCALEWINDOWEXT: dbg_str << "<!-- U_WMR_SCALEWINDOWEXT -->\n"; break;
+ case U_WMR_OFFSETVIEWPORTORG: dbg_str << "<!-- U_WMR_OFFSETVIEWPORTORG -->\n"; break;
+ case U_WMR_SCALEVIEWPORTEXT: dbg_str << "<!-- U_WMR_SCALEVIEWPORTEXT -->\n"; break;
+ case U_WMR_LINETO:
+ {
+ dbg_str << "<!-- U_WMR_LINETO -->\n";
+
+ nSize = U_WMRLINETO_get(contents, &pt16);
+
+ d->mask |= wmr_mask;
+
+ tmp_path << "\n\tL " << pix_to_xy( d, pt16.x, pt16.y) << " ";
+ break;
+ }
+ case U_WMR_MOVETO:
+ {
+ dbg_str << "<!-- U_WMR_MOVETO -->\n";
+
+ nSize = U_WMRLINETO_get(contents, &pt16);
+
+ d->mask |= wmr_mask;
+
+ d->dc[d->level].cur = pt16;
+
+ tmp_path <<
+ "\n\tM " << pix_to_xy( d, pt16.x, pt16.y ) << " ";
+ break;
+ }
+ case U_WMR_EXCLUDECLIPRECT:
+ {
+ dbg_str << "<!-- U_WMR_EXCLUDECLIPRECT -->\n";
+
+ U_RECT16 rc;
+ nSize = U_WMREXCLUDECLIPRECT_get(contents, &rc);
+
+ SVGOStringStream tmp_path;
+ float faraway = 10000000; // hopefully well outside any real drawing!
+ //outer rect, clockwise
+ tmp_path << "M " << faraway << "," << faraway << " ";
+ tmp_path << "L " << faraway << "," << -faraway << " ";
+ tmp_path << "L " << -faraway << "," << -faraway << " ";
+ tmp_path << "L " << -faraway << "," << faraway << " ";
+ tmp_path << "z ";
+ //inner rect, counterclockwise (sign of Y is reversed)
+ tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " ";
+ tmp_path << "z";
+
+ add_clips(d, tmp_path.str().c_str(), U_RGN_AND);
+
+ d->path = "";
+ d->drawtype = 0;
+ break;
+ }
+ case U_WMR_INTERSECTCLIPRECT:
+ {
+ dbg_str << "<!-- U_WMR_INTERSECTCLIPRECT -->\n";
+
+ nSize = U_WMRINTERSECTCLIPRECT_get(contents, &rc);
+
+ SVGOStringStream tmp_path;
+ tmp_path << "M " << pix_to_xy( d, rc.left , rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.top ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.right, rc.bottom ) << " ";
+ tmp_path << "L " << pix_to_xy( d, rc.left, rc.bottom ) << " ";
+ tmp_path << "z";
+
+ add_clips(d, tmp_path.str().c_str(), U_RGN_AND);
+
+ d->path = "";
+ d->drawtype = 0;
+ break;
+ }
+ case U_WMR_ARC:
+ {
+ dbg_str << "<!-- U_WMR_ARC -->\n";
+ U_POINT16 ArcStart, ArcEnd;
+ nSize = U_WMRARC_get(contents, &ArcStart, &ArcEnd, &rc);
+
+ U_PAIRF center,start,end,size;
+ int f1;
+ int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1);
+ int stat = wmr_arc_points(rc, ArcStart, ArcEnd,&f1, f2, &center, &start, &end, &size);
+ if(!stat){
+ tmp_path << "\n\tM " << pix_to_xy(d, start.x, start.y);
+ tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0;
+ tmp_path << " ";
+ tmp_path << 180.0 * current_rotation(d)/M_PI;
+ tmp_path << " ";
+ tmp_path << " " << f1 << "," << f2 << " ";
+ tmp_path << pix_to_xy(d, end.x, end.y) << " \n";
+ d->mask |= wmr_mask;
+ }
+ else {
+ dbg_str << "<!-- ARC record is invalid -->\n";
+ }
+ break;
+ }
+ case U_WMR_ELLIPSE:
+ {
+ dbg_str << "<!-- U_WMR_ELLIPSE -->\n";
+
+ nSize = U_WMRELLIPSE_get(contents, &rc);
+
+ double cx = pix_to_x_point( d, (rc.left + rc.right)/2.0, (rc.bottom + rc.top)/2.0 );
+ double cy = pix_to_y_point( d, (rc.left + rc.right)/2.0, (rc.bottom + rc.top)/2.0 );
+ double rx = pix_to_abs_size( d, std::abs(rc.right - rc.left )/2.0 );
+ double ry = pix_to_abs_size( d, std::abs(rc.top - rc.bottom)/2.0 );
+
+ SVGOStringStream tmp_ellipse;
+ tmp_ellipse << "cx=\"" << cx << "\" ";
+ tmp_ellipse << "cy=\"" << cy << "\" ";
+ tmp_ellipse << "rx=\"" << rx << "\" ";
+ tmp_ellipse << "ry=\"" << ry << "\" ";
+
+ d->mask |= wmr_mask;
+
+ d->outsvg += " <ellipse ";
+ output_style(d);
+ d->outsvg += "\n\t";
+ d->outsvg += tmp_ellipse.str().c_str();
+ d->outsvg += "/> \n";
+ d->path = "";
+ break;
+ }
+ case U_WMR_FLOODFILL: dbg_str << "<!-- U_WMR_EXTFLOODFILL -->\n"; break;
+ case U_WMR_PIE:
+ {
+ dbg_str << "<!-- U_WMR_PIE -->\n";
+ U_POINT16 ArcStart, ArcEnd;
+ nSize = U_WMRPIE_get(contents, &ArcStart, &ArcEnd, &rc);
+ U_PAIRF center,start,end,size;
+ int f1;
+ int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1);
+ if(!wmr_arc_points(rc, ArcStart, ArcEnd, &f1, f2, &center, &start, &end, &size)){
+ tmp_path << "\n\tM " << pix_to_xy(d, center.x, center.y);
+ tmp_path << "\n\tL " << pix_to_xy(d, start.x, start.y);
+ tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0;
+ tmp_path << " ";
+ tmp_path << 180.0 * current_rotation(d)/M_PI;
+ tmp_path << " ";
+ tmp_path << " " << f1 << "," << f2 << " ";
+ tmp_path << pix_to_xy(d, end.x, end.y) << " \n";
+ tmp_path << " z ";
+ d->mask |= wmr_mask;
+ }
+ else {
+ dbg_str << "<!-- PIE record is invalid -->\n";
+ }
+ break;
+ }
+ case U_WMR_RECTANGLE:
+ {
+ dbg_str << "<!-- U_WMR_RECTANGLE -->\n";
+
+ nSize = U_WMRRECTANGLE_get(contents, &rc);
+ U_sanerect16(rc, &left, &top, &right, &bottom);
+
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, left , top ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, right, top ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, right, bottom ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, left, bottom ) << " ";
+ tmp_rectangle << "\n\tz";
+
+ d->mask |= wmr_mask;
+
+ tmp_path << tmp_rectangle.str().c_str();
+ break;
+ }
+ case U_WMR_ROUNDRECT:
+ {
+ dbg_str << "<!-- U_WMR_ROUNDRECT -->\n";
+
+ int16_t Height,Width;
+ nSize = U_WMRROUNDRECT_get(contents, &Width, &Height, &rc);
+ U_sanerect16(rc, &left, &top, &right, &bottom);
+ double f = 4.*(sqrt(2) - 1)/3;
+ double f1 = 1.0 - f;
+ double cnx = Width/2;
+ double cny = Height/2;
+
+
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n"
+ << " M "
+ << pix_to_xy(d, left , top + cny )
+ << "\n";
+ tmp_rectangle << " C "
+ << pix_to_xy(d, left , top + cny*f1 )
+ << " "
+ << pix_to_xy(d, left + cnx*f1 , top )
+ << " "
+ << pix_to_xy(d, left + cnx , top )
+ << "\n";
+ tmp_rectangle << " L "
+ << pix_to_xy(d, right - cnx , top )
+ << "\n";
+ tmp_rectangle << " C "
+ << pix_to_xy(d, right - cnx*f1 , top )
+ << " "
+ << pix_to_xy(d, right , top + cny*f1 )
+ << " "
+ << pix_to_xy(d, right , top + cny )
+ << "\n";
+ tmp_rectangle << " L "
+ << pix_to_xy(d, right , bottom - cny )
+ << "\n";
+ tmp_rectangle << " C "
+ << pix_to_xy(d, right , bottom - cny*f1 )
+ << " "
+ << pix_to_xy(d, right - cnx*f1 , bottom )
+ << " "
+ << pix_to_xy(d, right - cnx , bottom )
+ << "\n";
+ tmp_rectangle << " L "
+ << pix_to_xy(d, left + cnx , bottom )
+ << "\n";
+ tmp_rectangle << " C "
+ << pix_to_xy(d, left + cnx*f1 , bottom )
+ << " "
+ << pix_to_xy(d, left , bottom - cny*f1 )
+ << " "
+ << pix_to_xy(d, left , bottom - cny )
+ << "\n";
+ tmp_rectangle << " z\n";
+
+
+ d->mask |= wmr_mask;
+
+ tmp_path << tmp_rectangle.str().c_str();
+ break;
+ }
+ case U_WMR_PATBLT:
+ {
+ dbg_str << "<!-- U_WMR_PATBLT -->\n";
+ // Treat this like any other rectangle, ie, ignore the dwRop3
+ nSize = U_WMRPATBLT_get(contents, &Dst, &cwh, &dwRop3);
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, Dst.x , Dst.y ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, Dst.x + cwh.x, Dst.y ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, Dst.x + cwh.x, Dst.y + cwh.y ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, Dst.x, Dst.y + cwh.y ) << " ";
+ tmp_rectangle << "\n\tz";
+
+ d->mask |= wmr_mask;
+
+ tmp_path << tmp_rectangle.str().c_str();
+ break;
+ }
+ case U_WMR_SAVEDC:
+ {
+ dbg_str << "<!-- U_WMR_SAVEDC -->\n";
+
+ if (d->level < WMF_MAX_DC) {
+ d->dc[d->level + 1] = d->dc[d->level];
+ if(d->dc[d->level].font_name){
+ d->dc[d->level + 1].font_name = strdup(d->dc[d->level].font_name); // or memory access problems because font name pointer duplicated
+ }
+ d->level = d->level + 1;
+ }
+ break;
+ }
+ case U_WMR_SETPIXEL: dbg_str << "<!-- U_WMR_SETPIXEL -->\n"; break;
+ case U_WMR_OFFSETCLIPRGN:
+ {
+ dbg_str << "<!-- U_EMR_OFFSETCLIPRGN -->\n";
+ U_POINT16 off;
+ nSize = U_WMROFFSETCLIPRGN_get(contents,&off);
+ if (d->dc[d->level].clip_id) { // can only offset an existing clipping path
+ unsigned int real_idx = d->dc[d->level].clip_id - 1;
+ Geom::PathVector tmp_vect = sp_svg_read_pathv(d->clips.strings[real_idx]);
+ double ox = pix_to_x_point(d, off.x, off.y) - pix_to_x_point(d, 0, 0); // take into account all active transforms
+ double oy = pix_to_y_point(d, off.x, off.y) - pix_to_y_point(d, 0, 0);
+ Geom::Affine tf = Geom::Translate(ox,oy);
+ tmp_vect *= tf;
+ add_clips(d, sp_svg_write_path(tmp_vect).c_str(), U_RGN_COPY);
+ }
+ break;
+ }
+ // U_WMR_TEXTOUT should be here, but has been moved down to merge with U_WMR_EXTTEXTOUT
+ case U_WMR_BITBLT:
+ {
+ dbg_str << "<!-- U_WMR_BITBLT -->\n";
+ nSize = U_WMRBITBLT_get(contents,&Dst,&cwh,&Src,&dwRop3,&Bm16,&px);
+ if(!px){
+ if(dwRop3 == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */
+ int32_t dx = Dst.x;
+ int32_t dy = Dst.y;
+ int32_t dw = cwh.x;
+ int32_t dh = cwh.y;
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " ";
+ tmp_rectangle << "\n\tz";
+
+ d->mask |= wmr_mask;
+ d->dwRop3 = dwRop3; // we will try to approximate SOME of these
+ d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that
+
+ tmp_path << tmp_rectangle.str().c_str();
+ }
+ else { /* Not done yet, Bm16 image present */ }
+ double dx = pix_to_x_point( d, Dst.x, Dst.y);
+ double dy = pix_to_y_point( d, Dst.x, Dst.y);
+ double dw = pix_to_abs_size( d, cwh.x);
+ double dh = pix_to_abs_size( d, cwh.y);
+ //source position within the bitmap, in pixels
+ int sx = Src.x;
+ int sy = Src.y;
+ int sw = 0; // extract all of the image
+ int sh = 0;
+ if(sx<0)sx=0;
+ if(sy<0)sy=0;
+ common_bm16_to_image(d,Bm16,px,dx,dy,dw,dh,sx,sy,sw,sh);
+ break;
+ }
+ case U_WMR_STRETCHBLT:
+ {
+ dbg_str << "<!-- U_WMR_STRETCHBLT -->\n";
+ nSize = U_WMRSTRETCHBLT_get(contents,&Dst,&cDst,&Src,&cSrc,&dwRop3,&Bm16,&px);
+ if(!px){
+ if(dwRop3 == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */
+ int32_t dx = Dst.x;
+ int32_t dy = Dst.y;
+ int32_t dw = cDst.x;
+ int32_t dh = cDst.y;
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " ";
+ tmp_rectangle << "\n\tz";
+
+ d->mask |= wmr_mask;
+ d->dwRop3 = dwRop3; // we will try to approximate SOME of these
+ d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that
+
+ tmp_path << tmp_rectangle.str().c_str();
+ }
+ else { /* Not done yet, Bm16 image present */ }
+ double dx = pix_to_x_point( d, Dst.x, Dst.y);
+ double dy = pix_to_y_point( d, Dst.x, Dst.y);
+ double dw = pix_to_abs_size( d, cDst.x);
+ double dh = pix_to_abs_size( d, cDst.y);
+ //source position within the bitmap, in pixels
+ int sx = Src.x;
+ int sy = Src.y;
+ int sw = cSrc.x; // extract the specified amount of the image
+ int sh = cSrc.y;
+ if(sx<0)sx=0;
+ if(sy<0)sy=0;
+ common_bm16_to_image(d,Bm16,px,dx,dy,dw,dh,sx,sy,sw,sh);
+ break;
+ }
+ case U_WMR_POLYGON:
+ case U_WMR_POLYLINE:
+ {
+ dbg_str << "<!-- U_WMR_POLYGON/POLYLINE -->\n";
+ nSize = U_WMRPOLYGON_get(contents, &cPts, &points);
+ uint32_t i;
+
+ if (cPts < 2)break;
+
+ d->mask |= wmr_mask;
+ memcpy(&pt16,points,U_SIZE_POINT16); points += U_SIZE_POINT16;
+
+ tmp_str << "\n\tM " << pix_to_xy( d, pt16.x, pt16.y) << " ";
+
+ for (i=1; i<cPts; i++) {
+ memcpy(&pt16,points,U_SIZE_POINT16); points+=U_SIZE_POINT16;
+ tmp_str << "\n\tL " << pix_to_xy( d, pt16.x, pt16.y) << " ";
+ }
+
+ tmp_path << tmp_str.str().c_str();
+ if(iType==U_WMR_POLYGON){ tmp_path << " z"; }
+
+ break;
+ }
+ case U_WMR_ESCAPE: // only 3 types of escape are implemented
+ {
+ dbg_str << "<!-- U_WMR_ESCAPE -->\n";
+ uint16_t Escape, elen;
+ nSize = U_WMRESCAPE_get(contents, &Escape, &elen, &text);
+ if(elen>=4){
+ uint32_t utmp4;
+ memcpy(&utmp4, text ,4);
+ if(Escape == U_MFE_SETLINECAP){
+ switch (utmp4 & U_PS_ENDCAP_MASK) {
+ case U_PS_ENDCAP_ROUND: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_ROUND; break; }
+ case U_PS_ENDCAP_SQUARE: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; break; }
+ case U_PS_ENDCAP_FLAT:
+ default: { d->dc[d->level].style.stroke_linecap.computed = SP_STROKE_LINECAP_BUTT; break; }
+ }
+ }
+ else if(Escape == U_MFE_SETLINEJOIN){
+ switch (utmp4 & U_PS_JOIN_MASK) {
+ case U_PS_JOIN_BEVEL: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_BEVEL; break; }
+ case U_PS_JOIN_MITER: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; break; }
+ case U_PS_JOIN_ROUND:
+ default: { d->dc[d->level].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_ROUND; break; }
+ }
+ }
+ else if(Escape == U_MFE_SETMITERLIMIT){
+ //The function takes a float but uses a 32 bit int in the record.
+ float miterlimit = utmp4;
+ d->dc[d->level].style.stroke_miterlimit.value = miterlimit; //ratio, not a pt size
+ if (d->dc[d->level].style.stroke_miterlimit.value < 2)
+ d->dc[d->level].style.stroke_miterlimit.value = 2.0;
+ }
+ }
+ break;
+ }
+ case U_WMR_RESTOREDC:
+ {
+ dbg_str << "<!-- U_WMR_RESTOREDC -->\n";
+
+ int16_t DC;
+ nSize = U_WMRRESTOREDC_get(contents, &DC);
+ int old_level = d->level;
+ if (DC >= 0) {
+ if (DC < d->level)
+ d->level = DC;
+ }
+ else {
+ if (d->level + DC >= 0)
+ d->level = d->level + DC;
+ }
+ while (old_level > d->level) {
+ if (!d->dc[old_level].style.stroke_dasharray.values.empty() &&
+ (old_level == 0 || (old_level > 0 && d->dc[old_level].style.stroke_dasharray !=
+ d->dc[old_level - 1].style.stroke_dasharray))) {
+ d->dc[old_level].style.stroke_dasharray.values.clear();
+ }
+ if(d->dc[old_level].font_name){
+ free(d->dc[old_level].font_name); // else memory leak
+ d->dc[old_level].font_name = nullptr;
+ }
+ old_level--;
+ }
+ break;
+ }
+ case U_WMR_FILLREGION: dbg_str << "<!-- U_WMR_FILLREGION -->\n"; break;
+ case U_WMR_FRAMEREGION: dbg_str << "<!-- U_WMR_FRAMEREGION -->\n"; break;
+ case U_WMR_INVERTREGION: dbg_str << "<!-- U_WMR_INVERTREGION -->\n"; break;
+ case U_WMR_PAINTREGION: dbg_str << "<!-- U_WMR_PAINTREGION -->\n"; break;
+ case U_WMR_SELECTCLIPREGION:
+ {
+ dbg_str << "<!-- U_WMR_EXTSELECTCLIPRGN -->\n";
+ nSize = U_WMRSELECTCLIPREGION_get(contents, &utmp16);
+ if (utmp16 == U_RGN_COPY)
+ clipset = false;
+ break;
+ }
+ case U_WMR_SELECTOBJECT:
+ {
+ dbg_str << "<!-- U_WMR_SELECTOBJECT -->\n";
+
+ nSize = U_WMRSELECTOBJECT_get(contents, &utmp16);
+ unsigned int index = utmp16;
+
+ // WMF has no stock objects
+ if ( /*index >= 0 &&*/ index < (unsigned int) d->n_obj) {
+ switch (d->wmf_obj[index].type)
+ {
+ case U_WMR_CREATEPENINDIRECT:
+ select_pen(d, index);
+ break;
+ case U_WMR_CREATEBRUSHINDIRECT:
+ case U_WMR_DIBCREATEPATTERNBRUSH:
+ case U_WMR_CREATEPATTERNBRUSH: // <- this one did not display properly on XP, DIBCREATEPATTERNBRUSH works
+ select_brush(d, index);
+ break;
+ case U_WMR_CREATEFONTINDIRECT:
+ select_font(d, index);
+ break;
+ case U_WMR_CREATEPALETTE:
+ case U_WMR_CREATEBITMAPINDIRECT:
+ case U_WMR_CREATEBITMAP:
+ case U_WMR_CREATEREGION:
+ /* these do not do anything, but their objects must be kept in the count */
+ break;
+ }
+ }
+ break;
+ }
+ case U_WMR_SETTEXTALIGN:
+ {
+ dbg_str << "<!-- U_WMR_SETTEXTALIGN -->\n";
+ nSize = U_WMRSETTEXTALIGN_get(contents, &(d->dc[d->level].textAlign));
+ break;
+ }
+ case U_WMR_DRAWTEXT: dbg_str << "<!-- U_WMR_DRAWTEXT -->\n"; break;
+ case U_WMR_CHORD:
+ {
+ dbg_str << "<!-- U_WMR_CHORD -->\n";
+ U_POINT16 ArcStart, ArcEnd;
+ nSize = U_WMRCHORD_get(contents, &ArcStart, &ArcEnd, &rc);
+ U_PAIRF center,start,end,size;
+ int f1;
+ int f2 = (d->arcdir == U_AD_COUNTERCLOCKWISE ? 0 : 1);
+ if(!wmr_arc_points(rc, ArcStart, ArcEnd, &f1, f2, &center, &start, &end, &size)){
+ tmp_path << "\n\tM " << pix_to_xy(d, start.x, start.y);
+ tmp_path << " A " << pix_to_abs_size(d, size.x)/2.0 << "," << pix_to_abs_size(d, size.y)/2.0 ;
+ tmp_path << " ";
+ tmp_path << 180.0 * current_rotation(d)/M_PI;
+ tmp_path << " ";
+ tmp_path << " " << f1 << "," << f2 << " ";
+ tmp_path << pix_to_xy(d, end.x, end.y) << " \n";
+ tmp_path << " z ";
+ d->mask |= wmr_mask;
+ }
+ else {
+ dbg_str << "<!-- CHORD record is invalid -->\n";
+ }
+ break;
+ }
+ case U_WMR_SETMAPPERFLAGS: dbg_str << "<!-- U_WMR_SETMAPPERFLAGS -->\n"; break;
+ case U_WMR_TEXTOUT:
+ case U_WMR_EXTTEXTOUT:
+ {
+ if(iType == U_WMR_TEXTOUT){
+ dbg_str << "<!-- U_WMR_TEXTOUT -->\n";
+ nSize = U_WMRTEXTOUT_get(contents, &Dst, &tlen, &text);
+ Opts=0;
+ }
+ else {
+ dbg_str << "<!-- U_WMR_EXTTEXTOUT -->\n";
+ nSize = U_WMREXTTEXTOUT_get(contents, &Dst, &tlen, &Opts, &text, &dx, &rc );
+ }
+ uint32_t fOptions = Opts;
+
+ double x1,y1;
+ int cChars;
+ x1 = Dst.x;
+ y1 = Dst.y;
+ cChars = tlen;
+
+ if (d->dc[d->level].textAlign & U_TA_UPDATECP) {
+ x1 = d->dc[d->level].cur.x;
+ y1 = d->dc[d->level].cur.y;
+ }
+
+ double x = pix_to_x_point(d, x1, y1);
+ double y = pix_to_y_point(d, x1, y1);
+
+ /* Rotation issues are handled entirely in libTERE now */
+
+ uint32_t *dup_wt = nullptr;
+
+ dup_wt = U_Latin1ToUtf32le(text, cChars, nullptr);
+ if(!dup_wt)dup_wt = unknown_chars(cChars);
+
+ msdepua(dup_wt); //convert everything in Microsoft's private use area. For Symbol, Wingdings, Dingbats
+
+ if(NonToUnicode(dup_wt, d->dc[d->level].font_name)){
+ free(d->dc[d->level].font_name);
+ d->dc[d->level].font_name = strdup("Times New Roman");
+ }
+
+ char *ansi_text;
+ ansi_text = (char *) U_Utf32leToUtf8((uint32_t *)dup_wt, 0, nullptr);
+ free(dup_wt);
+ // Empty text or starts with an invalid escape/control sequence, which is bogus text. Throw it out before g_markup_escape_text can make things worse
+ if(*((uint8_t *)ansi_text) <= 0x1F){
+ free(ansi_text);
+ ansi_text=nullptr;
+ }
+
+ if (ansi_text) {
+
+ SVGOStringStream ts;
+
+ gchar *escaped_text = g_markup_escape_text(ansi_text, -1);
+
+ tsp.x = x*0.8; // TERE expects sizes in points
+ tsp.y = y*0.8;
+ tsp.color.Red = d->dc[d->level].textColor.Red;
+ tsp.color.Green = d->dc[d->level].textColor.Green;
+ tsp.color.Blue = d->dc[d->level].textColor.Blue;
+ tsp.color.Reserved = 0;
+ switch(d->dc[d->level].style.font_style.value){
+ case SP_CSS_FONT_STYLE_OBLIQUE:
+ tsp.italics = FC_SLANT_OBLIQUE; break;
+ case SP_CSS_FONT_STYLE_ITALIC:
+ tsp.italics = FC_SLANT_ITALIC; break;
+ default:
+ case SP_CSS_FONT_STYLE_NORMAL:
+ tsp.italics = FC_SLANT_ROMAN; break;
+ }
+ switch(d->dc[d->level].style.font_weight.value){
+ case SP_CSS_FONT_WEIGHT_100: tsp.weight = FC_WEIGHT_THIN ; break;
+ case SP_CSS_FONT_WEIGHT_200: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break;
+ case SP_CSS_FONT_WEIGHT_300: tsp.weight = FC_WEIGHT_LIGHT ; break;
+ case SP_CSS_FONT_WEIGHT_400: tsp.weight = FC_WEIGHT_NORMAL ; break;
+ case SP_CSS_FONT_WEIGHT_500: tsp.weight = FC_WEIGHT_MEDIUM ; break;
+ case SP_CSS_FONT_WEIGHT_600: tsp.weight = FC_WEIGHT_SEMIBOLD ; break;
+ case SP_CSS_FONT_WEIGHT_700: tsp.weight = FC_WEIGHT_BOLD ; break;
+ case SP_CSS_FONT_WEIGHT_800: tsp.weight = FC_WEIGHT_EXTRABOLD ; break;
+ case SP_CSS_FONT_WEIGHT_900: tsp.weight = FC_WEIGHT_HEAVY ; break;
+ case SP_CSS_FONT_WEIGHT_NORMAL: tsp.weight = FC_WEIGHT_NORMAL ; break;
+ case SP_CSS_FONT_WEIGHT_BOLD: tsp.weight = FC_WEIGHT_BOLD ; break;
+ case SP_CSS_FONT_WEIGHT_LIGHTER: tsp.weight = FC_WEIGHT_EXTRALIGHT ; break;
+ case SP_CSS_FONT_WEIGHT_BOLDER: tsp.weight = FC_WEIGHT_EXTRABOLD ; break;
+ default: tsp.weight = FC_WEIGHT_NORMAL ; break;
+ }
+ // WMF only supports two types of text decoration
+ tsp.decoration = TXTDECOR_NONE;
+ if(d->dc[d->level].style.text_decoration_line.underline){ tsp.decoration |= TXTDECOR_UNDER; }
+ if(d->dc[d->level].style.text_decoration_line.line_through){ tsp.decoration |= TXTDECOR_STRIKE;}
+
+ // WMF textalignment is a bit strange: 0x6 is center, 0x2 is right, 0x0 is left, the value 0x4 is also drawn left
+ tsp.taln = ((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_CENTER) ? ALICENTER :
+ (((d->dc[d->level].textAlign & U_TA_CENTER) == U_TA_LEFT) ? ALILEFT :
+ ALIRIGHT);
+ tsp.taln |= ((d->dc[d->level].textAlign & U_TA_BASEBIT) ? ALIBASE :
+ ((d->dc[d->level].textAlign & U_TA_BOTTOM) ? ALIBOT :
+ ALITOP));
+
+ // language direction can be encoded two ways, U_TA_RTLREADING is preferred
+ if( (fOptions & U_ETO_RTLREADING) || (d->dc[d->level].textAlign & U_TA_RTLREADING) ){ tsp.ldir = LDIR_RL; }
+ else{ tsp.ldir = LDIR_LR; }
+
+ tsp.condensed = FC_WIDTH_NORMAL; // Not implemented well in libTERE (yet)
+ tsp.ori = d->dc[d->level].style.baseline_shift.value; // For now orientation is always the same as escapement
+ // There is no world transform, so ori need not be further rotated
+ tsp.string = (uint8_t *) U_strdup(escaped_text); // this will be free'd much later at a trinfo_clear().
+ tsp.fs = d->dc[d->level].style.font_size.computed * 0.8; // Font size in points
+ char *fontspec = TR_construct_fontspec(&tsp, d->dc[d->level].font_name);
+ tsp.fi_idx = ftinfo_load_fontname(d->tri->fti,fontspec);
+ free(fontspec);
+ // when font name includes narrow it may not be set to "condensed". Narrow fonts do not work well anyway though
+ // as the metrics from fontconfig may not match, or the font may not be present.
+ if(0<= TR_findcasesub(d->dc[d->level].font_name, (char *) "Narrow")){ tsp.co=1; }
+ else { tsp.co=0; }
+
+ int status;
+
+ status = trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ori is actually escapement
+ if(status==-1){ // change of escapement, emit what we have and reset
+ TR_layout_analyze(d->tri);
+ if (d->dc[d->level].clip_id){
+ SVGOStringStream tmp_clip;
+ tmp_clip << "\n<g\n\tclip-path=\"url(#clipWmfPath" << d->dc[d->level].clip_id << ")\"\n>";
+ d->outsvg += tmp_clip.str().c_str();
+ }
+ TR_layout_2_svg(d->tri);
+ ts << d->tri->out;
+ d->outsvg += ts.str().c_str();
+ d->tri = trinfo_clear(d->tri);
+ (void) trinfo_load_textrec(d->tri, &tsp, tsp.ori,TR_EMFBOT); // ignore return status, it must work
+ if (d->dc[d->level].clip_id){
+ d->outsvg += "\n</g>\n";
+ }
+ }
+
+ g_free(escaped_text);
+ free(ansi_text);
+ }
+
+ break;
+ }
+ case U_WMR_SETDIBTODEV: dbg_str << "<!-- U_WMR_EXTTEXTOUT -->\n"; break;
+ case U_WMR_SELECTPALETTE: dbg_str << "<!-- U_WMR_SELECTPALETTE -->\n"; break;
+ case U_WMR_REALIZEPALETTE: dbg_str << "<!-- U_WMR_REALIZEPALETTE -->\n"; break;
+ case U_WMR_ANIMATEPALETTE: dbg_str << "<!-- U_WMR_ANIMATEPALETTE -->\n"; break;
+ case U_WMR_SETPALENTRIES: dbg_str << "<!-- U_WMR_SETPALENTRIES -->\n"; break;
+ case U_WMR_POLYPOLYGON:
+ {
+ dbg_str << "<!-- U_WMR_POLYPOLYGON16 -->\n";
+ uint16_t nPolys;
+ const uint16_t *aPolyCounts;
+ const char *Points;
+ int cpts; /* total number of points in Points*/
+ nSize = U_WMRPOLYPOLYGON_get(contents, &nPolys, &aPolyCounts, &Points);
+ int n, i, j;
+
+ d->mask |= wmr_mask;
+
+ U_POINT16 apt;
+ for (n=cpts=0; n < nPolys; n++) { cpts += aPolyCounts[n]; }
+ i = 0; // offset in BYTES
+ cpts *= U_SIZE_POINT16; // limit for offset i, in BYTES
+
+ for (n=0; n < nPolys && i<cpts; n++) {
+ SVGOStringStream poly_path;
+
+ memcpy(&apt, Points + i, U_SIZE_POINT16); // points may not be aligned, copy them this way
+
+ poly_path << "\n\tM " << pix_to_xy( d, apt.x, apt.y) << " ";
+ i += U_SIZE_POINT16;
+
+ for (j=1; j < aPolyCounts[n] && i < cpts; j++) {
+ memcpy(&apt, Points + i, U_SIZE_POINT16); // points may not be aligned, copy them this way
+ poly_path << "\n\tL " << pix_to_xy( d, apt.x, apt.y) << " ";
+ i += U_SIZE_POINT16;
+ }
+
+ tmp_str << poly_path.str().c_str();
+ tmp_str << " z";
+ tmp_str << " \n";
+ }
+
+ tmp_path << tmp_str.str().c_str();
+
+ break;
+ }
+ case U_WMR_RESIZEPALETTE: dbg_str << "<!-- U_WMR_RESIZEPALETTE -->\n"; break;
+ case U_WMR_3A:
+ case U_WMR_3B:
+ case U_WMR_3C:
+ case U_WMR_3D:
+ case U_WMR_3E:
+ case U_WMR_3F:
+ {
+ dbg_str << "<!-- U_WMR_3A..3F -->\n";
+ break;
+ }
+ case U_WMR_DIBBITBLT:
+ {
+ dbg_str << "<!-- U_WMR_DIBBITBLT -->\n";
+ nSize = U_WMRDIBBITBLT_get(contents, &Dst, &cwh, &Src, &dwRop3, &dib);
+
+ // Treat all nonImage bitblts as a rectangular write. Definitely not correct, but at
+ // least it leaves objects where the operations should have been.
+ if (!dib) {
+ // should be an application of a DIBPATTERNBRUSHPT, use a solid color instead
+
+ if(dwRop3 == U_NOOP)break; /* GDI applications apparently often end with this as a sort of flush(), nothing should be drawn */
+ int32_t dx = Dst.x;
+ int32_t dy = Dst.y;
+ int32_t dw = cwh.x;
+ int32_t dh = cwh.y;
+ SVGOStringStream tmp_rectangle;
+ tmp_rectangle << "\n\tM " << pix_to_xy( d, dx, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx + dw, dy + dh ) << " ";
+ tmp_rectangle << "\n\tL " << pix_to_xy( d, dx, dy + dh ) << " ";
+ tmp_rectangle << "\n\tz";
+
+ d->mask |= wmr_mask;
+ d->dwRop3 = dwRop3; // we will try to approximate SOME of these
+ d->mask |= U_DRAW_CLOSED; // Bitblit is not really open or closed, but we need it to fill, and this is the flag for that
+
+ tmp_path << tmp_rectangle.str().c_str();
+ }
+ else {
+ double dx = pix_to_x_point( d, Dst.x, Dst.y);
+ double dy = pix_to_y_point( d, Dst.x, Dst.y);
+ double dw = pix_to_abs_size( d, cDst.x);
+ double dh = pix_to_abs_size( d, cDst.y);
+ //source position within the bitmap, in pixels
+ int sx = Src.x;
+ int sy = Src.y;
+ int sw = 0; // extract all of the image
+ int sh = 0;
+ if(sx<0)sx=0;
+ if(sy<0)sy=0;
+ // usageSrc not defined, implicitly it must be U_DIB_RGB_COLORS
+ common_dib_to_image(d,dib,dx,dy,dw,dh,sx,sy,sw,sh,U_DIB_RGB_COLORS);
+ }
+ break;
+ }
+ case U_WMR_DIBSTRETCHBLT:
+ {
+ dbg_str << "<!-- U_WMR_DIBSTRETCHBLT -->\n";
+ nSize = U_WMRDIBSTRETCHBLT_get(contents, &Dst, &cDst, &Src, &cSrc, &dwRop3, &dib);
+ // Always grab image, ignore modes.
+ if (dib) {
+ double dx = pix_to_x_point( d, Dst.x, Dst.y);
+ double dy = pix_to_y_point( d, Dst.x, Dst.y);
+ double dw = pix_to_abs_size( d, cDst.x);
+ double dh = pix_to_abs_size( d, cDst.y);
+ //source position within the bitmap, in pixels
+ int sx = Src.x;
+ int sy = Src.y;
+ int sw = cSrc.x; // extract the specified amount of the image
+ int sh = cSrc.y;
+ // usageSrc not defined, implicitly it must be U_DIB_RGB_COLORS
+ common_dib_to_image(d,dib,dx,dy,dw,dh,sx,sy,sw,sh, U_DIB_RGB_COLORS);
+ }
+ break;
+ }
+ case U_WMR_DIBCREATEPATTERNBRUSH:
+ {
+ dbg_str << "<!-- U_WMR_DIBCREATEPATTERNBRUSH -->\n";
+ insert_object(d, U_WMR_DIBCREATEPATTERNBRUSH, contents);
+ break;
+ }
+ case U_WMR_STRETCHDIB:
+ {
+ dbg_str << "<!-- U_WMR_STRETCHDIB -->\n";
+ nSize = U_WMRSTRETCHDIB_get(contents, &Dst, &cDst, &Src, &cSrc, &cUsage, &dwRop3, &dib);
+ double dx = pix_to_x_point( d, Dst.x, Dst.y );
+ double dy = pix_to_y_point( d, Dst.x, Dst.y );
+ double dw = pix_to_abs_size( d, cDst.x);
+ double dh = pix_to_abs_size( d, cDst.y);
+ int sx = Src.x; //source position within the bitmap, in pixels
+ int sy = Src.y;
+ int sw = cSrc.x; // extract the specified amount of the image
+ int sh = cSrc.y;
+ uint32_t iUsageSrc;
+ iUsageSrc = cUsage;
+ common_dib_to_image(d,dib,dx,dy,dw,dh,sx,sy,sw,sh,iUsageSrc);
+
+ break;
+ }
+ case U_WMR_44:
+ case U_WMR_45:
+ case U_WMR_46:
+ case U_WMR_47:
+ {
+ dbg_str << "<!-- U_WMR_44..47 -->\n";
+ break;
+ }
+ case U_WMR_EXTFLOODFILL: dbg_str << "<!-- U_WMR_EXTFLOODFILL -->\n"; break;
+ case U_WMR_49:
+ case U_WMR_4A:
+ case U_WMR_4B:
+ case U_WMR_4C:
+ case U_WMR_4D:
+ case U_WMR_4E:
+ case U_WMR_4F:
+ case U_WMR_50:
+ case U_WMR_51:
+ case U_WMR_52:
+ case U_WMR_53:
+ case U_WMR_54:
+ case U_WMR_55:
+ case U_WMR_56:
+ case U_WMR_57:
+ case U_WMR_58:
+ case U_WMR_59:
+ case U_WMR_5A:
+ case U_WMR_5B:
+ case U_WMR_5C:
+ case U_WMR_5D:
+ case U_WMR_5E:
+ case U_WMR_5F:
+ case U_WMR_60:
+ case U_WMR_61:
+ case U_WMR_62:
+ case U_WMR_63:
+ case U_WMR_64:
+ case U_WMR_65:
+ case U_WMR_66:
+ case U_WMR_67:
+ case U_WMR_68:
+ case U_WMR_69:
+ case U_WMR_6A:
+ case U_WMR_6B:
+ case U_WMR_6C:
+ case U_WMR_6D:
+ case U_WMR_6E:
+ case U_WMR_6F:
+ case U_WMR_70:
+ case U_WMR_71:
+ case U_WMR_72:
+ case U_WMR_73:
+ case U_WMR_74:
+ case U_WMR_75:
+ case U_WMR_76:
+ case U_WMR_77:
+ case U_WMR_78:
+ case U_WMR_79:
+ case U_WMR_7A:
+ case U_WMR_7B:
+ case U_WMR_7C:
+ case U_WMR_7D:
+ case U_WMR_7E:
+ case U_WMR_7F:
+ case U_WMR_80:
+ case U_WMR_81:
+ case U_WMR_82:
+ case U_WMR_83:
+ case U_WMR_84:
+ case U_WMR_85:
+ case U_WMR_86:
+ case U_WMR_87:
+ case U_WMR_88:
+ case U_WMR_89:
+ case U_WMR_8A:
+ case U_WMR_8B:
+ case U_WMR_8C:
+ case U_WMR_8D:
+ case U_WMR_8E:
+ case U_WMR_8F:
+ case U_WMR_90:
+ case U_WMR_91:
+ case U_WMR_92:
+ case U_WMR_93:
+ case U_WMR_94:
+ case U_WMR_95:
+ case U_WMR_96:
+ case U_WMR_97:
+ case U_WMR_98:
+ case U_WMR_99:
+ case U_WMR_9A:
+ case U_WMR_9B:
+ case U_WMR_9C:
+ case U_WMR_9D:
+ case U_WMR_9E:
+ case U_WMR_9F:
+ case U_WMR_A0:
+ case U_WMR_A1:
+ case U_WMR_A2:
+ case U_WMR_A3:
+ case U_WMR_A4:
+ case U_WMR_A5:
+ case U_WMR_A6:
+ case U_WMR_A7:
+ case U_WMR_A8:
+ case U_WMR_A9:
+ case U_WMR_AA:
+ case U_WMR_AB:
+ case U_WMR_AC:
+ case U_WMR_AD:
+ case U_WMR_AE:
+ case U_WMR_AF:
+ case U_WMR_B0:
+ case U_WMR_B1:
+ case U_WMR_B2:
+ case U_WMR_B3:
+ case U_WMR_B4:
+ case U_WMR_B5:
+ case U_WMR_B6:
+ case U_WMR_B7:
+ case U_WMR_B8:
+ case U_WMR_B9:
+ case U_WMR_BA:
+ case U_WMR_BB:
+ case U_WMR_BC:
+ case U_WMR_BD:
+ case U_WMR_BE:
+ case U_WMR_BF:
+ case U_WMR_C0:
+ case U_WMR_C1:
+ case U_WMR_C2:
+ case U_WMR_C3:
+ case U_WMR_C4:
+ case U_WMR_C5:
+ case U_WMR_C6:
+ case U_WMR_C7:
+ case U_WMR_C8:
+ case U_WMR_C9:
+ case U_WMR_CA:
+ case U_WMR_CB:
+ case U_WMR_CC:
+ case U_WMR_CD:
+ case U_WMR_CE:
+ case U_WMR_CF:
+ case U_WMR_D0:
+ case U_WMR_D1:
+ case U_WMR_D2:
+ case U_WMR_D3:
+ case U_WMR_D4:
+ case U_WMR_D5:
+ case U_WMR_D6:
+ case U_WMR_D7:
+ case U_WMR_D8:
+ case U_WMR_D9:
+ case U_WMR_DA:
+ case U_WMR_DB:
+ case U_WMR_DC:
+ case U_WMR_DD:
+ case U_WMR_DE:
+ case U_WMR_DF:
+ case U_WMR_E0:
+ case U_WMR_E1:
+ case U_WMR_E2:
+ case U_WMR_E3:
+ case U_WMR_E4:
+ case U_WMR_E5:
+ case U_WMR_E6:
+ case U_WMR_E7:
+ case U_WMR_E8:
+ case U_WMR_E9:
+ case U_WMR_EA:
+ case U_WMR_EB:
+ case U_WMR_EC:
+ case U_WMR_ED:
+ case U_WMR_EE:
+ case U_WMR_EF:
+ {
+ dbg_str << "<!-- U_WMR_EXTFLOODFILL..EF -->\n";
+ break;
+ }
+ case U_WMR_DELETEOBJECT:
+ {
+ dbg_str << "<!-- U_WMR_DELETEOBJECT -->\n";
+ nSize = U_WMRDELETEOBJECT_get(contents, &utmp16);
+ delete_object(d, utmp16);
+ break;
+ }
+ case U_WMR_F1:
+ case U_WMR_F2:
+ case U_WMR_F3:
+ case U_WMR_F4:
+ case U_WMR_F5:
+ case U_WMR_F6:
+ {
+ dbg_str << "<!-- F1..F6 -->\n";
+ break;
+ }
+ case U_WMR_CREATEPALETTE:
+ {
+ dbg_str << "<!-- U_WMR_CREATEPALETTE -->\n";
+ insert_object(d, U_WMR_CREATEPALETTE, contents);
+ break;
+ }
+ case U_WMR_F8: dbg_str << "<!-- F8 -->\n"; break;
+ case U_WMR_CREATEPATTERNBRUSH:
+ {
+ dbg_str << "<!-- U_WMR_CREATEPATTERNBRUSH -->\n";
+ insert_object(d, U_WMR_CREATEPATTERNBRUSH, contents);
+ break;
+ }
+ case U_WMR_CREATEPENINDIRECT:
+ {
+ dbg_str << "<!-- U_WMR_EXTCREATEPEN -->\n";
+ insert_object(d, U_WMR_CREATEPENINDIRECT, contents);
+ break;
+ }
+ case U_WMR_CREATEFONTINDIRECT:
+ {
+ dbg_str << "<!-- U_WMR_CREATEFONTINDIRECT -->\n";
+ insert_object(d, U_WMR_CREATEFONTINDIRECT, contents);
+ break;
+ }
+ case U_WMR_CREATEBRUSHINDIRECT:
+ {
+ dbg_str << "<!-- U_WMR_CREATEBRUSHINDIRECT -->\n";
+ insert_object(d, U_WMR_CREATEBRUSHINDIRECT, contents);
+ break;
+ }
+ case U_WMR_CREATEBITMAPINDIRECT:
+ {
+ dbg_str << "<!-- U_WMR_CREATEBITMAPINDIRECT -->\n";
+ insert_object(d, U_WMR_CREATEBITMAPINDIRECT, contents);
+ break;
+ }
+ case U_WMR_CREATEBITMAP:
+ {
+ dbg_str << "<!-- U_WMR_CREATEBITMAP -->\n";
+ insert_object(d, U_WMR_CREATEBITMAP, contents);
+ break;
+ }
+ case U_WMR_CREATEREGION:
+ {
+ dbg_str << "<!-- U_WMR_CREATEREGION -->\n";
+ insert_object(d, U_WMR_CREATEREGION, contents);
+ break;
+ }
+ default:
+ dbg_str << "<!-- U_WMR_??? -->\n";
+ break;
+ } //end of switch
+// At run time define environment variable INKSCAPE_DBG_WMF to include string COMMENT.
+// Users may employ this to to place a comment for each processed WMR record in the SVG
+ if(wDbgComment){
+ d->outsvg += dbg_str.str().c_str();
+ }
+ d->path += tmp_path.str().c_str();
+ if(!nSize){ // There was some problem with the processing of this record, it is not safe to continue
+ file_status = 0;
+ break;
+ }
+
+ } //end of while on OK
+// At run time define environment variable INKSCAPE_DBG_WMF to include string FINAL
+// Users may employ this to to show the final SVG derived from the WMF
+ if(wDbgFinal){
+ std::cout << d->outsvg << std::endl;
+ }
+ (void) U_wmr_properties(U_WMR_INVALID); // force the release of the lookup table memory, returned value is irrelevant
+
+ return(file_status);
+}
+
+void Wmf::free_wmf_strings(WMF_STRINGS name){
+ if(name.count){
+ for(int i=0; i< name.count; i++){ free(name.strings[i]); }
+ free(name.strings);
+ }
+ name.count = 0;
+ name.size = 0;
+}
+
+SPDocument *
+Wmf::open( Inkscape::Extension::Input * /*mod*/, const gchar *uri )
+{
+
+ if (uri == nullptr) {
+ return nullptr;
+ }
+
+ // ensure usage of dot as decimal separator in scanf/printf functions (indepentendly of current locale)
+ char *oldlocale = g_strdup(setlocale(LC_NUMERIC, nullptr));
+ setlocale(LC_NUMERIC, "C");
+
+ WMF_CALLBACK_DATA d;
+
+ d.n_obj = 0; //these might not be set otherwise if the input file is corrupt
+ d.wmf_obj=nullptr;
+
+ // Default font, WMF spec says device can pick whatever it wants.
+ // WMF files that do not specify a font are unlikely to look very good!
+ d.dc[0].style.font_size.computed = 16.0;
+ d.dc[0].style.font_weight.value = SP_CSS_FONT_WEIGHT_400;
+ d.dc[0].style.font_style.value = SP_CSS_FONT_STYLE_NORMAL;
+ d.dc[0].style.text_decoration_line.underline = false;
+ d.dc[0].style.text_decoration_line.line_through = false;
+ d.dc[0].style.baseline_shift.value = 0;
+
+ // Default pen, WMF files that do not specify a pen are unlikely to look very good!
+ d.dc[0].style.stroke_dasharray.set = false;
+ d.dc[0].style.stroke_linecap.computed = SP_STROKE_LINECAP_SQUARE; // U_PS_ENDCAP_SQUARE;
+ d.dc[0].style.stroke_linejoin.computed = SP_STROKE_LINEJOIN_MITER; // U_PS_JOIN_MITER;
+ d.dc[0].style.stroke_width.value = 1.0; // will be reset to something reasonable once WMF drawing size is known
+ d.dc[0].style.stroke.value.color.set( 0, 0, 0 );
+ d.dc[0].stroke_set = true;
+
+ // Default brush is none - no fill. WMF files that do not specify a brush are unlikely to look very good!
+ d.dc[0].fill_set = false;
+
+ d.dc[0].font_name = strdup("Arial"); // Default font, set only on lowest level, it copies up from there WMF spec says device can pick whatever it wants
+
+ // set up the size default for patterns in defs. This might not be referenced if there are no patterns defined in the drawing.
+
+ d.defs += "\n";
+ d.defs += " <pattern id=\"WMFhbasepattern\" \n";
+ d.defs += " patternUnits=\"userSpaceOnUse\"\n";
+ d.defs += " width=\"6\" \n";
+ d.defs += " height=\"6\" \n";
+ d.defs += " x=\"0\" \n";
+ d.defs += " y=\"0\"> \n";
+ d.defs += " </pattern> \n";
+
+
+ size_t length;
+ char *contents;
+ if(wmf_readdata(uri, &contents, &length))return(nullptr);
+
+ // set up the text reassembly system
+ if(!(d.tri = trinfo_init(nullptr)))return(nullptr);
+ (void) trinfo_load_ft_opts(d.tri, 1,
+ FT_LOAD_NO_SCALE | FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP,
+ FT_KERNING_UNSCALED);
+
+ int good = myMetaFileProc(contents,length, &d);
+ free(contents);
+
+// std::cout << "SVG Output: " << std::endl << d.outsvg << std::endl;
+
+ SPDocument *doc = nullptr;
+ if (good) {
+ doc = SPDocument::createNewDocFromMem(d.outsvg.c_str(), strlen(d.outsvg.c_str()), TRUE);
+ }
+
+ free_wmf_strings(d.hatches);
+ free_wmf_strings(d.images);
+ free_wmf_strings(d.clips);
+
+ if (d.wmf_obj) {
+ int i;
+ for (i=0; i<d.n_obj; i++)
+ delete_object(&d, i);
+ delete[] d.wmf_obj;
+ }
+
+ d.dc[0].style.stroke_dasharray.values.clear();
+
+ for(int i=0; i<=WMF_MAX_DC; i++){
+ if(d.dc[i].font_name)free(d.dc[i].font_name);
+ }
+
+ d.tri = trinfo_release_except_FC(d.tri);
+
+ // in earlier versions no viewbox was generated and a call to setViewBoxIfMissing() was needed here.
+
+ // restore decimal separator used in scanf/printf functions to initial value
+ setlocale(LC_NUMERIC, oldlocale);
+ g_free(oldlocale);
+
+ return doc;
+}
+
+
+void
+Wmf::init ()
+{
+ // clang-format off
+ /* 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"
+ "</input>\n"
+ "</inkscape-extension>", new Wmf());
+
+ /* WMF out */
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("WMF Output") "</name>\n"
+ "<id>org.inkscape.output.wmf</id>\n"
+ "<param name=\"textToPath\" gui-text=\"" N_("Convert texts to paths") "\" type=\"bool\">true</param>\n"
+ "<param name=\"TnrToSymbol\" gui-text=\"" N_("Map Unicode to Symbol font") "\" type=\"bool\">true</param>\n"
+ "<param name=\"TnrToWingdings\" gui-text=\"" N_("Map Unicode to Wingdings") "\" type=\"bool\">true</param>\n"
+ "<param name=\"TnrToZapfDingbats\" gui-text=\"" N_("Map Unicode to Zapf Dingbats") "\" type=\"bool\">true</param>\n"
+ "<param name=\"UsePUA\" gui-text=\"" N_("Use MS Unicode PUA (0xF020-0xF0FF) for converted characters") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTCharPos\" gui-text=\"" N_("Compensate for PPT font bug") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTDashLine\" gui-text=\"" N_("Convert dashed/dotted lines to single lines") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTGrad2Polys\" gui-text=\"" N_("Convert gradients to colored polygon series") "\" type=\"bool\">false</param>\n"
+ "<param name=\"FixPPTPatternAsHatch\" gui-text=\"" N_("Map all fill patterns to standard WMF hatches") "\" type=\"bool\">false</param>\n"
+ "<output>\n"
+ "<extension>.wmf</extension>\n"
+ "<mimetype>image/x-wmf</mimetype>\n"
+ "<filetypename>" N_("Windows Metafile (*.wmf)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Windows Metafile") "</filetypetooltip>\n"
+ "</output>\n"
+ "</inkscape-extension>", new Wmf());
+ // clang-format on
+
+ return;
+}
+
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/wmf-inout.h b/src/extension/internal/wmf-inout.h
new file mode 100644
index 0000000..129ab18
--- /dev/null
+++ b/src/extension/internal/wmf-inout.h
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Windows Metafile Input/Output
+ */
+/* Authors:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_EXTENSION_INTERNAL_WMF_H
+#define SEEN_EXTENSION_INTERNAL_WMF_H
+
+#include <3rdparty/libuemf/uwmf.h>
+#include "extension/internal/metafile-inout.h" // picks up PNG
+#include "extension/implementation/implementation.h"
+#include "style.h"
+#include "text_reassemble.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+#define DIRTY_NONE 0x00
+#define DIRTY_TEXT 0x01
+#define DIRTY_FILL 0x02
+#define DIRTY_STROKE 0x04 // not used currently
+
+struct WMF_OBJECT {
+ int type = 0;
+ int level = 0;
+ char *record = nullptr;
+};
+using PWMF_OBJECT = WMF_OBJECT *;
+
+struct WMF_STRINGS {
+ int size = 0; // number of slots allocated in strings
+ int count = 0; // number of slots used in strings
+ char **strings = nullptr; // place to store strings
+};
+using PWMF_STRINGS = WMF_STRINGS *;
+
+struct WMF_DEVICE_CONTEXT {
+ WMF_DEVICE_CONTEXT() :
+ // SPStyle: class with constructor
+ font_name(nullptr),
+ clip_id(0),
+ stroke_set(false), stroke_mode(0), stroke_idx(0), stroke_recidx(0),
+ fill_set(false), fill_mode(0), fill_idx(0), fill_recidx(0),
+ dirty(0),
+ active_pen(-1), active_brush(-1), active_font(-1), // -1 when the default is used
+ // sizeWnd, sizeView, winorg, vieworg,
+ ScaleInX(0), ScaleInY(0),
+ ScaleOutX(0), ScaleOutY(0),
+ bkMode(U_TRANSPARENT),
+ // bkColor, textColor
+ textAlign(0)
+ // worldTransform, cur
+ {
+ sizeWnd = point16_set( 0.0, 0.0 );
+ sizeView = point16_set( 0.0, 0.0 );
+ winorg = point16_set( 0.0, 0.0 );
+ vieworg = point16_set( 0.0, 0.0 );
+ bkColor = U_RGB(255, 255, 255); // default foreground color (white)
+ textColor = U_RGB(0, 0, 0); // default foreground color (black)
+ cur = point16_set( 0.0, 0.0 );
+ };
+ SPStyle style;
+ char *font_name;
+ int clip_id; // 0 if none, else 1 + index into clips
+ bool stroke_set;
+ int stroke_mode; // enumeration from drawmode, not used if fill_set is not True
+ int stroke_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill
+ int stroke_recidx;// record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change
+ bool fill_set;
+ int fill_mode; // enumeration from drawmode, not used if fill_set is not True
+ int fill_idx; // used with DRAW_PATTERN and DRAW_IMAGE to return the appropriate fill
+ int fill_recidx; // record used to regenerate hatch when it needs to be redone due to bkmode, textmode, etc. change
+ int dirty; // holds the dirty bits for text, stroke, fill
+ int active_pen; // used when the active object is deleted to set the default values, -1 is none active
+ int active_brush; // ditto
+ int active_font; // ditto. also used to hold object number in case font needs to be remade due to textcolor change.
+ U_POINT16 sizeWnd;
+ U_POINT16 sizeView;
+ U_POINT16 winorg;
+ U_POINT16 vieworg;
+ double ScaleInX, ScaleInY;
+ double ScaleOutX, ScaleOutY;
+ uint16_t bkMode;
+ U_COLORREF bkColor;
+ U_COLORREF textColor;
+ uint16_t textAlign;
+ U_POINT16 cur;
+};
+using PWMF_DEVICE_CONTEXT = WMF_DEVICE_CONTEXT *;
+
+#define WMF_MAX_DC 128
+
+
+// like this causes a mysterious crash on the return from Wmf::open
+//typedef struct emf_callback_data {
+// this fixes it, so some confusion between this struct and the one in emf-inout???
+//typedef struct wmf_callback_data {
+// as does this
+struct WMF_CALLBACK_DATA {
+
+ WMF_CALLBACK_DATA() :
+ // dc: array, structure w/ constructor
+ level(0),
+ E2IdirY(1.0),
+ D2PscaleX(1.0), D2PscaleY(1.0),
+ PixelsInX(0), PixelsInY(0),
+ PixelsOutX(0), PixelsOutY(0),
+ ulCornerInX(0), ulCornerInY(0),
+ ulCornerOutX(0), ulCornerOutY(0),
+ mask(0),
+ arcdir(U_AD_COUNTERCLOCKWISE),
+ dwRop2(U_R2_COPYPEN), dwRop3(0),
+ id(0), drawtype(0),
+ // hatches, images, gradients, struct w/ constructor
+ tri(nullptr),
+ n_obj(0),
+ low_water(0)
+ //wmf_obj
+ {};
+
+ Glib::ustring outsvg;
+ Glib::ustring path;
+ Glib::ustring outdef;
+ Glib::ustring defs;
+
+ WMF_DEVICE_CONTEXT dc[WMF_MAX_DC+1]; // FIXME: This should be dynamic..
+ int level;
+
+ double E2IdirY; // WMF Y direction relative to Inkscape Y direction. Will be negative for MM_LOMETRIC etc.
+ double D2PscaleX,D2PscaleY; // WMF device to Inkscape Page scale.
+ float PixelsInX, PixelsInY; // size of the drawing, in WMF device pixels
+ float PixelsOutX, PixelsOutY; // size of the drawing, in Inkscape pixels
+ double ulCornerInX,ulCornerInY; // Upper left corner, from header rclBounds, in logical units
+ double ulCornerOutX,ulCornerOutY; // Upper left corner, in Inkscape pixels
+ uint32_t mask; // Draw properties
+ int arcdir; // U_AD_COUNTERCLOCKWISE 1 or U_AD_CLOCKWISE 2
+
+ uint32_t dwRop2; // Binary raster operation, 0 if none (use brush/pen unmolested)
+ uint32_t dwRop3; // Ternary raster operation, 0 if none (use brush/pen unmolested)
+
+ unsigned int id;
+ unsigned int drawtype; // one of 0 or U_WMR_FILLPATH, U_WMR_STROKEPATH, U_WMR_STROKEANDFILLPATH
+ // both of these end up in <defs> under the names shown here. These structures allow duplicates to be avoided.
+ WMF_STRINGS hatches; // hold pattern names, all like WMFhatch#_$$$$$$ where # is the WMF hatch code and $$$$$$ is the color
+ WMF_STRINGS images; // hold images, all like Image#, where # is the slot the image lives.
+ WMF_STRINGS clips; // hold clipping paths, referred to be the slot where the clipping path lives
+ TR_INFO *tri; // Text Reassembly data structure
+
+
+ int n_obj;
+ int low_water; // first object slot which _might_ be unoccupied. Everything below is filled.
+ PWMF_OBJECT wmf_obj;
+};
+using PWMF_CALLBACK_DATA = WMF_CALLBACK_DATA *;
+
+class Wmf : public Metafile
+{
+
+public:
+ Wmf(); // Empty constructor
+
+ ~Wmf() override;//Destructor
+
+ bool check(Inkscape::Extension::Extension *module) override; //Can this module load (always yes for now)
+
+ void save(Inkscape::Extension::Output *mod, // Save the given document to the given filename
+ SPDocument *doc,
+ gchar const *filename) override;
+
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+
+ static void init();//Initialize the class
+
+private:
+protected:
+ static void print_document_to_file(SPDocument *doc, const gchar *filename);
+ static double current_scale(PWMF_CALLBACK_DATA d);
+ static std::string current_matrix(PWMF_CALLBACK_DATA d, double x, double y, int useoffset);
+ static double current_rotation(PWMF_CALLBACK_DATA d);
+ static void enlarge_hatches(PWMF_CALLBACK_DATA d);
+ static int in_hatches(PWMF_CALLBACK_DATA d, char *test);
+ static uint32_t add_hatch(PWMF_CALLBACK_DATA d, uint32_t hatchType, U_COLORREF hatchColor);
+ static void enlarge_images(PWMF_CALLBACK_DATA d);
+ static int in_images(PWMF_CALLBACK_DATA d, char *test);
+ static uint32_t add_dib_image(PWMF_CALLBACK_DATA d, const char *dib, uint32_t iUsage);
+ static uint32_t add_bm16_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px);
+
+ static void enlarge_clips(PWMF_CALLBACK_DATA d);
+ static int in_clips(PWMF_CALLBACK_DATA d, const char *test);
+ static void add_clips(PWMF_CALLBACK_DATA d, const char *clippath, unsigned int logic);
+
+ static void output_style(PWMF_CALLBACK_DATA d);
+ static double _pix_x_to_point(PWMF_CALLBACK_DATA d, double px);
+ static double _pix_y_to_point(PWMF_CALLBACK_DATA d, double py);
+ static double pix_to_x_point(PWMF_CALLBACK_DATA d, double px, double py);
+ static double pix_to_y_point(PWMF_CALLBACK_DATA d, double px, double py);
+ static double pix_to_abs_size(PWMF_CALLBACK_DATA d, double px);
+ static std::string pix_to_xy(PWMF_CALLBACK_DATA d, double x, double y);
+ static void select_brush(PWMF_CALLBACK_DATA d, int index);
+ static void select_font(PWMF_CALLBACK_DATA d, int index);
+ static void select_pen(PWMF_CALLBACK_DATA d, int index);
+ static int insertable_object(PWMF_CALLBACK_DATA d);
+ static void delete_object(PWMF_CALLBACK_DATA d, int index);
+ static int insert_object(PWMF_CALLBACK_DATA d, int type, const char *record);
+ static uint32_t *unknown_chars(size_t count);
+ static void common_dib_to_image(PWMF_CALLBACK_DATA d, const char *dib,
+ double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh, uint32_t iUsage);
+ static void common_bm16_to_image(PWMF_CALLBACK_DATA d, U_BITMAP16 Bm16, const char *px,
+ double dx, double dy, double dw, double dh, int sx, int sy, int sw, int sh);
+ static int myMetaFileProc(const char *contents, unsigned int length, PWMF_CALLBACK_DATA d);
+ static void free_wmf_strings(WMF_STRINGS name);
+
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+
+#endif /* EXTENSION_INTERNAL_WMF_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/wmf-print.cpp b/src/extension/internal/wmf-print.cpp
new file mode 100644
index 0000000..cab66d3
--- /dev/null
+++ b/src/extension/internal/wmf-print.cpp
@@ -0,0 +1,1602 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Windows Metafile printing
+ */
+/* Authors:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ * David Mathog
+ *
+ * Copyright (C) 2006-2009 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+/*
+ * References:
+ * - How to Create & Play Enhanced Metafiles in Win32
+ * http://support.microsoft.com/kb/q145999/
+ * - INFO: Windows Metafile Functions & Aldus Placeable Metafiles
+ * http://support.microsoft.com/kb/q66949/
+ * - Metafile Functions
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_0whf.asp
+ * - Metafile Structures
+ * http://msdn.microsoft.com/library/en-us/gdi/metafile_5hkj.asp
+ */
+
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/elliptical-arc.h>
+
+#include <2geom/path.h>
+#include <2geom/pathvector.h>
+#include <2geom/rect.h>
+#include <2geom/curves.h>
+#include "helper/geom.h"
+#include "helper/geom-curves.h"
+
+#include "inkscape-version.h"
+
+#include "util/units.h"
+
+#include "extension/system.h"
+#include "extension/print.h"
+#include "document.h"
+#include "path-prefix.h"
+
+#include "object/sp-pattern.h"
+#include "object/sp-image.h"
+#include "object/sp-gradient.h"
+#include "object/sp-radial-gradient.h"
+#include "object/sp-linear-gradient.h"
+#include "object/sp-root.h"
+#include "object/sp-item.h"
+
+#include "path/path-boolop.h"
+
+#include <2geom/svg-path-parser.h> // to get from SVG text to Geom::Path
+
+#include "display/cairo-utils.h" // for Inkscape::Pixbuf::PF_CAIRO
+
+#include "wmf-print.h"
+
+#include <cstring>
+#include <3rdparty/libuemf/symbol_convert.h>
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+#define PXPERMETER 2835
+#define MAXDISP 2.0 // This should be set in the output dialog. This is ok for experimenting, no more than 2 pixel deviation. Not actually used at present
+
+
+/* globals */
+static double PX2WORLD; // value set in begin()
+static bool FixPPTCharPos, FixPPTDashLine, FixPPTGrad2Polys, FixPPTPatternAsHatch;
+static WMFTRACK *wt = nullptr;
+static WMFHANDLES *wht = nullptr;
+
+void PrintWmf::smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *rtl, int *ndx, float scale)
+{
+ float fdx;
+ int i;
+ int16_t *ladx;
+ const char *cptr = &string[strlen(string) + 1]; // this works because of the first fake terminator
+
+ *adx = nullptr;
+ *ky = 0.0; // set a default value
+ sscanf(cptr, "%7d", ndx);
+ if (!*ndx) {
+ return; // this could happen with an empty string
+ }
+ cptr += 7;
+ ladx = (int16_t *) malloc(*ndx * sizeof(int16_t));
+ if (!ladx) {
+ g_error("Out of memory");
+ }
+ *adx = ladx;
+ for (i = 0; i < *ndx; i++, cptr += 7, ladx++) {
+ sscanf(cptr, "%7f", &fdx);
+ *ladx = (int16_t) round(fdx * scale);
+ }
+ cptr++; // skip 2nd fake terminator
+ sscanf(cptr, "%7f", &fdx);
+ *ky = fdx;
+ cptr += 7; // advance over ky and its space
+ sscanf(cptr, "%07d", rtl);
+}
+
+PrintWmf::PrintWmf()
+{
+ // all of the class variables are initialized elsewhere, many in PrintWmf::Begin,
+}
+
+
+unsigned int PrintWmf::setup(Inkscape::Extension::Print * /*mod*/)
+{
+ return TRUE;
+}
+
+
+unsigned int PrintWmf::begin(Inkscape::Extension::Print *mod, SPDocument *doc)
+{
+ char *rec;
+ gchar const *utf8_fn = mod->get_param_string("destination");
+
+ // Typically PX2WORLD is 1200/90, using inkscape's default dpi
+ PX2WORLD = 1200.0 / Inkscape::Util::Quantity::convert(1.0, "in", "px");
+ FixPPTCharPos = mod->get_param_bool("FixPPTCharPos");
+ FixPPTDashLine = mod->get_param_bool("FixPPTDashLine");
+ FixPPTGrad2Polys = mod->get_param_bool("FixPPTGrad2Polys");
+ FixPPTPatternAsHatch = mod->get_param_bool("FixPPTPatternAsHatch");
+
+ (void) wmf_start(utf8_fn, 1000000, 250000, &wt); // Initialize the wt structure
+ (void) wmf_htable_create(128, 128, &wht); // Initialize the wht structure
+
+ // WMF header the only things that can be set are the page size in inches (w,h) and the dpi
+ // width and height in px
+ _width = doc->getWidth().value("px");
+ _height = doc->getHeight().value("px");
+
+ // initialize a few global variables
+ hbrush = hpen = 0;
+ htextalignment = U_TA_BASELINE | U_TA_LEFT;
+ use_stroke = use_fill = simple_shape = usebk = false;
+
+ Inkscape::XML::Node *nv = doc->getReprNamedView();
+ if (nv) {
+ const char *p1 = nv->attribute("pagecolor");
+ char *p2;
+ uint32_t lc = strtoul(&p1[1], &p2, 16); // it looks like "#ABC123"
+ if (*p2) {
+ lc = 0;
+ }
+ gv.bgc = _gethexcolor(lc);
+ gv.rgb[0] = (float) U_RGBAGetR(gv.bgc) / 255.0;
+ gv.rgb[1] = (float) U_RGBAGetG(gv.bgc) / 255.0;
+ gv.rgb[2] = (float) U_RGBAGetB(gv.bgc) / 255.0;
+ }
+
+ bool pageBoundingBox;
+ pageBoundingBox = mod->get_param_bool("pageBoundingBox");
+
+ Geom::Rect d;
+ if (pageBoundingBox) {
+ d = Geom::Rect::from_xywh(0, 0, _width, _height);
+ } else {
+ SPItem *doc_item = doc->getRoot();
+ Geom::OptRect bbox = doc_item->desktopVisualBounds();
+ if (bbox) {
+ d = *bbox;
+ }
+ }
+
+ d *= Geom::Scale(Inkscape::Util::Quantity::convert(1, "px", "in")); // 90 dpi inside inkscape, wmf file will be 1200 dpi
+
+ /* -1/1200 in next two lines so that WMF read in will write out again at exactly the same size */
+ float dwInchesX = d.width() - 1.0 / 1200.0;
+ float dwInchesY = d.height() - 1.0 / 1200.0;
+ int dwPxX = round(dwInchesX * 1200.0);
+ int dwPxY = round(dwInchesY * 1200.0);
+#if 0
+ float dwInchesX = d.width();
+ float dwInchesY = d.height();
+ int dwPxX = round(d.width() * 1200.0);
+ int dwPxY = round(d.height() * 1200.0);
+#endif
+
+ U_PAIRF *ps = U_PAIRF_set(dwInchesX, dwInchesY);
+ rec = U_WMRHEADER_set(ps, 1200); // Example: drawing is A4 horizontal, 1200 dpi
+ free(ps);
+ if (!rec) {
+ g_warning("Failed in PrintWmf::begin at WMRHEADER");
+ return -1;
+ }
+ (void) wmf_header_append((U_METARECORD *)rec, wt, 1);
+
+ rec = U_WMRSETWINDOWEXT_set(point16_set(dwPxX, dwPxY));
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at WMRSETWINDOWEXT");
+ return -1;
+ }
+
+ rec = U_WMRSETWINDOWORG_set(point16_set(0, 0));
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at WMRSETWINDOWORG");
+ return -1;
+ }
+
+ rec = U_WMRSETMAPMODE_set(U_MM_ANISOTROPIC);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at WMRSETMAPMODE");
+ return -1;
+ }
+
+ /* set some parameters, else the program that reads the WMF may default to other values */
+
+ rec = U_WMRSETBKMODE_set(U_TRANSPARENT);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at U_WMRSETBKMODE");
+ return -1;
+ }
+
+ hpolyfillmode = U_WINDING;
+ rec = U_WMRSETPOLYFILLMODE_set(U_WINDING);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at U_WMRSETPOLYFILLMODE");
+ return -1;
+ }
+
+ // Text alignment: (only changed if RTL text is encountered )
+ // - (x,y) coordinates received by this filter are those of the point where the text
+ // actually starts, and already takes into account the text object's alignment;
+ // - for this reason, the WMF text alignment must always be TA_BASELINE|TA_LEFT.
+ rec = U_WMRSETTEXTALIGN_set(U_TA_BASELINE | U_TA_LEFT);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at U_WMRSETTEXTALIGN_set");
+ return -1;
+ }
+
+ htextcolor_rgb[0] = htextcolor_rgb[1] = htextcolor_rgb[2] = 0.0;
+ rec = U_WMRSETTEXTCOLOR_set(U_RGB(0, 0, 0));
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at U_WMRSETTEXTCOLOR_set");
+ return -1;
+ }
+
+ rec = U_WMRSETROP2_set(U_R2_COPYPEN);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at U_WMRSETROP2");
+ return -1;
+ }
+
+ hmiterlimit = 5;
+ rec = wmiterlimit_set(5);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at wmiterlimit_set");
+ return -1;
+ }
+
+
+ // create a pen as object 0. We never use it (except by mistake). Its purpose it to make all of the other object indices >=1
+ U_PEN up = U_PEN_set(U_PS_SOLID, 1, colorref_set(0, 0, 0));
+ uint32_t Pen;
+ rec = wcreatepenindirect_set(&Pen, wht, up);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at wcreatepenindirect_set");
+ return -1;
+ }
+
+ // create a null pen. If no specific pen is set, this is used
+ up = U_PEN_set(U_PS_NULL, 1, colorref_set(0, 0, 0));
+ rec = wcreatepenindirect_set(&hpen_null, wht, up);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at wcreatepenindirect_set");
+ return -1;
+ }
+ destroy_pen(); // make this pen active
+
+ // create a null brush. If no specific brush is set, this is used
+ U_WLOGBRUSH lb = U_WLOGBRUSH_set(U_BS_NULL, U_RGB(0, 0, 0), U_HS_HORIZONTAL);
+ rec = wcreatebrushindirect_set(&hbrush_null, wht, lb);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_warning("Failed in PrintWmf::begin at wcreatebrushindirect_set");
+ return -1;
+ }
+ destroy_brush(); // make this brush active
+
+ return 0;
+}
+
+
+unsigned int PrintWmf::finish(Inkscape::Extension::Print * /*mod*/)
+{
+ char *rec;
+ if (!wt) {
+ return 0;
+ }
+
+ // get rid of null brush
+ rec = wdeleteobject_set(&hbrush_null, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::finish at wdeleteobject_set null brush");
+ }
+
+ // get rid of null pen
+ rec = wdeleteobject_set(&hpen_null, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::finish at wdeleteobject_set null pen");
+ }
+
+ // get rid of object 0, which was a pen that was used to shift the other object indices to >=1.
+ hpen = 0;
+ rec = wdeleteobject_set(&hpen, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::finish at wdeleteobject_set filler object");
+ }
+
+ rec = U_WMREOF_set(); // generate the EOF record
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::finish");
+ }
+ (void) wmf_finish(wt); // Finalize and write out the WMF
+ uwmf_free(&wt); // clean up
+ wmf_htable_free(&wht); // clean up
+
+ return 0;
+}
+
+// fcolor is defined when gradients are being expanded, it is the color of one stripe or ring.
+int PrintWmf::create_brush(SPStyle const *style, U_COLORREF *fcolor)
+{
+ float rgb[3];
+ char *rec;
+ U_WLOGBRUSH lb;
+ uint32_t brush, fmode;
+ MFDrawMode fill_mode;
+ Inkscape::Pixbuf *pixbuf;
+ uint32_t brushStyle;
+ int hatchType;
+ U_COLORREF hatchColor;
+ U_COLORREF bkColor;
+ uint32_t width = 0; // quiets a harmless compiler warning, initialization not otherwise required.
+ uint32_t height = 0;
+
+ if (!wt) {
+ return 0;
+ }
+
+ // set a default fill in case we can't figure out a better way to do it
+ fmode = U_ALTERNATE;
+ fill_mode = DRAW_PAINT;
+ brushStyle = U_BS_SOLID;
+ hatchType = U_HS_SOLIDCLR;
+ bkColor = U_RGB(0, 0, 0);
+ if (fcolor) {
+ hatchColor = *fcolor;
+ } else {
+ hatchColor = U_RGB(0, 0, 0);
+ }
+
+ if (!fcolor && style) {
+ if (style->fill.isColor()) {
+ fill_mode = DRAW_PAINT;
+ /* Dead assignment: Value stored to 'opacity' is never read
+ float opacity = SP_SCALE24_TO_FLOAT(style->fill_opacity.value);
+ if (opacity <= 0.0) {
+ opacity = 0.0; // basically the same as no fill
+ }
+ */
+ style->fill.value.color.get_rgb_floatv(rgb);
+ hatchColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]);
+
+ fmode = style->fill_rule.computed == 0 ? U_WINDING : (style->fill_rule.computed == 2 ? U_ALTERNATE : U_ALTERNATE);
+ } else if (SP_IS_PATTERN(SP_STYLE_FILL_SERVER(style))) { // must be paint-server
+ SPPaintServer *paintserver = style->fill.value.href->getObject();
+ SPPattern *pat = SP_PATTERN(paintserver);
+ double dwidth = pat->width();
+ double dheight = pat->height();
+ width = dwidth;
+ height = dheight;
+ brush_classify(pat, 0, &pixbuf, &hatchType, &hatchColor, &bkColor);
+ if (pixbuf) {
+ fill_mode = DRAW_IMAGE;
+ } else { // pattern
+ fill_mode = DRAW_PATTERN;
+ if (hatchType == -1) { // Not a standard hatch, so force it to something
+ hatchType = U_HS_CROSS;
+ hatchColor = U_RGB(0xFF, 0xC3, 0xC3);
+ }
+ }
+ if (FixPPTPatternAsHatch) {
+ if (hatchType == -1) { // image or unclassified
+ fill_mode = DRAW_PATTERN;
+ hatchType = U_HS_DIAGCROSS;
+ hatchColor = U_RGB(0xFF, 0xC3, 0xC3);
+ }
+ }
+ brushStyle = U_BS_HATCHED;
+ } else if (SP_IS_GRADIENT(SP_STYLE_FILL_SERVER(style))) { // must be a gradient
+ // currently we do not do anything with gradients, the code below just sets the color to the average of the stops
+ SPPaintServer *paintserver = style->fill.value.href->getObject();
+ SPLinearGradient *lg = nullptr;
+ SPRadialGradient *rg = nullptr;
+
+ if (SP_IS_LINEARGRADIENT(paintserver)) {
+ lg = SP_LINEARGRADIENT(paintserver);
+ 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);
+ rg->ensureVector(); // when exporting from commandline, vector is not built
+ fill_mode = DRAW_RADIAL_GRADIENT;
+ } else {
+ // default fill
+ }
+
+ if (rg) {
+ if (FixPPTGrad2Polys) {
+ return hold_gradient(rg, fill_mode);
+ } else {
+ hatchColor = avg_stop_color(rg);
+ }
+ } else if (lg) {
+ if (FixPPTGrad2Polys) {
+ return hold_gradient(lg, fill_mode);
+ } else {
+ hatchColor = avg_stop_color(lg);
+ }
+ }
+ }
+ } else { // if (!style)
+ // default fill
+ }
+
+ switch (fill_mode) {
+ case DRAW_LINEAR_GRADIENT: // fill with average color unless gradients are converted to slices
+ case DRAW_RADIAL_GRADIENT: // ditto
+ case DRAW_PAINT:
+ case DRAW_PATTERN:
+ // SVG text has no background attribute, so OPAQUE mode ALWAYS cancels after the next draw, otherwise it would mess up future text output.
+ if (usebk) {
+ rec = U_WMRSETBKCOLOR_set(bkColor);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_brush at U_WMRSETBKCOLOR_set");
+ }
+ rec = U_WMRSETBKMODE_set(U_OPAQUE);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_brush at U_WMRSETBKMODE_set");
+ }
+ }
+ lb = U_WLOGBRUSH_set(brushStyle, hatchColor, hatchType);
+ rec = wcreatebrushindirect_set(&brush, wht, lb);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_brush at createbrushindirect_set");
+ }
+ break;
+ case DRAW_IMAGE:
+ char *px;
+ char *rgba_px;
+ uint32_t cbPx;
+ uint32_t colortype;
+ U_RGBQUAD *ct;
+ int numCt;
+ U_BITMAPINFOHEADER Bmih;
+ U_BITMAPINFO *Bmi;
+ rgba_px = (char *) pixbuf->pixels(); // Do NOT free this!!!
+ colortype = U_BCBM_COLOR32;
+ (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, rgba_px, width, height, width * 4, colortype, 0, 1);
+ // pixbuf can be either PF_CAIRO or PF_GDK, and these have R and B bytes swapped
+ if (pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) { swapRBinRGBA(px, width * height); }
+ Bmih = bitmapinfoheader_set(width, height, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0);
+ Bmi = bitmapinfo_set(Bmih, ct);
+ rec = wcreatedibpatternbrush_srcdib_set(&brush, wht, U_DIB_RGB_COLORS, Bmi, cbPx, px);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_brush at createdibpatternbrushpt_set");
+ }
+ free(px);
+ free(Bmi); // ct will be NULL because of colortype
+ break;
+ }
+
+ hbrush = brush; // need this later for destroy_brush
+ rec = wselectobject_set(brush, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_brush at wselectobject_set");
+ }
+
+ if (fmode != hpolyfillmode) {
+ hpolyfillmode = fmode;
+ rec = U_WMRSETPOLYFILLMODE_set(fmode);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_brush at U_WMRSETPOLYFILLMODE_set");
+ }
+ }
+
+ return 0;
+}
+
+
+void PrintWmf::destroy_brush()
+{
+ char *rec;
+ // WMF lets any object be deleted whenever, and the chips fall where they may...
+ if (hbrush) {
+ rec = wdeleteobject_set(&hbrush, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::destroy_brush");
+ }
+ hbrush = 0;
+ }
+
+ // (re)select the null brush
+
+ rec = wselectobject_set(hbrush_null, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::destroy_brush");
+ }
+}
+
+
+int PrintWmf::create_pen(SPStyle const *style, const Geom::Affine &transform)
+{
+ char *rec = nullptr;
+ uint32_t pen;
+ uint32_t penstyle;
+ U_COLORREF penColor;
+ U_PEN up;
+ int modstyle;
+
+ if (!wt) {
+ return 0;
+ }
+
+ // set a default stroke in case we can't figure out a better way to do it
+ penstyle = U_PS_SOLID;
+ modstyle = 0;
+ penColor = U_RGB(0, 0, 0);
+ uint32_t linewidth = 1;
+
+ if (style) { // override some or all of the preceding
+ float rgb[3];
+
+ // WMF does not support hatched, bitmap, or gradient pens, just set the color.
+ style->stroke.value.color.get_rgb_floatv(rgb);
+ penColor = U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]);
+
+ using Geom::X;
+ using Geom::Y;
+
+ Geom::Point zero(0, 0);
+ Geom::Point one(1, 1);
+ Geom::Point p0(zero * transform);
+ Geom::Point p1(one * transform);
+ Geom::Point p(p1 - p0);
+
+ double scale = sqrt((p[X] * p[X]) + (p[Y] * p[Y])) / sqrt(2);
+
+ if (!style->stroke_width.computed) {
+ return 0; //if width is 0 do not (reset) the pen, it should already be NULL_PEN
+ }
+ linewidth = MAX(1, (uint32_t) round(scale * style->stroke_width.computed * PX2WORLD));
+
+ // most WMF readers will ignore linecap and linejoin, but set them anyway. Inkscape itself can read them back in.
+
+ if (style->stroke_linecap.computed == 0) {
+ modstyle |= U_PS_ENDCAP_FLAT;
+ } else if (style->stroke_linecap.computed == 1) {
+ modstyle |= U_PS_ENDCAP_ROUND;
+ } else {
+ modstyle |= U_PS_ENDCAP_SQUARE;
+ }
+
+ if (style->stroke_linejoin.computed == 0) {
+ float miterlimit = style->stroke_miterlimit.value; // This is a ratio.
+ if (miterlimit < 1) {
+ miterlimit = 1;
+ }
+
+ // most WMF readers will ignore miterlimit, but set it anyway. Inkscape itself can read it back in
+ if ((uint32_t)miterlimit != hmiterlimit) {
+ hmiterlimit = (uint32_t)miterlimit;
+ rec = wmiterlimit_set((uint32_t) miterlimit);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_pen at wmiterlimit_set");
+ }
+ }
+ modstyle |= U_PS_JOIN_MITER;
+ } else if (style->stroke_linejoin.computed == 1) {
+ modstyle |= U_PS_JOIN_ROUND;
+ } else {
+ modstyle |= U_PS_JOIN_BEVEL;
+ }
+
+ if (!style->stroke_dasharray.values.empty()) {
+ if (!FixPPTDashLine) { // if this is set code elsewhere will break dots/dashes into many smaller lines.
+ int n_dash = style->stroke_dasharray.values.size();
+ /* options are dash, dot, dashdot and dashdotdot. Try to pick the closest one. */
+ int mark_short=INT_MAX;
+ int mark_long =0;
+ int i;
+ for (i=0;i<n_dash;i++) {
+ int mark = style->stroke_dasharray.values[i].value;
+ if (mark > mark_long) {
+ mark_long = mark;
+ }
+ if (mark < mark_short) {
+ mark_short = mark;
+ }
+ }
+ if(mark_long == mark_short){ // only one mark size
+ penstyle = U_PS_DOT;
+ }
+ else if (n_dash==2) {
+ penstyle = U_PS_DASH;
+ }
+ else if (n_dash==4) {
+ penstyle = U_PS_DASHDOT;
+ }
+ else {
+ penstyle = U_PS_DASHDOTDOT;
+ }
+ }
+ }
+
+ }
+
+ up = U_PEN_set(penstyle | modstyle, linewidth, penColor);
+ rec = wcreatepenindirect_set(&pen, wht, up);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_pen at wcreatepenindirect_set");
+ }
+
+ rec = wselectobject_set(pen, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::create_pen at wselectobject_set");
+ }
+ hpen = pen; // need this later for destroy_pen
+
+ return 0;
+}
+
+// delete the defined pen object
+void PrintWmf::destroy_pen()
+{
+ char *rec = nullptr;
+ // WMF lets any object be deleted whenever, and the chips fall where they may...
+ if (hpen) {
+ rec = wdeleteobject_set(&hpen, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::destroy_pen");
+ }
+ hpen = 0;
+ }
+
+ // (re)select the null pen
+
+ rec = wselectobject_set(hpen_null, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::destroy_pen");
+ }
+}
+
+
+unsigned int PrintWmf::fill(
+ Inkscape::Extension::Print * /*mod*/,
+ Geom::PathVector const &pathv, Geom::Affine const & /*transform*/, SPStyle const *style,
+ Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/)
+{
+ using Geom::X;
+ using Geom::Y;
+
+ Geom::Affine tf = m_tr_stack.top();
+
+ use_fill = true;
+ use_stroke = false;
+
+ fill_transform = tf;
+
+ if (create_brush(style, nullptr)) {
+ /*
+ Handle gradients. Uses modified livarot as 2geom boolops is currently broken.
+ Can handle gradients with multiple stops.
+
+ The overlap is needed to avoid antialiasing artifacts when edges are not strictly aligned on pixel boundaries.
+ There is an inevitable loss of accuracy saving through an WMF file because of the integer coordinate system.
+ Keep the overlap quite large so that loss of accuracy does not remove an overlap.
+ */
+ destroy_pen(); //this sets the NULL_PEN, otherwise gradient slices may display with boundaries, see longer explanation below
+ Geom::Path cutter;
+ float rgb[3];
+ U_COLORREF wc, c1, c2;
+ FillRule frb = SPWR_to_LVFR((SPWindRule) style->fill_rule.computed);
+ double doff, doff_base, doff_range;
+ double divisions = 128.0;
+ int nstops;
+ int istop = 1;
+ float opa; // opacity at stop
+
+ SPRadialGradient *tg = (SPRadialGradient *)(gv.grad); // linear/radial are the same here
+ nstops = tg->vector.stops.size();
+ tg->vector.stops[0].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[0].opacity;
+ c1 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+ tg->vector.stops[nstops - 1].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[nstops - 1].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+
+ doff = 0.0;
+ doff_base = 0.0;
+ doff_range = tg->vector.stops[1].offset; // next or last stop
+
+ if (gv.mode == DRAW_RADIAL_GRADIENT) {
+ Geom::Point xv = gv.p2 - gv.p1; // X' vector
+ Geom::Point yv = gv.p3 - gv.p1; // Y' vector
+ Geom::Point xuv = Geom::unit_vector(xv); // X' unit vector
+ double rx = hypot(xv[X], xv[Y]);
+ double ry = hypot(yv[X], yv[Y]);
+ double range = fmax(rx, ry); // length along the gradient
+ double step = range / divisions; // adequate approximation for gradient
+ double overlap = step / 4.0; // overlap slices slightly
+ double start;
+ double stop;
+ Geom::PathVector pathvc, pathvr;
+
+ /* radial gradient might stop part way through the shape, fill with outer color from there to "infinity".
+ Do this first so that outer colored ring will overlay it.
+ */
+ pathvc = center_elliptical_hole_as_SVG_PathV(gv.p1, rx * (1.0 - overlap / range), ry * (1.0 - overlap / range), asin(xuv[Y]));
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_oddEven, frb);
+ wc = weight_opacity(c2);
+ (void) create_brush(style, &wc);
+ print_pathv(pathvr, fill_transform);
+
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+
+ for (start = 0.0; start < range; start += step, doff += 1. / divisions) {
+ stop = start + step + overlap;
+ if (stop > range) {
+ stop = range;
+ }
+ wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base));
+ (void) create_brush(style, &wc);
+
+ pathvc = center_elliptical_ring_as_SVG_PathV(gv.p1, rx * start / range, ry * start / range, rx * stop / range, ry * stop / range, asin(xuv[Y]));
+
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform); // show the intersection
+
+ if (doff >= doff_range - doff_base) {
+ istop++;
+ if (istop >= nstops) {
+ continue; // could happen on a rounding error
+ }
+ doff_base = doff_range;
+ doff_range = tg->vector.stops[istop].offset; // next or last stop
+ c1 = c2;
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+ }
+ }
+ } else if (gv.mode == DRAW_LINEAR_GRADIENT) {
+ Geom::Point uv = Geom::unit_vector(gv.p2 - gv.p1); // unit vector
+ Geom::Point puv = uv.cw(); // perp. to unit vector
+ double range = Geom::distance(gv.p1, gv.p2); // length along the gradient
+ double step = range / divisions; // adequate approximation for gradient
+ double overlap = step / 4.0; // overlap slices slightly
+ double start;
+ double stop;
+ Geom::PathVector pathvc, pathvr;
+
+ /* before lower end of gradient, overlap first slice position */
+ wc = weight_opacity(c1);
+ (void) create_brush(style, &wc);
+ pathvc = rect_cutter(gv.p1, uv * (overlap), uv * (-50000.0), puv * 50000.0);
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform);
+
+ /* after high end of gradient, overlap last slice position */
+ wc = weight_opacity(c2);
+ (void) create_brush(style, &wc);
+ pathvc = rect_cutter(gv.p2, uv * (-overlap), uv * (50000.0), puv * 50000.0);
+ pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform);
+
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+
+ for (start = 0.0; start < range; start += step, doff += 1. / divisions) {
+ stop = start + step + overlap;
+ if (stop > range) {
+ stop = range;
+ }
+ pathvc = rect_cutter(gv.p1, uv * start, uv * stop, puv * 50000.0);
+
+ wc = weight_colors(c1, c2, (doff - doff_base) / (doff_range - doff_base));
+ (void) create_brush(style, &wc);
+ Geom::PathVector pathvr = sp_pathvector_boolop(pathvc, pathv, bool_op_inters, (FillRule) fill_nonZero, frb);
+ print_pathv(pathvr, fill_transform); // show the intersection
+
+ if (doff >= doff_range - doff_base) {
+ istop++;
+ if (istop >= nstops) {
+ continue; // could happen on a rounding error
+ }
+ doff_base = doff_range;
+ doff_range = tg->vector.stops[istop].offset; // next or last stop
+ c1 = c2;
+ tg->vector.stops[istop].color.get_rgb_floatv(rgb);
+ opa = tg->vector.stops[istop].opacity;
+ c2 = U_RGBA(255 * rgb[0], 255 * rgb[1], 255 * rgb[2], 255 * opa);
+ }
+ }
+ } else {
+ g_error("Fatal programming error in PrintWmf::fill, invalid gradient type detected");
+ }
+ use_fill = false; // gradients handled, be sure stroke does not use stroke and fill
+ } else {
+ /*
+ Inkscape was not calling create_pen for objects with no border.
+ This was because it never called stroke() (next method).
+ PPT, and presumably others, pick whatever they want for the border if it is not specified, so no border can
+ become a visible border.
+ To avoid this force the pen to NULL_PEN if we can determine that no pen will be needed after the fill.
+ */
+ if (style->stroke.noneSet || style->stroke_width.computed == 0.0) {
+ destroy_pen(); //this sets the NULL_PEN
+ }
+
+ /* postpone fill in case stroke also required AND all stroke paths closed
+ Dashes converted to line segments will "open" a closed path.
+ */
+ bool all_closed = true;
+ for (const auto & pit : pathv) {
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ if (pit.end_default() != pit.end_closed()) {
+ all_closed = false;
+ }
+ }
+ }
+ if (
+ (style->stroke.isNone() || style->stroke.noneSet || style->stroke_width.computed == 0.0) ||
+ (!style->stroke_dasharray.values.empty() && FixPPTDashLine) ||
+ !all_closed
+ ) {
+ print_pathv(pathv, fill_transform); // do any fills. side effect: clears fill_pathv
+ use_fill = false;
+ }
+ }
+
+ return 0;
+}
+
+
+unsigned int PrintWmf::stroke(
+ Inkscape::Extension::Print * /*mod*/,
+ Geom::PathVector const &pathv, const Geom::Affine &/*transform*/, const SPStyle *style,
+ Geom::OptRect const &/*pbox*/, Geom::OptRect const &/*dbox*/, Geom::OptRect const &/*bbox*/)
+{
+
+ char *rec = nullptr;
+ Geom::Affine tf = m_tr_stack.top();
+
+ use_stroke = true;
+ // use_fill was set in ::fill, if it is needed, if not, the null brush is used, it should be already set
+
+ if (create_pen(style, tf)) {
+ return 0;
+ }
+
+ if (!style->stroke_dasharray.values.empty() && FixPPTDashLine) {
+ // convert the path, gets its complete length, and then make a new path with parameter length instead of t
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw; // pathv-> sbasis
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw2; // sbasis using arc length parameter
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > tmp_pathpw3; // new (discontinuous) path, composed of dots/dashes
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > first_frag; // first fragment, will be appended at end
+ int n_dash = style->stroke_dasharray.values.size();
+ int i = 0; //dash index
+ double tlength; // length of tmp_pathpw
+ double slength = 0.0; // start of gragment
+ double elength; // end of gragment
+ for (const auto & i : pathv) {
+ tmp_pathpw.concat(i.toPwSb());
+ }
+ tlength = length(tmp_pathpw, 0.1);
+ tmp_pathpw2 = arc_length_parametrization(tmp_pathpw);
+
+ // go around the dash array repeatedly until the entire path is consumed (but not beyond).
+ while (slength < tlength) {
+ elength = slength + style->stroke_dasharray.values[i++].value;
+ if (elength > tlength) {
+ elength = tlength;
+ }
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > fragment(portion(tmp_pathpw2, slength, elength));
+ if (slength) {
+ tmp_pathpw3.concat(fragment);
+ } else {
+ first_frag = fragment;
+ }
+ slength = elength;
+ slength += style->stroke_dasharray.values[i++].value; // the gap
+ if (i >= n_dash) {
+ i = 0;
+ }
+ }
+ tmp_pathpw3.concat(first_frag); // may merge line around start point
+ Geom::PathVector out_pathv = Geom::path_from_piecewise(tmp_pathpw3, 0.01);
+ print_pathv(out_pathv, tf);
+ } else {
+ print_pathv(pathv, tf);
+ }
+
+ use_stroke = false;
+ use_fill = false;
+
+ if (usebk) { // OPAQUE was set, revert to TRANSPARENT
+ usebk = false;
+ rec = U_WMRSETBKMODE_set(U_TRANSPARENT);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::stroke at U_WMRSETBKMODE_set");
+ }
+ }
+
+ return 0;
+}
+
+
+// Draws simple_shapes, those with closed WMR_* primitives, like polygons, rectangles and ellipses.
+// These use whatever the current pen/brush are and need not be followed by a FILLPATH or STROKEPATH.
+// For other paths it sets a few flags and returns.
+bool PrintWmf::print_simple_shape(Geom::PathVector const &pathv, const Geom::Affine &transform)
+{
+
+ Geom::PathVector pv = pathv_to_linear(pathv * transform, MAXDISP);
+
+ int nodes = 0;
+ int moves = 0;
+ int lines = 0;
+ int curves = 0;
+ char *rec = nullptr;
+
+ for (const auto & pit : pv) {
+ moves++;
+ nodes++;
+
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ nodes++;
+
+ if (is_straight_curve(*cit)) {
+ lines++;
+ } else if (dynamic_cast<Geom::CubicBezier const *>(&*cit)) {
+ curves++;
+ }
+ }
+ }
+
+ if (!nodes) {
+ return false;
+ }
+
+ U_POINT16 *lpPoints = new U_POINT16[moves + lines + curves * 3];
+ int i = 0;
+
+ /** For all Subpaths in the <path> */
+
+ for (const auto & pit : pv) {
+ using Geom::X;
+ using Geom::Y;
+
+ Geom::Point p0 = pit.initialPoint();
+
+ p0[X] = (p0[X] * PX2WORLD);
+ p0[Y] = (p0[Y] * PX2WORLD);
+
+ int32_t const x0 = (int32_t) round(p0[X]);
+ int32_t const y0 = (int32_t) round(p0[Y]);
+
+ lpPoints[i].x = x0;
+ lpPoints[i].y = y0;
+ i = i + 1;
+
+ /** For all segments in the subpath */
+
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ if (is_straight_curve(*cit)) {
+ //Geom::Point p0 = cit->initialPoint();
+ Geom::Point p1 = cit->finalPoint();
+
+ //p0[X] = (p0[X] * PX2WORLD);
+ p1[X] = (p1[X] * PX2WORLD);
+ //p0[Y] = (p0[Y] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+
+ //int32_t const x0 = (int32_t) round(p0[X]);
+ //int32_t const y0 = (int32_t) round(p0[Y]);
+ int32_t const x1 = (int32_t) round(p1[X]);
+ int32_t const y1 = (int32_t) round(p1[Y]);
+
+ lpPoints[i].x = x1;
+ lpPoints[i].y = y1;
+ i = i + 1;
+ } else if (Geom::CubicBezier const *cubic = dynamic_cast<Geom::CubicBezier const *>(&*cit)) {
+ std::vector<Geom::Point> points = cubic->controlPoints();
+ //Geom::Point p0 = points[0];
+ Geom::Point p1 = points[1];
+ Geom::Point p2 = points[2];
+ Geom::Point p3 = points[3];
+
+ //p0[X] = (p0[X] * PX2WORLD);
+ p1[X] = (p1[X] * PX2WORLD);
+ p2[X] = (p2[X] * PX2WORLD);
+ p3[X] = (p3[X] * PX2WORLD);
+ //p0[Y] = (p0[Y] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+ p2[Y] = (p2[Y] * PX2WORLD);
+ p3[Y] = (p3[Y] * PX2WORLD);
+
+ //int32_t const x0 = (int32_t) round(p0[X]);
+ //int32_t const y0 = (int32_t) round(p0[Y]);
+ int32_t const x1 = (int32_t) round(p1[X]);
+ int32_t const y1 = (int32_t) round(p1[Y]);
+ int32_t const x2 = (int32_t) round(p2[X]);
+ int32_t const y2 = (int32_t) round(p2[Y]);
+ int32_t const x3 = (int32_t) round(p3[X]);
+ int32_t const y3 = (int32_t) round(p3[Y]);
+
+ lpPoints[i].x = x1;
+ lpPoints[i].y = y1;
+ lpPoints[i + 1].x = x2;
+ lpPoints[i + 1].y = y2;
+ lpPoints[i + 2].x = x3;
+ lpPoints[i + 2].y = y3;
+ i = i + 3;
+ }
+ }
+ }
+
+ bool done = false;
+ bool closed = (lpPoints[0].x == lpPoints[i - 1].x) && (lpPoints[0].y == lpPoints[i - 1].y);
+ bool polygon = false;
+ bool rectangle = false;
+ bool ellipse = false;
+
+ if (moves == 1 && moves + lines == nodes && closed) {
+ polygon = true;
+ // if (nodes==5) { // disable due to LP Bug 407394
+ // if (lpPoints[0].x == lpPoints[3].x && lpPoints[1].x == lpPoints[2].x &&
+ // lpPoints[0].y == lpPoints[1].y && lpPoints[2].y == lpPoints[3].y)
+ // {
+ // rectangle = true;
+ // }
+ // }
+ } else if (moves == 1 && nodes == 5 && moves + curves == nodes && closed) {
+ // if (lpPoints[0].x == lpPoints[1].x && lpPoints[1].x == lpPoints[11].x &&
+ // lpPoints[5].x == lpPoints[6].x && lpPoints[6].x == lpPoints[7].x &&
+ // lpPoints[2].x == lpPoints[10].x && lpPoints[3].x == lpPoints[9].x && lpPoints[4].x == lpPoints[8].x &&
+ // lpPoints[2].y == lpPoints[3].y && lpPoints[3].y == lpPoints[4].y &&
+ // lpPoints[8].y == lpPoints[9].y && lpPoints[9].y == lpPoints[10].y &&
+ // lpPoints[5].y == lpPoints[1].y && lpPoints[6].y == lpPoints[0].y && lpPoints[7].y == lpPoints[11].y)
+ // { // disable due to LP Bug 407394
+ // ellipse = true;
+ // }
+ }
+
+ if (polygon || ellipse) {
+ // pens and brushes already set by caller, do not touch them
+
+ if (polygon) {
+ if (rectangle) {
+ U_RECT16 rcl = U_RECT16_set((U_POINT16) {
+ lpPoints[0].x, lpPoints[0].y
+ }, (U_POINT16) {
+ lpPoints[2].x, lpPoints[2].y
+ });
+ rec = U_WMRRECTANGLE_set(rcl);
+ } else {
+ rec = U_WMRPOLYGON_set(nodes, lpPoints);
+ }
+ } else if (ellipse) {
+ U_RECT16 rcl = U_RECT16_set((U_POINT16) {
+ lpPoints[6].x, lpPoints[3].y
+ }, (U_POINT16) {
+ lpPoints[0].x, lpPoints[9].y
+ });
+ rec = U_WMRELLIPSE_set(rcl);
+ }
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::print_simple_shape at retangle/ellipse/polygon");
+ }
+
+ done = true;
+
+ }
+
+ delete[] lpPoints;
+
+ return done;
+}
+
+/** Some parts based on win32.cpp by Lauris Kaplinski <lauris@kaplinski.com>. Was a part of Inkscape
+ in the past (or will be in the future?) Not in current trunk. (4/19/2012)
+
+ Limitations of this code:
+ 1. Images lose their rotation, one corner stays in the same place.
+ 2. Transparency is lost on export. (A limitation of the WMF format.)
+ 3. Probably messes up if row stride != w*4
+ 4. There is still a small memory leak somewhere, possibly in a pixbuf created in a routine
+ that calls this one and passes px, but never removes the rest of the pixbuf. The first time
+ this is called it leaked 5M (in one test) and each subsequent call leaked around 200K more.
+ If this routine is reduced to
+ if(1)return(0);
+ and called for a single 1280 x 1024 image then the program leaks 11M per call, or roughly the
+ size of two bitmaps.
+*/
+
+unsigned int PrintWmf::image(
+ Inkscape::Extension::Print * /* module */, /** not used */
+ unsigned char *rgba_px, /** array of pixel values, Gdk::Pixbuf bitmap format */
+ unsigned int w, /** width of bitmap */
+ unsigned int h, /** height of bitmap */
+ unsigned int rs, /** row stride (normally w*4) */
+ Geom::Affine const &tf_rect, /** affine transform only used for defining location and size of rect, for all other transforms, use the one from m_tr_stack */
+ SPStyle const * /*style*/) /** provides indirect link to image object */
+{
+ double x1, y1, dw, dh;
+ char *rec = nullptr;
+ Geom::Affine tf = m_tr_stack.top();
+
+ rec = U_WMRSETSTRETCHBLTMODE_set(U_COLORONCOLOR);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::image at EMRHEADER");
+ }
+
+ x1 = tf_rect[4];
+ y1 = tf_rect[5];
+ dw = ((double) w) * tf_rect[0];
+ dh = ((double) h) * tf_rect[3];
+ Geom::Point pLL(x1, y1);
+ Geom::Point pLL2 = pLL * tf; //location of LL corner in Inkscape coordinates
+
+ /* adjust scale of w and h. This works properly when there is no rotation. The values are
+ a bit strange when there is rotation, but since WMF cannot handle rotation in any case, all
+ answers are equally wrong.
+ */
+ Geom::Point pWH(dw, dh);
+ Geom::Point pWH2 = pWH * tf.withoutTranslation();
+
+ char *px;
+ uint32_t cbPx;
+ uint32_t colortype;
+ U_RGBQUAD *ct;
+ int numCt;
+ U_BITMAPINFOHEADER Bmih;
+ U_BITMAPINFO *Bmi;
+ colortype = U_BCBM_COLOR32;
+ (void) RGBA_to_DIB(&px, &cbPx, &ct, &numCt, (char *) rgba_px, w, h, w * 4, colortype, 0, 1);
+ Bmih = bitmapinfoheader_set(w, h, 1, colortype, U_BI_RGB, 0, PXPERMETER, PXPERMETER, numCt, 0);
+ Bmi = bitmapinfo_set(Bmih, ct);
+
+ U_POINT16 Dest = point16_set(round(pLL2[Geom::X] * PX2WORLD), round(pLL2[Geom::Y] * PX2WORLD));
+ U_POINT16 cDest = point16_set(round(pWH2[Geom::X] * PX2WORLD), round(pWH2[Geom::Y] * PX2WORLD));
+ U_POINT16 Src = point16_set(0, 0);
+ U_POINT16 cSrc = point16_set(w, h);
+ rec = U_WMRSTRETCHDIB_set(
+ Dest, //! Destination UL corner in logical units
+ cDest, //! Destination W & H in logical units
+ Src, //! Source UL corner in logical units
+ cSrc, //! Source W & H in logical units
+ U_DIB_RGB_COLORS, //! DIBColors Enumeration
+ U_SRCCOPY, //! RasterOPeration Enumeration
+ Bmi, //! (Optional) bitmapbuffer (U_BITMAPINFO section)
+ h * rs, //! size in bytes of px
+ px //! (Optional) bitmapbuffer (U_BITMAPINFO section)
+ );
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::image at U_WMRSTRETCHDIB_set");
+ }
+ free(px);
+ free(Bmi);
+ if (numCt) {
+ free(ct);
+ }
+ return 0;
+}
+
+// may also be called with a simple_shape or an empty path, whereupon it just returns without doing anything
+unsigned int PrintWmf::print_pathv(Geom::PathVector const &pathv, const Geom::Affine &transform)
+{
+ char *rec = nullptr;
+ U_POINT16 *pt16hold, *pt16ptr;
+ uint16_t *n16hold;
+ uint16_t *n16ptr;
+
+ simple_shape = print_simple_shape(pathv, transform);
+ if (!simple_shape && !pathv.empty()) {
+ // WMF does not have beziers, need to convert to ONLY linears with something like this:
+ Geom::PathVector pv = pathv_to_linear(pathv * transform, MAXDISP);
+
+ /** For all Subpaths in the <path> */
+
+ /* If the path consists entirely of closed subpaths use one polypolygon.
+ Otherwise use a mix of polygon or polyline separately on each path.
+ If the polyline turns out to be single line segments, use a series of MOVETO/LINETO instead,
+ because WMF has no POLYPOLYLINE.
+ The former allows path delimited donuts and the like, which
+ cannot be represented in WMF with polygon or polyline because there is no external way to combine paths
+ as there is in EMF or SVG.
+ For polygons specify the last point the same as the first. The WMF/EMF manuals say that the
+ reading program SHOULD close the path, which allows a conforming program not to, potentially rendering
+ a closed path as an open one. */
+ int nPolys = 0;
+ int totPoints = 0;
+ for (const auto & pit : pv) {
+ totPoints += 1 + pit.size_default(); // big array, will hold all points, for all polygons. Size_default ignores first point in each path.
+ if (pit.end_default() == pit.end_closed()) {
+ nPolys++;
+ } else {
+ nPolys = 0;
+ break;
+ }
+ }
+
+ if (nPolys > 1) { // a single polypolygon, a single polygon falls through to the else
+ pt16hold = pt16ptr = (U_POINT16 *) malloc(totPoints * sizeof(U_POINT16));
+ if (!pt16ptr) {
+ return(false);
+ }
+
+ n16hold = n16ptr = (uint16_t *) malloc(nPolys * sizeof(uint16_t));
+ if (!n16ptr) {
+ free(pt16hold);
+ return(false);
+ }
+
+ for (const auto & pit : pv) {
+ using Geom::X;
+ using Geom::Y;
+
+
+ *n16ptr++ = pit.size_default(); // points in the subpath
+
+ /** For each segment in the subpath */
+
+ Geom::Point p1 = pit.initialPoint(); // This point is special, it isn't in the iterator
+
+ p1[X] = (p1[X] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+ *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y]));
+
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_open(); ++cit) {
+ Geom::Point p1 = cit->finalPoint();
+
+ p1[X] = (p1[X] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+ *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y]));
+ }
+
+ }
+ rec = U_WMRPOLYPOLYGON_set(nPolys, n16hold, pt16hold);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRPOLYPOLYGON_set");
+ }
+ free(pt16hold);
+ free(n16hold);
+ } else { // one or more polyline or polygons (but not all polygons, that would be the preceding case)
+ for (const auto & pit : pv) {
+ using Geom::X;
+ using Geom::Y;
+
+ /* Malformatted Polylines with a sequence like M L M M L have been seen, the 2nd M does nothing
+ and that point must not go into the output. */
+ if (!(pit.size_default())) {
+ continue;
+ }
+ /* Figure out how many points there are, make an array big enough to hold them, and store
+ all the points. This is the same for open or closed path. This gives the upper bound for
+ the number of points. The actual number used is calculated on the fly.
+ */
+ int nPoints = 1 + pit.size_default();
+
+ pt16hold = pt16ptr = (U_POINT16 *) malloc(nPoints * sizeof(U_POINT16));
+ if (!pt16ptr) {
+ break;
+ }
+
+ /** For each segment in the subpath */
+
+ Geom::Point p1 = pit.initialPoint(); // This point is special, it isn't in the iterator
+
+ p1[X] = (p1[X] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+ *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y]));
+ nPoints = 1;
+
+ for (Geom::Path::const_iterator cit = pit.begin(); cit != pit.end_default(); ++cit, nPoints++) {
+ Geom::Point p1 = cit->finalPoint();
+
+ p1[X] = (p1[X] * PX2WORLD);
+ p1[Y] = (p1[Y] * PX2WORLD);
+ *pt16ptr++ = point16_set((int32_t) round(p1[X]), (int32_t) round(p1[Y]));
+ }
+
+ if (pit.end_default() == pit.end_closed()) {
+ rec = U_WMRPOLYGON_set(nPoints, pt16hold);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRPOLYGON_set");
+ }
+ } else if (nPoints > 2) {
+ rec = U_WMRPOLYLINE_set(nPoints, pt16hold);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::print_pathv at U_POLYLINE_set");
+ }
+ } else if (nPoints == 2) {
+ rec = U_WMRMOVETO_set(pt16hold[0]);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRMOVETO_set");
+ }
+ rec = U_WMRLINETO_set(pt16hold[1]);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::print_pathv at U_WMRLINETO_set");
+ }
+ }
+ free(pt16hold);
+ }
+ }
+ }
+
+ // WMF has no fill or stroke commands, the draw does it with active pen/brush
+
+ // clean out brush and pen, but only after all parts of the draw complete
+ if (use_fill) {
+ destroy_brush();
+ }
+ if (use_stroke) {
+ destroy_pen();
+ }
+
+ return TRUE;
+}
+
+
+unsigned int PrintWmf::text(Inkscape::Extension::Print * /*mod*/, char const *text, Geom::Point const &p,
+ SPStyle const *const style)
+{
+ if (!wt || !text) {
+ return 0;
+ }
+
+ char *rec = nullptr;
+ int ccount, newfont;
+ int fix90n = 0;
+ uint32_t hfont = 0;
+ Geom::Affine tf = m_tr_stack.top();
+ double rot = -1800.0 * std::atan2(tf[1], tf[0]) / M_PI; // 0.1 degree rotation, - sign for MM_TEXT
+ double rotb = -std::atan2(tf[1], tf[0]); // rotation for baseline offset for superscript/subscript, used below
+ double dx, dy;
+ double ky;
+
+ // the dx array is smuggled in like: text<nul>w1 w2 w3 ...wn<nul><nul>, where the widths are floats 7 characters wide, including the space
+ int ndx = 0;
+ int rtl = 0;
+ int16_t *adx;
+ smuggle_adxky_out(text, &adx, &ky, &rtl, &ndx, PX2WORLD * std::min(tf.expansionX(), tf.expansionY())); // side effect: free() adx
+
+ uint32_t textalignment;
+ if (rtl > 0) {
+ textalignment = U_TA_BASELINE | U_TA_LEFT;
+ } else {
+ textalignment = U_TA_BASELINE | U_TA_RIGHT | U_TA_RTLREADING;
+ }
+ if (textalignment != htextalignment) {
+ htextalignment = textalignment;
+ rec = U_WMRSETTEXTALIGN_set(textalignment);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTALIGN_set");
+ }
+ }
+
+ char *text2 = strdup(text); // because U_Utf8ToUtf16le calls iconv which does not like a const char *
+ uint16_t *unicode_text = U_Utf8ToUtf16le(text2, 0, nullptr);
+ free(text2);
+ //translates Unicode as Utf16le to NonUnicode, if possible. If any translate, all will, and all to
+ //the same font, because of code in Layout::print
+ UnicodeToNon(unicode_text, &ccount, &newfont);
+ // The preceding hopefully handled conversions to symbol, wingdings or zapf dingbats. Now slam everything
+ // else down into latin1, which is all WMF can handle. If the language isn't English expect terrible results.
+ char *latin1_text = U_Utf16leToLatin1(unicode_text, 0, nullptr);
+ free(unicode_text);
+
+ // in some cases a UTF string may reduce to NO latin1 characters, which returns NULL
+ if(!latin1_text){free(adx); return 0; }
+
+ //PPT gets funky with text within +-1 degree of a multiple of 90, but only for SOME fonts.Snap those to the central value
+ //Some funky ones: Arial, Times New Roman
+ //Some not funky ones: Symbol and Verdana.
+ //Without a huge table we cannot catch them all, so just the most common problem ones.
+ FontfixParams params;
+
+ if (FixPPTCharPos) {
+ switch (newfont) {
+ case CVTSYM:
+ _lookup_ppt_fontfix("Convert To Symbol", params);
+ break;
+ case CVTZDG:
+ _lookup_ppt_fontfix("Convert To Zapf Dingbats", params);
+ break;
+ case CVTWDG:
+ _lookup_ppt_fontfix("Convert To Wingdings", params);
+ break;
+ default: //also CVTNON
+ _lookup_ppt_fontfix(style->font_family.value(), params);
+ break;
+ }
+ if (params.f2 != 0 || params.f3 != 0) {
+ int irem = ((int) round(rot)) % 900 ;
+ if (irem <= 9 && irem >= -9) {
+ fix90n = 1; //assume vertical
+ rot = (double)(((int) round(rot)) - irem);
+ rotb = rot * M_PI / 1800.0;
+ if (std::abs(rot) == 900.0) {
+ fix90n = 2;
+ }
+ }
+ }
+ }
+
+ /*
+ Note that text font sizes are stored into the WMF as fairly small integers and that limits their precision.
+ The WMF output files produced here have been designed so that the integer valued pt sizes
+ land right on an integer value in the WMF file, so those are exact. However, something like 18.1 pt will be
+ somewhat off, so that when it is read back in it becomes 18.11 pt. (For instance.)
+ */
+ int textheight = round(-style->font_size.computed * PX2WORLD * std::min(tf.expansionX(), tf.expansionY()));
+ if (!hfont) {
+
+ // Get font face name. Use changed font name if unicode mapped to one
+ // of the special fonts.
+ char *facename;
+ if (!newfont) {
+ facename = U_Utf8ToLatin1(style->font_family.value(), 0, nullptr);
+ } else {
+ facename = U_Utf8ToLatin1(FontName(newfont), 0, nullptr);
+ }
+
+ // Scale the text to the minimum stretch. (It tends to stay within bounding rectangles even if
+ // it was streteched asymmetrically.) Few applications support text from WMF which is scaled
+ // differently by height/width, so leave lfWidth alone.
+
+ U_FONT *puf = U_FONT_set(
+ textheight,
+ 0,
+ round(rot),
+ round(rot),
+ _translate_weight(style->font_weight.computed),
+ (style->font_style.computed == SP_CSS_FONT_STYLE_ITALIC),
+ style->text_decoration_line.underline,
+ style->text_decoration_line.line_through,
+ U_DEFAULT_CHARSET,
+ U_OUT_DEFAULT_PRECIS,
+ U_CLIP_DEFAULT_PRECIS,
+ U_DEFAULT_QUALITY,
+ U_DEFAULT_PITCH | U_FF_DONTCARE,
+ facename);
+ free(facename);
+
+ rec = wcreatefontindirect_set(&hfont, wht, puf);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::text at wcreatefontindirect_set");
+ }
+ free(puf);
+ }
+
+ rec = wselectobject_set(hfont, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::text at wselectobject_set");
+ }
+
+ float rgb[3];
+ style->fill.value.color.get_rgb_floatv(rgb);
+ // only change the text color when it needs to be changed
+ if (memcmp(htextcolor_rgb, rgb, 3 * sizeof(float))) {
+ memcpy(htextcolor_rgb, rgb, 3 * sizeof(float));
+ rec = U_WMRSETTEXTCOLOR_set(U_RGB(255 * rgb[0], 255 * rgb[1], 255 * rgb[2]));
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::text at U_WMRSETTEXTCOLOR_set");
+ }
+ }
+
+
+ // Text alignment:
+ // - (x,y) coordinates received by this filter are those of the point where the text
+ // actually starts, and already takes into account the text object's alignment;
+ // - for this reason, the WMF text alignment must always be TA_BASELINE|TA_LEFT.
+ // this is set at the beginning of the file and never changed
+
+ // Transparent text background, never changes, set at the beginning of the file
+
+ Geom::Point p2 = p * tf;
+
+ //Handle super/subscripts and vertical kerning
+ /* Previously used this, but vertical kerning was not supported
+ p2[Geom::X] -= style->baseline_shift.computed * std::sin( rotb );
+ p2[Geom::Y] -= style->baseline_shift.computed * std::cos( rotb );
+ */
+ p2[Geom::X] += ky * std::sin(rotb);
+ p2[Geom::Y] += ky * std::cos(rotb);
+
+ //Conditionally handle compensation for PPT WMF import bug (affects PPT 2003-2010, at least)
+ if (FixPPTCharPos) {
+ if (fix90n == 1) { //vertical
+ dx = 0.0;
+ dy = params.f3 * style->font_size.computed * std::cos(rotb);
+ } else if (fix90n == 2) { //horizontal
+ dx = params.f2 * style->font_size.computed * std::sin(rotb);
+ dy = 0.0;
+ } else {
+ dx = params.f1 * style->font_size.computed * std::sin(rotb);
+ dy = params.f1 * style->font_size.computed * std::cos(rotb);
+ }
+ p2[Geom::X] += dx;
+ p2[Geom::Y] += dy;
+ }
+
+ p2[Geom::X] = (p2[Geom::X] * PX2WORLD);
+ p2[Geom::Y] = (p2[Geom::Y] * PX2WORLD);
+
+ int32_t const xpos = (int32_t) round(p2[Geom::X]);
+ int32_t const ypos = (int32_t) round(p2[Geom::Y]);
+
+ // The number of characters in the string is a bit fuzzy. ndx, the number of entries in adx is
+ // the number of VISIBLE characters, since some may combine from the UTF (8 originally,
+ // now 16) encoding. Conversely strlen() or wchar16len() would give the absolute number of
+ // encoding characters. Unclear if emrtext wants the former or the latter but for now assume the former.
+
+ // This is currently being smuggled in from caller as part of text, works
+ // MUCH better than the fallback hack below
+ // uint32_t *adx = dx_set(textheight, U_FW_NORMAL, slen); // dx is needed, this makes one up
+ if (rtl > 0) {
+ rec = U_WMREXTTEXTOUT_set((U_POINT16) {
+ (int16_t) xpos, (int16_t) ypos
+ },
+ ndx, U_ETO_NONE, latin1_text, adx, U_RCL16_DEF);
+ } else { // RTL text, U_TA_RTLREADING should be enough, but set this one too just in case
+ rec = U_WMREXTTEXTOUT_set((U_POINT16) {
+ (int16_t) xpos, (int16_t) ypos
+ },
+ ndx, U_ETO_RTLREADING, latin1_text, adx, U_RCL16_DEF);
+ }
+ free(latin1_text);
+ free(adx);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::text at U_WMREXTTEXTOUTW_set");
+ }
+
+ rec = wdeleteobject_set(&hfont, wht);
+ if (!rec || wmf_append((U_METARECORD *)rec, wt, U_REC_FREE)) {
+ g_error("Fatal programming error in PrintWmf::text at wdeleteobject_set");
+ }
+
+ return 0;
+}
+
+void PrintWmf::init()
+{
+ /* WMF print */
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>Windows Metafile Print</name>\n"
+ "<id>org.inkscape.print.wmf</id>\n"
+ "<param gui-hidden=\"true\" name=\"destination\" type=\"string\"></param>\n"
+ "<param gui-hidden=\"true\" name=\"textToPath\" type=\"bool\">true</param>\n"
+ "<param gui-hidden=\"true\" name=\"pageBoundingBox\" type=\"bool\">true</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTCharPos\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTDashLine\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTGrad2Polys\" type=\"bool\">false</param>\n"
+ "<param gui-hidden=\"true\" name=\"FixPPTPatternAsHatch\" type=\"bool\">false</param>\n"
+ "<print/>\n"
+ "</inkscape-extension>", new PrintWmf());
+ // clang-format on
+
+ return;
+}
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/wmf-print.h b/src/extension/internal/wmf-print.h
new file mode 100644
index 0000000..cc594fe
--- /dev/null
+++ b/src/extension/internal/wmf-print.h
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * @brief Windows Metafile printing - implementation
+ */
+/* Author:
+ * Ulf Erikson <ulferikson@users.sf.net>
+ *
+ * Copyright (C) 2006-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+#ifndef SEEN_INKSCAPE_EXTENSION_INTERNAL_WMF_PRINT_H
+#define SEEN_INKSCAPE_EXTENSION_INTERNAL_WMF_PRINT_H
+
+#include <3rdparty/libuemf/uwmf.h>
+#include "extension/internal/metafile-print.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class PrintWmf : public PrintMetafile
+{
+ uint32_t hbrush, hpen, hbrush_null, hpen_null;
+ uint32_t hmiterlimit; // used to minimize redundant records that set this
+
+ unsigned int print_pathv (Geom::PathVector const &pathv, const Geom::Affine &transform);
+ bool print_simple_shape (Geom::PathVector const &pathv, const Geom::Affine &transform);
+
+public:
+ PrintWmf();
+
+ /* Print functions */
+ unsigned int setup (Inkscape::Extension::Print * module) override;
+
+ unsigned int begin (Inkscape::Extension::Print * module, SPDocument *doc) override;
+ unsigned int finish (Inkscape::Extension::Print * module) override;
+
+ /* Rendering methods */
+ unsigned int fill (Inkscape::Extension::Print *module,
+ Geom::PathVector const &pathv,
+ Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox) override;
+ unsigned int stroke (Inkscape::Extension::Print * module,
+ Geom::PathVector const &pathv,
+ Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox) override;
+ unsigned int image(Inkscape::Extension::Print *module,
+ unsigned char *px,
+ unsigned int w,
+ unsigned int h,
+ unsigned int rs,
+ Geom::Affine const &transform,
+ SPStyle const *style) override;
+ unsigned int text(Inkscape::Extension::Print *module, char const *text,
+ Geom::Point const &p, SPStyle const *style) override;
+
+ static void init ();
+protected:
+ static void smuggle_adxky_out(const char *string, int16_t **adx, double *ky, int *rtl, int *ndx, float scale);
+
+ int create_brush(SPStyle const *style, PU_COLORREF fcolor) override;
+ void destroy_brush() override;
+ int create_pen(SPStyle const *style, const Geom::Affine &transform) override;
+ void destroy_pen() override;
+};
+
+} /* namespace Internal */
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+
+#endif /* __INKSCAPE_EXTENSION_INTERNAL_PRINT_WMF_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/internal/wpg-input.cpp b/src/extension/internal/wpg-input.cpp
new file mode 100644
index 0000000..5e6f381
--- /dev/null
+++ b/src/extension/internal/wpg-input.cpp
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This file came from libwpg as a source, their utility wpg2svg
+ * specifically. It has been modified to work as an Inkscape extension.
+ * The Inkscape extension code is covered by this copyright, but the
+ * rest is covered by the one below.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ *
+ */
+
+/* libwpg
+ * Copyright (C) 2006 Ariya Hidayat (ariya@kde.org)
+ * Copyright (C) 2005 Fridrich Strba (fridrich.strba@bluewin.ch)
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * For further information visit http://libwpg.sourceforge.net
+ */
+
+/* "This product is not manufactured, approved, or supported by
+ * Corel Corporation or Corel Corporation Limited."
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#include <cstdio>
+
+#ifdef WITH_LIBWPG
+
+#include "wpg-input.h"
+#include "extension/system.h"
+#include "extension/input.h"
+#include "document.h"
+#include "object/sp-root.h"
+#include "util/units.h"
+#include <cstring>
+
+#include "libwpg/libwpg.h"
+#include <librevenge-stream/librevenge-stream.h>
+
+using librevenge::RVNGString;
+using librevenge::RVNGFileStream;
+using librevenge::RVNGInputStream;
+
+using namespace libwpg;
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+
+SPDocument *WpgInput::open(Inkscape::Extension::Input * /*mod*/, const gchar * uri)
+{
+ #ifdef _WIN32
+ // RVNGFileStream uses fopen() internally which unfortunately only uses ANSI encoding on Windows
+ // therefore attempt to convert uri to the system codepage
+ // even if this is not possible the alternate short (8.3) file name will be used if available
+ gchar * converted_uri = g_win32_locale_filename_from_utf8(uri);
+ RVNGInputStream* input = new RVNGFileStream(converted_uri);
+ g_free(converted_uri);
+ #else
+ RVNGInputStream* input = new RVNGFileStream(uri);
+ #endif
+
+ if (input->isStructured()) {
+ RVNGInputStream* olestream = input->getSubStreamByName("PerfectOffice_MAIN");
+
+ if (olestream) {
+ delete input;
+ input = olestream;
+ }
+ }
+
+ if (!WPGraphics::isSupported(input)) {
+ //! \todo Dialog here
+ // fprintf(stderr, "ERROR: Unsupported file format (unsupported version) or file is encrypted!\n");
+ // printf("I'm giving up not supported\n");
+ delete input;
+ return nullptr;
+ }
+
+ librevenge::RVNGStringVector vec;
+ librevenge::RVNGSVGDrawingGenerator generator(vec, "");
+
+ if (!libwpg::WPGraphics::parse(input, &generator) || vec.empty() || vec[0].empty()) {
+ delete input;
+ return nullptr;
+ }
+
+ RVNGString output("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
+ output.append(vec[0]);
+
+ //printf("I've got a doc: \n%s", painter.document.c_str());
+
+ SPDocument * doc = SPDocument::createNewDocFromMem(output.cstr(), strlen(output.cstr()), TRUE);
+
+ // Set viewBox if it doesn't exist
+ if (doc && !doc->getRoot()->viewBox_set) {
+ doc->setViewBox(Geom::Rect::from_xywh(0, 0, doc->getWidth().value(doc->getDisplayUnit()), doc->getHeight().value(doc->getDisplayUnit())));
+ }
+
+ delete input;
+ return doc;
+}
+
+#include "clear-n_.h"
+
+void WpgInput::init() {
+ // clang-format off
+ Inkscape::Extension::build_from_mem(
+ "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
+ "<name>" N_("WPG Input") "</name>\n"
+ "<id>org.inkscape.input.wpg</id>\n"
+ "<input>\n"
+ "<extension>.wpg</extension>\n"
+ "<mimetype>image/x-wpg</mimetype>\n"
+ "<filetypename>" N_("WordPerfect Graphics (*.wpg)") "</filetypename>\n"
+ "<filetypetooltip>" N_("Vector graphics format used by Corel WordPerfect") "</filetypetooltip>\n"
+ "</input>\n"
+ "</inkscape-extension>", new WpgInput());
+ // clang-format on
+} // init
+
+} } } /* namespace Inkscape, Extension, Implementation */
+#endif /* WITH_LIBWPG */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/internal/wpg-input.h b/src/extension/internal/wpg-input.h
new file mode 100644
index 0000000..67e4d91
--- /dev/null
+++ b/src/extension/internal/wpg-input.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This code abstracts the libwpg interfaces into the Inkscape
+ * input extension interface.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __EXTENSION_INTERNAL_WPGOUTPUT_H__
+#define __EXTENSION_INTERNAL_WPGOUTPUT_H__
+
+#ifdef HAVE_CONFIG_H
+# include "config.h" // only include where actually required!
+#endif
+
+#ifdef WITH_LIBWPG
+
+#include "../implementation/implementation.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+class WpgInput : public Inkscape::Extension::Implementation::Implementation {
+ WpgInput () = default;;
+public:
+ SPDocument *open( Inkscape::Extension::Input *mod,
+ const gchar *uri ) override;
+ static void init( );
+
+};
+
+} } } /* namespace Inkscape, Extension, Implementation */
+
+#endif /* WITH_LIBWPG */
+#endif /* __EXTENSION_INTERNAL_WPGOUTPUT_H__ */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/loader.cpp b/src/extension/loader.cpp
new file mode 100644
index 0000000..d409d09
--- /dev/null
+++ b/src/extension/loader.cpp
@@ -0,0 +1,140 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Loader for external plug-ins.
+ *
+ * Authors:
+ * Moritz Eberl <moritz@semiodesk.com>
+ *
+ * Copyright (C) 2016 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "loader.h"
+
+#include <gmodule.h>
+
+#include "system.h"
+#include <cstring>
+#include "dependency.h"
+#include "inkscape-version.h"
+
+namespace Inkscape {
+namespace Extension {
+
+typedef Implementation::Implementation *(*_getImplementation)();
+typedef const gchar *(*_getInkscapeVersion)();
+
+bool Loader::load_dependency(Dependency *dep)
+{
+ GModule *module = nullptr;
+ module = g_module_open(dep->get_name(), (GModuleFlags)0);
+ if (module == nullptr) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * @brief Load the actual implementation of a plugin supplied by the plugin.
+ * @param doc The xml representation of the INX extension configuration.
+ * @return The implementation of the extension loaded from the plugin.
+ */
+Implementation::Implementation *Loader::load_implementation(Inkscape::XML::Document *doc)
+{
+ try {
+
+ Inkscape::XML::Node *repr = doc->root();
+ Inkscape::XML::Node *child_repr = repr->firstChild();
+
+ // Iterate over the xml content
+ while (child_repr != nullptr) {
+ char const *chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+
+ // Deal with dependencies if we have them
+ if (!strcmp(chname, "dependency")) {
+ Dependency dep = Dependency(child_repr, nullptr); // TODO: Why is "this" not an extension?
+ // try to load it
+ bool success = load_dependency(&dep);
+ if( !success ){
+ // Could not load dependency, we abort
+ const char *res = g_module_error();
+ g_warning("Unable to load dependency %s of plugin %s.\nDetails: %s\n", dep.get_name(), "<todo>", res);
+ return nullptr;
+ }
+ }
+
+ // Found a plugin to load
+ if (!strcmp(chname, "plugin")) {
+
+ // The name of the plugin is actually the library file we want to load
+ if (const gchar *name = child_repr->attribute("name")) {
+ GModule *module = nullptr;
+ _getImplementation GetImplementation = nullptr;
+ _getInkscapeVersion GetInkscapeVersion = nullptr;
+
+ // build the path where to look for the plugin
+ gchar *path = g_build_filename(_baseDirectory.c_str(), name, (char *) nullptr);
+ module = g_module_open(path, G_MODULE_BIND_LOCAL);
+ g_free(path);
+
+ if (module == nullptr) {
+ // we were not able to load the plugin, write warning and abort
+ const char *res = g_module_error();
+ g_warning("Unable to load extension %s.\nDetails: %s\n", name, res);
+ return nullptr;
+ }
+
+ // Get a handle to the version function of the module
+ if (g_module_symbol(module, "GetInkscapeVersion", (gpointer *) &GetInkscapeVersion) == FALSE) {
+ // This didn't work, write warning and abort
+ const char *res = g_module_error();
+ g_warning("Unable to load extension %s.\nDetails: %s\n", name, res);
+ return nullptr;
+ }
+
+ // Get a handle to the function that delivers the implementation
+ if (g_module_symbol(module, "GetImplementation", (gpointer *) &GetImplementation) == FALSE) {
+ // This didn't work, write warning and abort
+ const char *res = g_module_error();
+ g_warning("Unable to load extension %s.\nDetails: %s\n", name, res);
+ return nullptr;
+ }
+
+ // Get version and test against this version
+ const gchar* version = GetInkscapeVersion();
+ if( strcmp(version, version_string) != 0) {
+ // The versions are different, display warning.
+ g_warning("Plugin was built against Inkscape version %s, this is %s. The plugin might not be compatible.", version, version_string);
+ }
+
+
+ Implementation::Implementation *i = GetImplementation();
+ return i;
+ }
+ }
+
+ child_repr = child_repr->next();
+ }
+ } catch (std::exception &e) {
+ g_warning("Unable to load extension.");
+ }
+ return nullptr;
+}
+
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace .0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
diff --git a/src/extension/loader.h b/src/extension/loader.h
new file mode 100644
index 0000000..abfb4fb
--- /dev/null
+++ b/src/extension/loader.h
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Loader for external plug-ins.
+ *//*
+ *
+ * Authors:
+ * Moritz Eberl <moritz@semiodesk.com>
+ *
+ * Copyright (C) 2016 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_LOADER_H_
+#define INKSCAPE_EXTENSION_LOADER_H_
+
+#include "extension.h"
+
+
+namespace Inkscape {
+
+namespace XML {
+class Document;
+}
+
+namespace Extension {
+
+/** This class contains the mechanism to load c++ plugins dynamically.
+*/
+class Loader {
+
+public:
+ /**
+ * Sets a base directory where to look for the actual plugin to load.
+ *
+ * @param dir is the path where the plugin should be loaded from.
+ */
+ void set_base_directory(std::string const &dir) {
+ _baseDirectory = dir;
+ }
+
+ /**
+ * Loads plugin dependencies which are needed for the plugin to load.
+ *
+ * @param dep
+ */
+ bool load_dependency(Dependency *dep);
+
+ /**
+ * Load the actual implementation of a plugin supplied by the plugin.
+ *
+ * @param doc The xml representation of the INX extension configuration.
+ * @return The implementation of the extension loaded from the plugin.
+ */
+ Implementation::Implementation *load_implementation(Inkscape::XML::Document *doc);
+
+private:
+ std::string _baseDirectory; /**< The base directory to load a plugin from */
+
+
+};
+
+} // namespace Extension
+} // namespace Inkscape */
+
+#endif // _LOADER_H_
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace .0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim:filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99:
diff --git a/src/extension/output.cpp b/src/extension/output.cpp
new file mode 100644
index 0000000..750d80b
--- /dev/null
+++ b/src/extension/output.cpp
@@ -0,0 +1,303 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "output.h"
+
+#include "document.h"
+
+#include "io/sys.h"
+#include "implementation/implementation.h"
+
+#include "prefdialog/prefdialog.h"
+
+#include "xml/repr.h"
+
+
+/* Inkscape::Extension::Output */
+
+namespace Inkscape {
+namespace Extension {
+
+/**
+ \return None
+ \brief Builds a SPModuleOutput object from a XML description
+ \param module The module to be initialized
+ \param repr The XML description in a Inkscape::XML::Node tree
+
+ Okay, so you want to build a SPModuleOutput object.
+
+ This function first takes and does the build of the parent class,
+ which is SPModule. Then, it looks for the <output> section of the
+ XML description. Under there should be several fields which
+ describe the output module to excruciating detail. Those are parsed,
+ copied, and put into the structure that is passed in as module.
+ Overall, there are many levels of indentation, just to handle the
+ levels of indentation in the XML file.
+*/
+Output::Output (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory)
+ : Extension(in_repr, in_imp, base_directory)
+{
+ mimetype = nullptr;
+ extension = nullptr;
+ filetypename = nullptr;
+ filetypetooltip = nullptr;
+ dataloss = true;
+ savecopyonly = false;
+ exported = false;
+ raster = false;
+
+ if (repr != nullptr) {
+ Inkscape::XML::Node * child_repr;
+
+ child_repr = repr->firstChild();
+
+ while (child_repr != nullptr) {
+ if (!strcmp(child_repr->name(), INKSCAPE_EXTENSION_NS "output")) {
+
+ if (child_repr->attribute("raster") && !strcmp(child_repr->attribute("raster"), "true")) {
+ raster = true;
+ }
+ if (child_repr->attribute("is_exported") && !strcmp(child_repr->attribute("is_exported"), "true")) {
+ exported = true;
+ }
+
+ child_repr = child_repr->firstChild();
+ while (child_repr != nullptr) {
+ char const * chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (chname[0] == '_') /* Allow _ for translation of tags */
+ chname++;
+ if (!strcmp(chname, "extension")) {
+ g_free (extension);
+ extension = g_strdup(child_repr->firstChild()->content());
+ }
+ if (!strcmp(chname, "mimetype")) {
+ g_free (mimetype);
+ mimetype = g_strdup(child_repr->firstChild()->content());
+ }
+ if (!strcmp(chname, "filetypename")) {
+ g_free (filetypename);
+ filetypename = g_strdup(child_repr->firstChild()->content());
+ }
+ if (!strcmp(chname, "filetypetooltip")) {
+ g_free (filetypetooltip);
+ filetypetooltip = g_strdup(child_repr->firstChild()->content());
+ }
+ if (!strcmp(chname, "dataloss")) {
+ dataloss = strcmp(child_repr->firstChild()->content(), "false");
+ }
+ if (!strcmp(chname, "savecopyonly")) {
+ savecopyonly = !strcmp(child_repr->firstChild()->content(), "true");
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ break;
+ }
+
+ child_repr = child_repr->next();
+ }
+
+ }
+}
+
+/**
+ \brief Destroy an output extension
+*/
+Output::~Output ()
+{
+ g_free(mimetype);
+ g_free(extension);
+ g_free(filetypename);
+ g_free(filetypetooltip);
+ return;
+}
+
+/**
+ \return Whether this extension checks out
+ \brief Validate this extension
+
+ This function checks to make sure that the output extension has
+ a filename extension and a MIME type. Then it calls the parent
+ class' check function which also checks out the implementation.
+*/
+bool
+Output::check ()
+{
+ if (extension == nullptr)
+ return FALSE;
+ if (mimetype == nullptr)
+ return FALSE;
+
+ return Extension::check();
+}
+
+/**
+ \return IETF mime-type for the extension
+ \brief Get the mime-type that describes this extension
+*/
+gchar *
+Output::get_mimetype()
+{
+ return mimetype;
+}
+
+/**
+ \return Filename extension for the extension
+ \brief Get the filename extension for this extension
+*/
+gchar *
+Output::get_extension()
+{
+ return extension;
+}
+
+/**
+ \return The name of the filetype supported
+ \brief Get the name of the filetype supported
+*/
+const char *
+Output::get_filetypename(bool translated)
+{
+ const char *name;
+
+ if (filetypename)
+ name = filetypename;
+ else
+ name = get_name();
+
+ if (name && translated && filetypename) {
+ return get_translation(name);
+ } else {
+ return name;
+ }
+}
+
+/**
+ \return Tooltip giving more information on the filetype
+ \brief Get the tooltip for more information on the filetype
+*/
+const char *
+Output::get_filetypetooltip(bool translated)
+{
+ if (filetypetooltip && translated) {
+ return get_translation(filetypetooltip);
+ } else {
+ return filetypetooltip;
+ }
+}
+
+/**
+ \return A dialog to get settings for this extension
+ \brief Create a dialog for preference for this extension
+
+ Calls the implementation to get the preferences.
+*/
+bool
+Output::prefs ()
+{
+ if (!loaded())
+ set_state(Extension::STATE_LOADED);
+ if (!loaded()) return false;
+
+ Gtk::Widget * controls;
+ controls = imp->prefs_output(this);
+ if (controls == nullptr) {
+ // std::cout << "No preferences for Output" << std::endl;
+ return true;
+ }
+
+ Glib::ustring title = this->get_name();
+ PrefDialog *dialog = new PrefDialog(title, controls);
+ int response = dialog->run();
+ dialog->hide();
+
+ delete dialog;
+
+ return (response == Gtk::RESPONSE_OK);
+}
+
+/**
+ \return None
+ \brief Save a document as a file
+ \param doc Document to save
+ \param filename File to save the document as
+
+ This function does a little of the dirty work involved in saving
+ a document so that the implementation only has to worry about getting
+ bits on the disk.
+
+ The big thing that it does is remove and read the fields that are
+ only used at runtime and shouldn't be saved. One that may surprise
+ people is the output extension. This is not saved so that the IDs
+ could be changed, and old files will still work properly.
+*/
+void
+Output::save(SPDocument *doc, gchar const *filename, bool detachbase)
+{
+ if (!loaded())
+ set_state(Extension::STATE_LOADED);
+
+ if (loaded()) {
+ imp->setDetachBase(detachbase);
+ auto new_doc = doc->copy();
+ imp->save(this, new_doc.get(), filename);
+ // This shouldn't be needed, but the unique_ptr is not destroying this SPDocument.
+ delete new_doc.release();
+ }
+}
+
+/**
+ \return None
+ \brief Save a rendered png as a raster output
+ \param png_filename source png file.
+ \param filename File to save the raster as
+
+*/
+void
+Output::export_raster(const SPDocument *doc, std::string png_filename, gchar const *filename, bool detachbase)
+{
+ if (!loaded())
+ set_state(Extension::STATE_LOADED);
+
+ if (loaded()) {
+ imp->setDetachBase(detachbase);
+ imp->export_raster(this, doc, png_filename, filename);
+ }
+}
+
+/**
+ * Adds a valid extension to the filename if it's missing.
+ */
+void
+Output::add_extension(Glib::ustring &filename)
+{
+ auto current_ext = Inkscape::IO::get_file_extension(filename);
+ if (extension && current_ext != extension) {
+ filename = filename + extension;
+ }
+}
+
+} } /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/output.h b/src/extension/output.h
new file mode 100644
index 0000000..f457326
--- /dev/null
+++ b/src/extension/output.h
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+
+#ifndef INKSCAPE_EXTENSION_OUTPUT_H__
+#define INKSCAPE_EXTENSION_OUTPUT_H__
+
+#include "extension.h"
+class SPDocument;
+
+namespace Inkscape {
+namespace Extension {
+
+class Output : public Extension {
+ gchar *mimetype; /**< What is the mime type this inputs? */
+ gchar *extension; /**< The extension of the input files */
+ gchar *filetypename; /**< A userfriendly name for the file type */
+ gchar *filetypetooltip; /**< A more detailed description of the filetype */
+ bool dataloss; /**< The extension causes data loss on save */
+ bool savecopyonly; /**< Limit output option to Save a Copy */
+ bool raster; /**< Is the extension expecting a png file */
+ bool exported; /**< Is the extension available in the export dialog */
+
+public:
+ class save_failed {}; /**< Generic failure for an undescribed reason */
+ class save_cancelled {}; /**< Saving was cancelled */
+ class no_extension_found {}; /**< Failed because we couldn't find an extension to match the filename */
+ class file_read_only {}; /**< The existing file can not be opened for writing */
+ class export_id_not_found { /**< The object ID requested for export could not be found in the document */
+ public:
+ const gchar * const id;
+ export_id_not_found(const gchar * const id = nullptr) : id{id} {};
+ };
+
+ Output(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory);
+ ~Output () override;
+
+ bool check() override;
+
+ void save (SPDocument *doc,
+ gchar const *filename,
+ bool detachbase = false);
+ void export_raster (const SPDocument *doc,
+ std::string png_filename,
+ gchar const *filename,
+ bool detachbase);
+ bool prefs ();
+ gchar * get_mimetype();
+ gchar * get_extension();
+ const char * get_filetypename(bool translated=false);
+ const char * get_filetypetooltip(bool translated=false);
+ bool causes_dataloss() { return dataloss; };
+ bool savecopy_only() { return savecopyonly; };
+ bool is_raster() { return raster; };
+ bool is_exported() { return exported; };
+ void add_extension(Glib::ustring &filename);
+};
+
+} } /* namespace Inkscape, Extension */
+#endif /* INKSCAPE_EXTENSION_OUTPUT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/patheffect.cpp b/src/extension/patheffect.cpp
new file mode 100644
index 0000000..3ed53e7
--- /dev/null
+++ b/src/extension/patheffect.cpp
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+
+#include "patheffect.h"
+
+#include "db.h"
+
+#include "object/sp-defs.h"
+
+#include "xml/repr.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+PathEffect::PathEffect (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory)
+ : Extension(in_repr, in_imp, base_directory)
+{
+
+}
+
+PathEffect::~PathEffect (void)
+= default;
+
+void
+PathEffect::processPath (SPDocument * /*doc*/, Inkscape::XML::Node * /*path*/, Inkscape::XML::Node * /*def*/)
+{
+
+
+}
+
+void
+PathEffect::processPathEffects (SPDocument * doc, Inkscape::XML::Node * path)
+{
+ gchar const * patheffectlist = path->attribute("inkscape:path-effects");
+ if (patheffectlist == nullptr)
+ return;
+
+ gchar ** patheffects = g_strsplit(patheffectlist, ";", 128);
+ Inkscape::XML::Node * defs = doc->getDefs()->getRepr();
+
+ for (int i = 0; (i < 128) && (patheffects[i] != nullptr); i++) {
+ gchar * patheffect = patheffects[i];
+
+ // This is weird, they should all be references... but anyway
+ if (patheffect[0] != '#') continue;
+
+ Inkscape::XML::Node * prefs = sp_repr_lookup_child(defs, "id", &(patheffect[1]));
+ if (prefs == nullptr) {
+
+ continue;
+ }
+
+ gchar const * ext_id = prefs->attribute("extension");
+ if (ext_id == nullptr) {
+
+ continue;
+ }
+
+ Inkscape::Extension::PathEffect * peffect;
+ peffect = dynamic_cast<Inkscape::Extension::PathEffect *>(Inkscape::Extension::db.get(ext_id));
+ if (peffect != nullptr) {
+ peffect->processPath(doc, path, prefs);
+ }
+ }
+
+ g_strfreev(patheffects);
+ return;
+}
+
+
+} } /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/patheffect.h b/src/extension/patheffect.h
new file mode 100644
index 0000000..b2dd7a0
--- /dev/null
+++ b/src/extension/patheffect.h
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_PATHEFFECT_H__
+#define INKSCAPE_EXTENSION_PATHEFFECT_H__
+
+#include "document.h"
+#include "extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+class PathEffect : public Extension {
+
+public:
+ PathEffect(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory);
+ ~PathEffect() override;
+
+ void processPath (SPDocument * doc,
+ Inkscape::XML::Node * path,
+ Inkscape::XML::Node * def);
+ static void processPathEffects (SPDocument * doc,
+ Inkscape::XML::Node * path);
+}; /* PathEffect */
+
+
+} } /* namespace Inkscape, Extension */
+#endif /* INKSCAPE_EXTENSION_PATHEFFECT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/plugins/CMakeLists.txt b/src/extension/plugins/CMakeLists.txt
new file mode 100644
index 0000000..dc15b4a
--- /dev/null
+++ b/src/extension/plugins/CMakeLists.txt
@@ -0,0 +1,2 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+add_subdirectory(grid2)
diff --git a/src/extension/plugins/grid2/CMakeLists.txt b/src/extension/plugins/grid2/CMakeLists.txt
new file mode 100644
index 0000000..1d23d83
--- /dev/null
+++ b/src/extension/plugins/grid2/CMakeLists.txt
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+set(grid_PART_SRCS grid.cpp)
+
+include_directories( ${CMAKE_BINARY_DIR}/src )
+
+add_library(grid2 SHARED EXCLUDE_FROM_ALL ${grid_PART_SRCS})
+
+target_link_libraries(grid2 inkscape_base)
+
diff --git a/src/extension/plugins/grid2/grid.cpp b/src/extension/plugins/grid2/grid.cpp
new file mode 100644
index 0000000..5c94726
--- /dev/null
+++ b/src/extension/plugins/grid2/grid.cpp
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ \file grid.cpp
+
+ A plug-in to add a grid creation effect into Inkscape.
+*/
+/*
+ * Copyright (C) 2004-2005 Ted Gould <ted@gould.cx>
+ * Copyright (C) 2007 MenTaLguY <mental@rydia.net>
+ * Abhishek Sharma
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <gtkmm/box.h>
+#include <gtkmm/adjustment.h>
+#include <gtkmm/spinbutton.h>
+
+#include "desktop.h"
+
+#include "document.h"
+#include "selection.h"
+#include "2geom/geom.h"
+
+#include "object/sp-object.h"
+
+#include "svg/path-string.h"
+
+#include "extension/effect.h"
+#include "extension/system.h"
+
+#include "util/units.h"
+
+#include "grid.h"
+
+namespace Inkscape {
+namespace Extension {
+namespace Internal {
+
+/**
+ \brief A function to allocated anything -- just an example here
+ \param module Unused
+ \return Whether the load was successful
+*/
+bool
+Grid::load (Inkscape::Extension::Extension */*module*/)
+{
+ // std::cout << "Hey, I'm Grid, I'm loading!" << std::endl;
+ return TRUE;
+}
+
+namespace {
+
+Glib::ustring build_lines(Geom::Rect bounding_area,
+ Geom::Point const &offset, Geom::Point const &spacing)
+{
+
+ std::cout << "Building lines" << std::endl;
+
+ Geom::Point point_offset(0.0, 0.0);
+
+ SVG::PathString path_data;
+
+ for ( int axis = Geom::X ; axis <= Geom::Y ; ++axis ) {
+ point_offset[axis] = offset[axis];
+
+ for (Geom::Point start_point = bounding_area.min();
+ start_point[axis] + offset[axis] <= (bounding_area.max())[axis];
+ start_point[axis] += spacing[axis]) {
+ Geom::Point end_point = start_point;
+ end_point[1-axis] = (bounding_area.max())[1-axis];
+
+ path_data.moveTo(start_point + point_offset)
+ .lineTo(end_point + point_offset);
+ }
+ }
+ std::cout << "Path data:" << path_data.c_str() << std::endl;
+ return path_data;
+}
+
+} // namespace
+
+/**
+ \brief This actually draws the grid.
+ \param module The effect that was called (unused)
+ \param document What should be edited.
+*/
+void
+Grid::effect (Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+
+ std::cout << "Executing effect" << std::endl;
+
+ Inkscape::Selection * selection = ((SPDesktop *)document)->selection;
+
+ Geom::Rect bounding_area = Geom::Rect(Geom::Point(0,0), Geom::Point(100,100));
+ if (selection->isEmpty()) {
+ /* get page size */
+ SPDocument * doc = document->doc();
+ bounding_area = Geom::Rect( Geom::Point(0,0),
+ Geom::Point(doc->getWidth().value("px"), doc->getHeight().value("px")) );
+ } else {
+ Geom::OptRect bounds = selection->visualBounds();
+ if (bounds) {
+ bounding_area = *bounds;
+ }
+
+ gdouble doc_height = (document->doc())->getHeight().value("px");
+ Geom::Rect temprec = Geom::Rect(Geom::Point(bounding_area.min()[Geom::X], doc_height - bounding_area.min()[Geom::Y]),
+ Geom::Point(bounding_area.max()[Geom::X], doc_height - bounding_area.max()[Geom::Y]));
+
+ bounding_area = temprec;
+ }
+
+ double scale = document->doc()->getDocumentScale().inverse()[Geom::X];
+
+ bounding_area *= Geom::Scale(scale);
+ Geom::Point spacings( scale * module->get_param_float("xspacing"),
+ scale * module->get_param_float("yspacing") );
+ gdouble line_width = scale * module->get_param_float("lineWidth");
+ Geom::Point offsets( scale * module->get_param_float("xoffset"),
+ scale * module->get_param_float("yoffset") );
+
+ Glib::ustring path_data("");
+
+ path_data = build_lines(bounding_area, offsets, spacings);
+ Inkscape::XML::Document * xml_doc = document->doc()->getReprDoc();
+
+ //XML Tree being used directly here while it shouldn't be.
+ Inkscape::XML::Node * current_layer = static_cast<SPDesktop *>(document)->currentLayer()->getRepr();
+ Inkscape::XML::Node * path = xml_doc->createElement("svg:path");
+
+ path->setAttribute("d", path_data);
+
+ std::ostringstream stringstream;
+ stringstream << "fill:none;stroke:#000000;stroke-width:" << line_width << "px";
+ path->setAttribute("style", stringstream.str());
+
+ current_layer->appendChild(path);
+ Inkscape::GC::release(path);
+}
+
+/** \brief A class to make an adjustment that uses Extension params */
+class PrefAdjustment : public Gtk::Adjustment {
+ /** Extension that this relates to */
+ Inkscape::Extension::Extension * _ext;
+ /** The string which represents the parameter */
+ char * _pref;
+public:
+ /** \brief Make the adjustment using an extension and the string
+ describing the parameter. */
+ PrefAdjustment(Inkscape::Extension::Extension * ext, char * pref) :
+ Gtk::Adjustment(0.0, 0.0, 10.0, 0.1), _ext(ext), _pref(pref) {
+ this->set_value(_ext->get_param_float(_pref));
+ this->signal_value_changed().connect(sigc::mem_fun(this, &PrefAdjustment::val_changed));
+ return;
+ };
+
+ void val_changed ();
+}; /* class PrefAdjustment */
+
+/** \brief A function to respond to the value_changed signal from the
+ adjustment.
+
+ This function just grabs the value from the adjustment and writes
+ it to the parameter. Very simple, but yet beautiful.
+*/
+void
+PrefAdjustment::val_changed ()
+{
+ // std::cout << "Value Changed to: " << this->get_value() << std::endl;
+ _ext->set_param_float(_pref, this->get_value());
+ return;
+}
+
+/** \brief A function to get the preferences for the grid
+ \param module Module which holds the params
+ \param view Unused today - may get style information in the future.
+
+ Uses AutoGUI for creating the GUI.
+*/
+Gtk::Widget *
+Grid::prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * /*docCache*/)
+{
+ SPDocument * current_document = view->doc();
+
+ auto selected = ((SPDesktop *) view)->getSelection()->items();
+ Inkscape::XML::Node * first_select = nullptr;
+ if (!selected.empty()) {
+ first_select = selected.front()->getRepr();
+ }
+
+ return module->autogui(current_document, first_select, changeSignal);
+}
+
+
+
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/plugins/grid2/grid.h b/src/extension/plugins/grid2/grid.h
new file mode 100644
index 0000000..f6d7d57
--- /dev/null
+++ b/src/extension/plugins/grid2/grid.h
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2004-2005 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef __GRID_H
+
+#include "extension/implementation/implementation.h"
+
+
+#include <glib.h>
+#include <gmodule.h>
+#include "inkscape-version.cpp"
+
+
+
+namespace Inkscape {
+namespace Extension {
+
+class Effect;
+class Extension;
+
+namespace Internal {
+
+/** \brief Implementation class of the GIMP gradient plugin. This mostly
+ just creates a namespace for the GIMP gradient plugin today.
+*/
+class Grid : public Inkscape::Extension::Implementation::Implementation {
+
+public:
+ bool load(Inkscape::Extension::Extension *module) override;
+ void effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View *document, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+ Gtk::Widget * prefs_effect(Inkscape::Extension::Effect *module, Inkscape::UI::View::View * view, sigc::signal<void> * changeSignal, Inkscape::Extension::Implementation::ImplementationDocumentCache * docCache) override;
+
+};
+
+}; /* namespace Internal */
+}; /* namespace Extension */
+}; /* namespace Inkscape */
+
+extern "C" G_MODULE_EXPORT Inkscape::Extension::Implementation::Implementation* GetImplementation() { return new Inkscape::Extension::Internal::Grid(); }
+extern "C" G_MODULE_EXPORT const gchar* GetInkscapeVersion() { return Inkscape::version_string; }
+#endif
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/plugins/grid2/libgrid2.inx b/src/extension/plugins/grid2/libgrid2.inx
new file mode 100644
index 0000000..db95cd5
--- /dev/null
+++ b/src/extension/plugins/grid2/libgrid2.inx
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!-- SPDX-License-Identifier: GPL-2.0-or-later -->
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+<_name>Grid2</_name>
+<id>org.inkscape.effect.grid2</id>
+<param name="lineWidth" _gui-text="Line Width:" type="float">1.0</param>
+<param name="xspacing" _gui-text="Horizontal Spacing:" type="float" min="0.1" max="1000">10.0</param>
+<param name="yspacing" _gui-text="Vertical Spacing:" type="float" min="0.1" max="1000">10.0</param>
+<param name="xoffset" _gui-text="Horizontal Offset:" type="float" min="0.0" max="1000">0.0</param>
+<param name="yoffset" _gui-text="Vertical Offset:" type="float" min="0.0" max="1000">0.0</param>
+<effect>
+ <object-type>all</object-type>
+ <effects-menu>
+ <submenu _name="Render" >
+ <submenu _name="Grids" />
+ </submenu>
+ </effects-menu>
+<_menu-tip>Draw a path which is a grid</_menu-tip>
+</effect>
+<plugin name="libgrid2" />
+</inkscape-extension>
diff --git a/src/extension/prefdialog/parameter-bool.cpp b/src/extension/prefdialog/parameter-bool.cpp
new file mode 100644
index 0000000..609e727
--- /dev/null
+++ b/src/extension/prefdialog/parameter-bool.cpp
@@ -0,0 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-bool.h"
+
+#include <gtkmm/box.h>
+#include <gtkmm/checkbutton.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Extension {
+
+ParamBool::ParamBool(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ if (!strcmp(value, "true")) {
+ _value = true;
+ } else if (!strcmp(value, "false")) {
+ _value = false;
+ } else {
+ g_warning("Invalid default value ('%s') for parameter '%s' in extension '%s'",
+ value, _name, _extension->get_id());
+ }
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getBool(pref_name(), _value);
+}
+
+bool ParamBool::set(bool in)
+{
+ _value = in;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setBool(pref_name(), _value);
+
+ return _value;
+}
+
+bool ParamBool::get() const
+{
+ return _value;
+}
+
+/**
+ * A check button which is Param aware. It works with the
+ * parameter to change it's value as the check button changes
+ * value.
+ */
+class ParamBoolCheckButton : public Gtk::CheckButton {
+public:
+ /**
+ * Initialize the check button.
+ * This function sets the value of the checkbox to be that of the
+ * parameter, and then sets up a callback to \c on_toggle.
+ *
+ * @param param Which parameter to adjust on changing the check button
+ */
+ ParamBoolCheckButton(ParamBool *param, char *label, sigc::signal<void> *changeSignal)
+ : Gtk::CheckButton(label)
+ , _pref(param)
+ , _changeSignal(changeSignal) {
+ this->set_active(_pref->get());
+ this->signal_toggled().connect(sigc::mem_fun(this, &ParamBoolCheckButton::on_toggle));
+ return;
+ }
+
+ /**
+ * A function to respond to the check box changing.
+ * Adjusts the value of the preference to match that in the check box.
+ */
+ void on_toggle ();
+
+private:
+ /** Param to change. */
+ ParamBool *_pref;
+ sigc::signal<void> *_changeSignal;
+};
+
+void ParamBoolCheckButton::on_toggle()
+{
+ _pref->set(this->get_active());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+ return;
+}
+
+std::string ParamBool::value_to_string() const
+{
+ if (_value) {
+ return "true";
+ }
+ return "false";
+}
+
+Gtk::Widget *ParamBool::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+ hbox->set_homogeneous(false);
+
+ ParamBoolCheckButton * checkbox = Gtk::manage(new ParamBoolCheckButton(this, _text, changeSignal));
+ checkbox->show();
+ hbox->pack_start(*checkbox, false, false);
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/prefdialog/parameter-bool.h b/src/extension/prefdialog/parameter-bool.h
new file mode 100644
index 0000000..52fe06a
--- /dev/null
+++ b/src/extension/prefdialog/parameter-bool.h
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INK_EXTENSION_PARAMBOOL_H
+#define SEEN_INK_EXTENSION_PARAMBOOL_H
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+/**
+ * A boolean parameter.
+ */
+class ParamBool : public InxParameter {
+public:
+ ParamBool(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /**
+ * Returns the current state/value.
+ */
+ bool get() const;
+
+ /**
+ * A function to set the state/value.
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place pref_name() is used.
+ *
+ * @param in The value to set to
+ */
+ bool set(bool in);
+
+ /**
+ * Creates a bool check button for a bool parameter.
+ * Builds a hbox with a label and a check button in it.
+ */
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ /**
+ * Appends 'true' or 'false'.
+ * @todo investigate. Returning a value that can then be appended would probably work better/safer.
+ */
+ std::string value_to_string() const override;
+
+private:
+ /** Internal value. */
+ bool _value = true;
+};
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_PARAMBOOL_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter-color.cpp b/src/extension/prefdialog/parameter-color.cpp
new file mode 100644
index 0000000..b262afd
--- /dev/null
+++ b/src/extension/prefdialog/parameter-color.cpp
@@ -0,0 +1,146 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl>
+ * Christopher Brown <audiere@gmail.com>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-color.h"
+
+#include <iostream>
+#include <sstream>
+
+#include <gtkmm/box.h>
+#include <gtkmm/colorbutton.h>
+#include <gtkmm/label.h>
+
+#include "color.h"
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "ui/widget/color-notebook.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+ParamColor::ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ unsigned int _value = 0x000000ff; // default to black
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ _value = strtoul(value, nullptr, 0);
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getUInt(pref_name(), _value);
+
+ _color.setValue(_value);
+
+ _color_changed = _color.signal_changed.connect(sigc::mem_fun(this, &ParamColor::_onColorChanged));
+ // TODO: SelectedColor does not properly emit signal_changed after dragging, so we also need the following
+ _color_released = _color.signal_released.connect(sigc::mem_fun(this, &ParamColor::_onColorChanged));
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "colorbutton")) {
+ _mode = COLOR_BUTTON;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+ParamColor::~ParamColor()
+{
+ _color_changed.disconnect();
+ _color_released.disconnect();
+}
+
+unsigned int ParamColor::set(unsigned int in)
+{
+ _color.setValue(in);
+
+ return in;
+}
+
+Gtk::Widget *ParamColor::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ if (changeSignal) {
+ _changeSignal = new sigc::signal<void>(*changeSignal);
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+ if (_mode == COLOR_BUTTON) {
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, true, true);
+
+ Gdk::RGBA rgba;
+ rgba.set_red_u (((_color.value() >> 24) & 255) << 8);
+ rgba.set_green_u(((_color.value() >> 16) & 255) << 8);
+ rgba.set_blue_u (((_color.value() >> 8) & 255) << 8);
+ rgba.set_alpha_u(((_color.value() >> 0) & 255) << 8);
+
+ // TODO: It would be nicer to have a custom Gtk::ColorButton() implementation here,
+ // that wraps an Inkscape::UI::Widget::ColorNotebook into a new dialog
+ _color_button = Gtk::manage(new Gtk::ColorButton(rgba));
+ _color_button->set_title(_text);
+ _color_button->set_use_alpha();
+ _color_button->show();
+ hbox->pack_end(*_color_button, false, false);
+
+ _color_button->signal_color_set().connect(sigc::mem_fun(this, &ParamColor::_onColorButtonChanged));
+ } else {
+ Gtk::Widget *selector = Gtk::manage(new Inkscape::UI::Widget::ColorNotebook(_color));
+ hbox->pack_start(*selector, true, true, 0);
+ selector->show();
+ }
+ hbox->show();
+ return hbox;
+
+}
+
+void ParamColor::_onColorChanged()
+{
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setUInt(pref_name(), _color.value());
+
+ if (_changeSignal)
+ _changeSignal->emit();
+}
+
+void ParamColor::_onColorButtonChanged()
+{
+ Gdk::RGBA rgba = _color_button->get_rgba();
+ unsigned int value = ((rgba.get_red_u() >> 8) << 24) +
+ ((rgba.get_green_u() >> 8) << 16) +
+ ((rgba.get_blue_u() >> 8) << 8) +
+ ((rgba.get_alpha_u() >> 8) << 0);
+ set(value);
+}
+
+std::string ParamColor::value_to_string() const
+{
+ char value_string[16];
+ snprintf(value_string, 16, "%u", _color.value());
+ return value_string;
+}
+
+}; /* namespace Extension */
+}; /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-color.h b/src/extension/prefdialog/parameter-color.h
new file mode 100644
index 0000000..4fef4b6
--- /dev/null
+++ b/src/extension/prefdialog/parameter-color.h
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef SEEN_INK_EXTENSION_PARAMCOLOR_H__
+#define SEEN_INK_EXTENSION_PARAMCOLOR_H__
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+#include "ui/selected-color.h"
+
+namespace Gtk {
+class Widget;
+class ColorButton;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class ParamColor : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, COLOR_BUTTON
+ };
+
+ ParamColor(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+ ~ParamColor() override;
+
+ /** Returns \c _value, with a \i const to protect it. */
+ unsigned int get() const { return _color.value(); }
+
+ unsigned int set(unsigned int in);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ sigc::signal<void> *_changeSignal;
+
+private:
+ void _onColorChanged();
+ void _onColorButtonChanged();
+
+ /** Internal value of this parameter */
+ Inkscape::UI::SelectedColor _color;
+
+ sigc::connection _color_changed;
+ sigc::connection _color_released;
+
+ Gtk::ColorButton *_color_button;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+}; // class ParamColor
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_PARAMCOLOR_H__
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter-float.cpp b/src/extension/prefdialog/parameter-float.cpp
new file mode 100644
index 0000000..7d8c843
--- /dev/null
+++ b/src/extension/prefdialog/parameter-float.cpp
@@ -0,0 +1,191 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-float.h"
+
+#include <iomanip>
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "ui/widget/spin-scale.h"
+#include "ui/widget/spinbutton.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+ParamFloat::ParamFloat(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ _value = g_ascii_strtod(value, nullptr);
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getDouble(pref_name(), _value);
+
+ // parse and apply limits
+ const char *min = xml->attribute("min");
+ if (min) {
+ _min = g_ascii_strtod(min, nullptr);
+ }
+
+ const char *max = xml->attribute("max");
+ if (max) {
+ _max = g_ascii_strtod(max, nullptr);
+ }
+
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ if (_value > _max) {
+ _value = _max;
+ }
+
+ // parse precision
+ const char *precision = xml->attribute("precision");
+ if (precision != nullptr) {
+ _precision = strtol(precision, nullptr, 0);
+ }
+
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "full")) {
+ _mode = FULL;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set to.
+ */
+double ParamFloat::set(double in)
+{
+ _value = in;
+ if (_value > _max) {
+ _value = _max;
+ }
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setDouble(pref_name(), _value);
+
+ return _value;
+}
+
+std::string ParamFloat::value_to_string() const
+{
+ static constexpr auto digits10 = std::numeric_limits<double>::digits10; // number of decimal digits that are ensured to be precise
+ return Glib::ustring::format(std::setprecision(digits10), _value);
+}
+
+/** A class to make an adjustment that uses Extension params. */
+class ParamFloatAdjustment : public Gtk::Adjustment {
+ /** The parameter to adjust. */
+ ParamFloat *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /** Make the adjustment using an extension and the string
+ describing the parameter. */
+ ParamFloatAdjustment(ParamFloat *param, sigc::signal<void> *changeSignal)
+ : Gtk::Adjustment(0.0, param->min(), param->max(), 0.1, 1.0, 0)
+ , _pref(param)
+ , _changeSignal(changeSignal) {
+ this->set_value(_pref->get());
+ this->signal_value_changed().connect(sigc::mem_fun(this, &ParamFloatAdjustment::val_changed));
+ return;
+ };
+
+ void val_changed ();
+}; /* class ParamFloatAdjustment */
+
+/**
+ * A function to respond to the value_changed signal from the adjustment.
+ *
+ * This function just grabs the value from the adjustment and writes
+ * it to the parameter. Very simple, but yet beautiful.
+ */
+void ParamFloatAdjustment::val_changed()
+{
+ _pref->set(this->get_value());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+ return;
+}
+
+/**
+ * Creates a Float Adjustment for a float parameter.
+ *
+ * Builds a hbox with a label and a float adjustment in it.
+ */
+Gtk::Widget *ParamFloat::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ auto pfa = new ParamFloatAdjustment(this, changeSignal);
+ Glib::RefPtr<Gtk::Adjustment> fadjust(pfa);
+
+ if (_mode == FULL) {
+
+ Glib::ustring text;
+ if (_text != nullptr)
+ text = _text;
+ UI::Widget::SpinScale *scale = Gtk::manage(new UI::Widget::SpinScale(text, fadjust, _precision));
+ scale->set_size_request(400, -1);
+ scale->show();
+ hbox->pack_start(*scale, true, true);
+
+ }
+ else if (_mode == DEFAULT) {
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, true, true);
+
+ auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 0.1, _precision));
+ spin->show();
+ hbox->pack_start(*spin, false, false);
+ }
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-float.h b/src/extension/prefdialog/parameter-float.h
new file mode 100644
index 0000000..a74470a
--- /dev/null
+++ b/src/extension/prefdialog/parameter-float.h
@@ -0,0 +1,79 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMFLOAT_H_SEEN
+#define INK_EXTENSION_PARAMFLOAT_H_SEEN
+
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class ParamFloat : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, FULL
+ };
+
+ ParamFloat(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** Returns \c _value. */
+ double get() const { return _value; }
+
+ double set(double in);
+
+ double max () { return _max; }
+
+ double min () { return _min; }
+
+ double precision () { return _precision; }
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+private:
+ /** Internal value. */
+ double _value = 0;
+
+ /** limits */
+ // TODO: do these defaults make sense or should we be unbounded by default?
+ double _min = 0;
+ double _max = 10;
+
+ /** numeric precision (i.e. number of digits) */
+ int _precision = 1;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* INK_EXTENSION_PARAMFLOAT_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter-int.cpp b/src/extension/prefdialog/parameter-int.cpp
new file mode 100644
index 0000000..c94b75b
--- /dev/null
+++ b/src/extension/prefdialog/parameter-int.cpp
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-int.h"
+
+#include <gtkmm/adjustment.h>
+#include <gtkmm/box.h>
+
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "ui/widget/spinbutton.h"
+#include "ui/widget/spin-scale.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+
+ParamInt::ParamInt(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ if (xml->firstChild()) {
+ const char *value = xml->firstChild()->content();
+ if (value) {
+ _value = strtol(value, nullptr, 0);
+ }
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getInt(pref_name(), _value);
+
+ // parse and apply limits
+ const char *min = xml->attribute("min");
+ if (min) {
+ _min = strtol(min, nullptr, 0);
+ }
+
+ const char *max = xml->attribute("max");
+ if (max) {
+ _max = strtol(max, nullptr, 0);
+ }
+
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ if (_value > _max) {
+ _value = _max;
+ }
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "full")) {
+ _mode = FULL;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set to.
+ */
+int ParamInt::set(int in)
+{
+ _value = in;
+ if (_value > _max) {
+ _value = _max;
+ }
+ if (_value < _min) {
+ _value = _min;
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setInt(pref_name(), _value);
+
+ return _value;
+}
+
+/** A class to make an adjustment that uses Extension params. */
+class ParamIntAdjustment : public Gtk::Adjustment {
+ /** The parameter to adjust. */
+ ParamInt *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /** Make the adjustment using an extension and the string describing the parameter. */
+ ParamIntAdjustment(ParamInt *param, sigc::signal<void> *changeSignal)
+ : Gtk::Adjustment(0.0, param->min(), param->max(), 1.0, 10.0, 0)
+ , _pref(param)
+ , _changeSignal(changeSignal)
+ {
+ this->set_value(_pref->get());
+ this->signal_value_changed().connect(sigc::mem_fun(this, &ParamIntAdjustment::val_changed));
+ };
+
+ void val_changed ();
+}; /* class ParamIntAdjustment */
+
+/**
+ * A function to respond to the value_changed signal from the adjustment.
+ *
+ * This function just grabs the value from the adjustment and writes
+ * it to the parameter. Very simple, but yet beautiful.
+ */
+void ParamIntAdjustment::val_changed()
+{
+ _pref->set((int)this->get_value());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+/**
+ * Creates a Int Adjustment for a int parameter.
+ *
+ * Builds a hbox with a label and a int adjustment in it.
+ */
+Gtk::Widget *
+ParamInt::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ auto pia = new ParamIntAdjustment(this, changeSignal);
+ Glib::RefPtr<Gtk::Adjustment> fadjust(pia);
+
+ if (_mode == FULL) {
+
+ Glib::ustring text;
+ if (_text != nullptr)
+ text = _text;
+ UI::Widget::SpinScale *scale = Gtk::manage(new UI::Widget::SpinScale(text, fadjust, 0));
+ scale->set_size_request(400, -1);
+ scale->show();
+ hbox->pack_start(*scale, true, true);
+ } else if (_mode == DEFAULT) {
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, true, true);
+
+ auto spin = Gtk::manage(new Inkscape::UI::Widget::SpinButton(fadjust, 1.0, 0));
+ spin->show();
+ hbox->pack_start(*spin, false, false);
+ }
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+std::string ParamInt::value_to_string() const
+{
+ char value_string[32];
+ snprintf(value_string, 32, "%d", _value);
+ return value_string;
+}
+
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/prefdialog/parameter-int.h b/src/extension/prefdialog/parameter-int.h
new file mode 100644
index 0000000..da43eb7
--- /dev/null
+++ b/src/extension/prefdialog/parameter-int.h
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMINT_H_SEEN
+#define INK_EXTENSION_PARAMINT_H_SEEN
+
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class ParamInt : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, FULL
+ };
+
+ ParamInt(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** Returns \c _value. */
+ int get() const { return _value; }
+
+ int set(int in);
+
+ int max () { return _max; }
+
+ int min () { return _min; }
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+private:
+ /** Internal value. */
+ int _value = 0;
+
+ /** limits */
+ // TODO: do these defaults make sense or should we be unbounded by default?
+ int _min = 0;
+ int _max = 10;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* INK_EXTENSION_PARAMINT_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter-notebook.cpp b/src/extension/prefdialog/parameter-notebook.cpp
new file mode 100644
index 0000000..2f431db
--- /dev/null
+++ b/src/extension/prefdialog/parameter-notebook.cpp
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ * Notebook and NotebookPage parameters for extensions.
+ */
+
+/*
+ * Authors:
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2006 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-notebook.h"
+
+#include <unordered_set>
+
+#include <gtkmm/box.h>
+#include <gtkmm/notebook.h>
+
+#include "preferences.h"
+
+#include "extension/extension.h"
+
+#include "xml/node.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+ParamNotebook::ParamNotebookPage::ParamNotebookPage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // Read XML tree of page and parse parameters
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
+ chname++;
+ }
+
+ if (InxWidget::is_valid_widget_name(chname)) {
+ InxWidget *widget = InxWidget::make(child_repr, _extension);
+ if (widget) {
+ _children.push_back(widget);
+ }
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') in notebook page in extension '%s'.",
+ chname, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in notebook page in extension '%s'.", _extension->get_id());
+ }
+
+ child_repr = child_repr->next();
+ }
+ }
+}
+
+
+/**
+ * Creates a notebookpage widget for a notebook.
+ *
+ * Builds a notebook page (a vbox) and puts parameters on it.
+ */
+Gtk::Widget *ParamNotebook::ParamNotebookPage::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box * vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL));
+ vbox->set_border_width(GUI_BOX_MARGIN);
+ vbox->set_spacing(GUI_BOX_SPACING);
+
+ // add parameters onto page (if any)
+ for (auto child : _children) {
+ Gtk::Widget *child_widget = child->get_widget(changeSignal);
+ if (child_widget) {
+ int indent = child->get_indent();
+ child_widget->set_margin_start(indent *GUI_INDENTATION);
+ vbox->pack_start(*child_widget, false, true, 0); // fill=true does not have an effect here, but allows the
+ // child to choose to expand by setting hexpand/vexpand
+
+ const char *tooltip = child->get_tooltip();
+ if (tooltip) {
+ child_widget->set_tooltip_text(tooltip);
+ }
+ }
+ }
+
+ vbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(vbox);
+}
+
+/** End ParamNotebookPage **/
+
+
+
+/** ParamNotebook **/
+
+ParamNotebook::ParamNotebook(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // Read XML tree to add pages (allow _page for backwards compatibility)
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (chname && (!strcmp(chname, INKSCAPE_EXTENSION_NS "page") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "_page") )) {
+ ParamNotebookPage *page;
+ page = new ParamNotebookPage(child_repr, ext);
+
+ if (page) {
+ _children.push_back(page);
+ }
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') for parameter '%s' in extension '%s'. Expected 'page'.",
+ chname, _name, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in parameter '%s' in extension '%s'. Expected 'page'.",
+ _name, _extension->get_id());
+ }
+ child_repr = child_repr->next();
+ }
+ }
+ if (_children.empty()) {
+ g_warning("No (valid) pages for parameter '%s' in extension '%s'", _name, _extension->get_id());
+ }
+
+ // check for duplicate page names
+ std::unordered_set<std::string> names;
+ for (auto child : _children) {
+ ParamNotebookPage *page = static_cast<ParamNotebookPage *>(child);
+ auto ret = names.emplace(page->_name);
+ if (!ret.second) {
+ g_warning("Duplicate page name ('%s') for parameter '%s' in extension '%s'.",
+ page->_name, _name, _extension->get_id());
+ }
+ }
+
+ // get value (initialize with value of first page if pref is empty)
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name());
+
+ if (_value.empty()) {
+ if (!_children.empty()) {
+ ParamNotebookPage *first_page = dynamic_cast<ParamNotebookPage *>(_children[0]);
+ _value = first_page->_name;
+ }
+ }
+}
+
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The number of the page to set as new value.
+ */
+const Glib::ustring& ParamNotebook::set(const int in)
+{
+ int i = in < _children.size() ? in : _children.size()-1;
+ ParamNotebookPage *page = dynamic_cast<ParamNotebookPage *>(_children[i]);
+
+ if (page) {
+ _value = page->_name;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value);
+ }
+
+ return _value;
+}
+
+std::string ParamNotebook::value_to_string() const
+{
+ return _value.raw();
+}
+
+
+/** A special category of Gtk::Notebook to handle notebook parameters. */
+class NotebookWidget : public Gtk::Notebook {
+private:
+ ParamNotebook *_pref;
+public:
+ /**
+ * Build a notebookpage preference for the given parameter.
+ * @param pref Where to get the string (pagename) from, and where to put it when it changes.
+ */
+ NotebookWidget(ParamNotebook *pref)
+ : Gtk::Notebook()
+ , _pref(pref)
+ , activated(false)
+ {
+ // don't have to set the correct page: this is done in ParamNotebook::get_widget hook function
+ this->signal_switch_page().connect(sigc::mem_fun(this, &NotebookWidget::changed_page));
+ }
+
+ void changed_page(Gtk::Widget *page, guint pagenum);
+
+ bool activated;
+};
+
+/**
+ * Respond to the selected page of notebook changing.
+ * This function responds to the changing by reporting it to
+ * ParamNotebook. The change is only reported when the notebook
+ * is actually visible. This to exclude 'fake' changes when the
+ * notebookpages are added or removed.
+ */
+void NotebookWidget::changed_page(Gtk::Widget * /*page*/, guint pagenum)
+{
+ if (get_visible()) {
+ _pref->set((int)pagenum);
+ }
+}
+
+/**
+ * Creates a Notebook widget for a notebook parameter.
+ *
+ * Builds a notebook and puts pages in it.
+ */
+Gtk::Widget *ParamNotebook::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ NotebookWidget *notebook = Gtk::manage(new NotebookWidget(this));
+
+ // add pages (if any) and switch to previously selected page
+ int current_page = -1;
+ int selected_page = -1;
+ for (auto child : _children) {
+ ParamNotebookPage *page = dynamic_cast<ParamNotebookPage *>(child);
+ g_assert(child); // A ParamNotebook has only children of type ParamNotebookPage.
+ // If we receive a non-page child here something is very wrong!
+ current_page++;
+
+ Gtk::Widget *page_widget = page->get_widget(changeSignal);
+
+ Glib::ustring page_text = page->_text;
+ if (page->_translatable != NO) { // translate unless explicitly marked untranslatable
+ page_text = page->get_translation(page_text.c_str());
+ }
+
+ notebook->append_page(*page_widget, page_text);
+
+ if (_value == page->_name) {
+ selected_page = current_page;
+ }
+ }
+ if (selected_page >= 0) {
+ notebook->set_current_page(selected_page);
+ }
+
+ notebook->show();
+
+ return static_cast<Gtk::Widget *>(notebook);
+}
+
+
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/prefdialog/parameter-notebook.h b/src/extension/prefdialog/parameter-notebook.h
new file mode 100644
index 0000000..b64e5c6
--- /dev/null
+++ b/src/extension/prefdialog/parameter-notebook.h
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMNOTEBOOK_H_SEEN
+#define INK_EXTENSION_PARAMNOTEBOOK_H_SEEN
+
+/** \file
+ * Notebook parameter for extensions.
+ */
+
+/*
+ * Author:
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2006 Author
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+#include <vector>
+
+#include <glibmm/ustring.h>
+
+
+namespace Gtk {
+class Widget;
+}
+
+
+namespace Inkscape {
+namespace Extension {
+
+class Extension;
+
+
+/** A class to represent a notebook parameter of an extension. */
+class ParamNotebook : public InxParameter {
+private:
+ /** Internal value. */
+ Glib::ustring _value;
+
+ /**
+ * A class to represent the pages of a notebook parameter of an extension.
+ */
+ class ParamNotebookPage : public InxParameter {
+ friend class ParamNotebook;
+ public:
+ ParamNotebookPage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ // ParamNotebookPage is not a real parameter (it has no value), so make sure it does not return one
+ std::string value_to_string() const override { return ""; };
+ }; /* class ParamNotebookPage */
+
+public:
+ ParamNotebook(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ const Glib::ustring& get() { return _value; }
+ const Glib::ustring& set(const int in);
+}; /* class ParamNotebook */
+
+
+
+
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif /* INK_EXTENSION_PARAMNOTEBOOK_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter-optiongroup.cpp b/src/extension/prefdialog/parameter-optiongroup.cpp
new file mode 100644
index 0000000..4652788
--- /dev/null
+++ b/src/extension/prefdialog/parameter-optiongroup.cpp
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** \file
+ *extension parameter for options with multiple predefined value choices
+ *
+ * Currently implemented as either Gtk::RadioButton or Gtk::ComboBoxText
+ */
+
+/*
+ * Author:
+ * Johan Engelen <johan@shouraizou.nl>
+ *
+ * Copyright (C) 2006-2007 Johan Engelen
+ * Copyright (C) 2008 Jon A. Cruz
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-optiongroup.h"
+
+#include <unordered_set>
+
+#include <gtkmm/box.h>
+#include <gtkmm/comboboxtext.h>
+#include <gtkmm/radiobutton.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+ParamOptionGroup::ParamOptionGroup(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // Read valid optiongroup choices from XML tree, i,e.
+ // - <option> elements
+ // - <item> elements (for backwards-compatibility with params of type enum)
+ // - underscored variants of both (for backwards-compatibility)
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (chname && (!strcmp(chname, INKSCAPE_EXTENSION_NS "option") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "_option") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "item") ||
+ !strcmp(chname, INKSCAPE_EXTENSION_NS "_item")) ) {
+ child_repr->setAttribute("name", "option"); // TODO: hack to allow options to be parameters
+ child_repr->setAttribute("gui-text", "option"); // TODO: hack to allow options to be parameters
+ ParamOptionGroupOption *param = new ParamOptionGroupOption(child_repr, ext, this);
+ choices.push_back(param);
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') for parameter '%s' in extension '%s'. Expected 'option'.",
+ chname, _name, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in parameter '%s' in extension '%s'. Expected 'option'.",
+ _name, _extension->get_id());
+ }
+ child_repr = child_repr->next();
+ }
+ }
+ if (choices.empty()) {
+ g_warning("No (valid) choices for parameter '%s' in extension '%s'", _name, _extension->get_id());
+ }
+
+ // check for duplicate option texts and values
+ std::unordered_set<std::string> texts;
+ std::unordered_set<std::string> values;
+ for (auto choice : choices) {
+ auto ret1 = texts.emplace(choice->_text.raw());
+ if (!ret1.second) {
+ g_warning("Duplicate option text ('%s') for parameter '%s' in extension '%s'.",
+ choice->_text.c_str(), _name, _extension->get_id());
+ }
+ auto ret2 = values.emplace(choice->_value.raw());
+ if (!ret2.second) {
+ g_warning("Duplicate option value ('%s') for parameter '%s' in extension '%s'.",
+ choice->_value.c_str(), _name, _extension->get_id());
+ }
+ }
+
+ // get value (initialize with value of first choice if pref is empty)
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name());
+
+ if (_value.empty()) {
+ if (!choices.empty()) {
+ _value = choices[0]->_value;
+ }
+ }
+
+ // parse appearance
+ // (we support "combo" and "radio"; "minimal" is for backwards-compatibility)
+ if (_appearance) {
+ if (!strcmp(_appearance, "combo") || !strcmp(_appearance, "minimal")) {
+ _mode = COMBOBOX;
+ } else if (!strcmp(_appearance, "radio")) {
+ _mode = RADIOBUTTON;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+ParamOptionGroup::~ParamOptionGroup ()
+{
+ // destroy choice strings
+ for (auto choice : choices) {
+ delete choice;
+ }
+}
+
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets ONLY the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set.
+ */
+const Glib::ustring& ParamOptionGroup::set(Glib::ustring in)
+{
+ if (contains(in)) {
+ _value = in;
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value.c_str());
+ } else {
+ g_warning("Could not set value ('%s') for parameter '%s' in extension '%s'. Not a valid choice.",
+ in.c_str(), _name, _extension->get_id());
+ }
+
+ return _value;
+}
+
+bool ParamOptionGroup::contains(const Glib::ustring text) const
+{
+ for (auto choice : choices) {
+ if (choice->_value == text) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+std::string ParamOptionGroup::value_to_string() const
+{
+ return _value.raw();
+}
+
+/**
+ * Returns the value for the options label parameter
+ */
+Glib::ustring ParamOptionGroup::value_from_label(const Glib::ustring label)
+{
+ Glib::ustring value;
+
+ for (auto choice : choices) {
+ if (choice->_text == label) {
+ value = choice->_value;
+ break;
+ }
+ }
+
+ return value;
+}
+
+
+
+/** A special RadioButton class to use in ParamOptionGroup. */
+class RadioWidget : public Gtk::RadioButton {
+private:
+ ParamOptionGroup *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ RadioWidget(Gtk::RadioButtonGroup& group, const Glib::ustring& label,
+ ParamOptionGroup *pref, sigc::signal<void> *changeSignal)
+ : Gtk::RadioButton(group, label)
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ add_changesignal();
+ };
+
+ void add_changesignal() {
+ this->signal_toggled().connect(sigc::mem_fun(this, &RadioWidget::changed));
+ };
+
+ void changed();
+};
+
+/**
+ * Respond to the selected radiobutton changing.
+ *
+ * This function responds to the radiobutton selection changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void RadioWidget::changed()
+{
+ if (this->get_active()) {
+ Glib::ustring value = _pref->value_from_label(this->get_label());
+ _pref->set(value.c_str());
+ }
+
+ if (_changeSignal) {
+ _changeSignal->emit();
+ }
+}
+
+
+/** A special ComboBoxText class to use in ParamOptionGroup. */
+class ComboWidget : public Gtk::ComboBoxText {
+private:
+ ParamOptionGroup *_pref;
+ sigc::signal<void> *_changeSignal;
+
+public:
+ ComboWidget(ParamOptionGroup *pref, sigc::signal<void> *changeSignal)
+ : _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ this->signal_changed().connect(sigc::mem_fun(this, &ComboWidget::changed));
+ }
+
+ ~ComboWidget() override = default;
+
+ void changed();
+};
+
+void ComboWidget::changed()
+{
+ if (_pref) {
+ Glib::ustring value = _pref->value_from_label(get_active_text());
+ _pref->set(value.c_str());
+ }
+
+ if (_changeSignal) {
+ _changeSignal->emit();
+ }
+}
+
+
+
+/**
+ * Creates the widget for the optiongroup parameter.
+ */
+Gtk::Widget *ParamOptionGroup::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ auto hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ hbox->pack_start(*label, false, false);
+
+ if (_mode == COMBOBOX) {
+ ComboWidget *combo = Gtk::manage(new ComboWidget(this, changeSignal));
+
+ for (auto choice : choices) {
+ combo->append(choice->_text);
+ if (choice->_value == _value) {
+ combo->set_active_text(choice->_text);
+ }
+ }
+
+ if (combo->get_active_row_number() == -1) {
+ combo->set_active(0);
+ }
+
+ hbox->pack_end(*combo, false, false);
+ } else if (_mode == RADIOBUTTON) {
+ label->set_valign(Gtk::ALIGN_START); // align label and first radio
+
+ Gtk::Box *radios = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0));
+ Gtk::RadioButtonGroup group;
+
+ for (auto choice : choices) {
+ RadioWidget *radio = Gtk::manage(new RadioWidget(group, choice->_text, this, changeSignal));
+ radios->pack_start(*radio, true, true);
+ if (choice->_value == _value) {
+ radio->set_active();
+ }
+ }
+
+ hbox->pack_end(*radios, false, false);
+ }
+
+ hbox->show_all();
+ return static_cast<Gtk::Widget *>(hbox);
+}
+
+
+ParamOptionGroup::ParamOptionGroupOption::ParamOptionGroupOption(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext,
+ const Inkscape::Extension::ParamOptionGroup *parent)
+ : InxParameter(xml, ext)
+{
+ // get content (=label) of option and translate it
+ const char *text = nullptr;
+ if (xml->firstChild()) {
+ text = xml->firstChild()->content();
+ }
+ if (text) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ _text = get_translation(text);
+ } else {
+ _text = text;
+ }
+ } else {
+ g_warning("Missing content in option of parameter '%s' in extension '%s'.",
+ parent->_name, _extension->get_id());
+ }
+
+ // get string value of option
+ const char *value = xml->attribute("value");
+ if (value) {
+ _value = value;
+ } else {
+ if (text) {
+ const char *name = xml->name();
+ if (!strcmp(name, INKSCAPE_EXTENSION_NS "item") || !strcmp(name, INKSCAPE_EXTENSION_NS "_item")) {
+ _value = text; // use untranslated UI text as value (for backwards-compatibility)
+ } else {
+ _value = _text; // use translated UI text as value
+ }
+ } else {
+ g_warning("Missing value for option '%s' of parameter '%s' in extension '%s'.",
+ _text.c_str(), parent->_name, _extension->get_id());
+ }
+ }
+}
+
+
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter-optiongroup.h b/src/extension/prefdialog/parameter-optiongroup.h
new file mode 100644
index 0000000..533b131
--- /dev/null
+++ b/src/extension/prefdialog/parameter-optiongroup.h
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN
+#define INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN
+
+/** \file
+ * extension parameter for options with multiple predefined value choices
+ *
+ * Currently implemented as either Gtk::RadioButton or Gtk::ComboBoxText
+ */
+
+/*
+ * Authors:
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2006-2007 Johan Engelen
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+#include <vector>
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace Extension {
+
+class Extension;
+
+
+
+// \brief A class to represent an optiongroup (option with multiple predefined value choices) parameter of an extension
+class ParamOptionGroup : public InxParameter {
+public:
+ enum AppearanceMode {
+ RADIOBUTTON, COMBOBOX
+ };
+
+ ParamOptionGroup(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+ ~ParamOptionGroup() override;
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ Glib::ustring value_from_label(const Glib::ustring label);
+
+ const Glib::ustring& get() const { return _value; }
+
+ const Glib::ustring& set(const Glib::ustring in);
+
+ /**
+ * @returns true if text is a valid choice for this option group
+ * @param text string value to check (this is an actual option value, not the user-visible option name!)
+ */
+ bool contains(const Glib::ustring text) const;
+
+private:
+ /** \brief Internal value. */
+ Glib::ustring _value;
+
+ /** appearance mode **/
+ AppearanceMode _mode = RADIOBUTTON;
+
+ /* For internal use only. */
+ class ParamOptionGroupOption : public InxParameter {
+ friend class ParamOptionGroup;
+ public:
+ ParamOptionGroupOption(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext,
+ const Inkscape::Extension::ParamOptionGroup *parent);
+ private:
+ Glib::ustring _value;
+ Glib::ustring _text;
+ };
+
+ std::vector<ParamOptionGroupOption *> choices; /**< List of available options for the option group */
+}; /* class ParamOptionGroup */
+
+
+
+
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /*INK_EXTENSION_PARAMOPTIONGROUP_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/prefdialog/parameter-path.cpp b/src/extension/prefdialog/parameter-path.cpp
new file mode 100644
index 0000000..83b54fc
--- /dev/null
+++ b/src/extension/prefdialog/parameter-path.cpp
@@ -0,0 +1,268 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Path parameter for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-path.h"
+
+#include <boost/algorithm/string/case_conv.hpp>
+#include <boost/algorithm/string/join.hpp>
+
+#include <glibmm/i18n.h>
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+#include <glibmm/regex.h>
+
+#include <gtkmm/box.h>
+#include <gtkmm/button.h>
+#include <gtkmm/dialog.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/filechoosernative.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Extension {
+
+ParamPath::ParamPath(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ const char *value = nullptr;
+ if (xml->firstChild()) {
+ value = xml->firstChild()->content();
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name()).raw();
+
+ if (_value.empty() && value) {
+ _value = value;
+ }
+
+ // parse selection mode
+ const char *mode = xml->attribute("mode");
+ if (mode) {
+ if (!strcmp(mode, "file")) {
+ // this is the default
+ } else if (!strcmp(mode, "files")) {
+ _select_multiple = true;
+ } else if (!strcmp(mode, "folder")) {
+ _mode = FOLDER;
+ } else if (!strcmp(mode, "folders")) {
+ _mode = FOLDER;
+ _select_multiple = true;
+ } else if (!strcmp(mode, "file_new")) {
+ _mode = FILE_NEW;
+ } else if (!strcmp(mode, "folder_new")) {
+ _mode = FOLDER_NEW;
+ } else {
+ g_warning("Invalid value ('%s') for mode of parameter '%s' in extension '%s'",
+ mode, _name, _extension->get_id());
+ }
+ }
+
+ // parse filetypes
+ const char *filetypes = xml->attribute("filetypes");
+ if (filetypes) {
+ _filetypes = Glib::Regex::split_simple("," , filetypes);
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * @param in The value to set to.
+ */
+const std::string& ParamPath::set(const std::string &in)
+{
+ _value = in;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value);
+
+ return _value;
+}
+
+std::string ParamPath::value_to_string() const
+{
+ if (!Glib::path_is_absolute(_value) && !_value.empty()) {
+ return Glib::build_filename(_extension->get_base_directory(), _value);
+ } else {
+ return _value;
+ }
+}
+
+
+/** A special type of Gtk::Entry to handle path parameters. */
+class ParamPathEntry : public Gtk::Entry {
+private:
+ ParamPath *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /**
+ * Build a string preference for the given parameter.
+ * @param pref Where to get the string from, and where to put it
+ * when it changes.
+ */
+ ParamPathEntry(ParamPath *pref, sigc::signal<void> *changeSignal)
+ : Gtk::Entry()
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ this->set_text(_pref->get());
+ this->signal_changed().connect(sigc::mem_fun(this, &ParamPathEntry::changed_text));
+ };
+ void changed_text();
+};
+
+
+/**
+ * Respond to the text box changing.
+ *
+ * This function responds to the box changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void ParamPathEntry::changed_text()
+{
+ auto data = this->get_text();
+ _pref->set(data.c_str());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+/**
+ * Creates a text box for the string parameter.
+ *
+ * Builds a hbox with a label and a text box in it.
+ */
+Gtk::Widget *ParamPath::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ hbox->pack_start(*label, false, false);
+
+ ParamPathEntry *textbox = Gtk::manage(new ParamPathEntry(this, changeSignal));
+ textbox->show();
+ hbox->pack_start(*textbox, true, true);
+ _entry = textbox;
+
+ Gtk::Button *button = Gtk::manage(new Gtk::Button("…"));
+ button->show();
+ hbox->pack_end(*button, false, false);
+ button->signal_clicked().connect(sigc::mem_fun(*this, &ParamPath::on_button_clicked));
+
+ hbox->show();
+
+ return dynamic_cast<Gtk::Widget *>(hbox);
+}
+
+/**
+ * Create and show the file chooser dialog when the "…" button is clicked
+ * Then set the value of the ParamPathEntry holding the current value accordingly
+ */
+void ParamPath::on_button_clicked()
+{
+ // set-up action and dialog title according to 'mode'
+ Gtk::FileChooserAction action;
+ std::string dialog_title;
+ if (_mode == FILE) {
+ // pick the "save" variants here - otherwise the dialog will only accept existing files
+ action = Gtk::FILE_CHOOSER_ACTION_OPEN;
+ if (_select_multiple) {
+ dialog_title = _("Select existing files");
+ } else {
+ dialog_title = _("Select existing file");
+ }
+ } else if (_mode == FOLDER) {
+ // pick the "create" variant here - otherwise the dialog will only accept existing folders
+ action = Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER;
+ if (_select_multiple) {
+ dialog_title = _("Select existing folders");
+ } else {
+ dialog_title = _("Select existing folder");
+ }
+ } else if (_mode == FILE_NEW) {
+ action = Gtk::FILE_CHOOSER_ACTION_SAVE;
+ dialog_title = _("Choose file name");
+ } else if (_mode == FOLDER_NEW) {
+ action = Gtk::FILE_CHOOSER_ACTION_CREATE_FOLDER;
+ dialog_title = _("Choose folder name");
+ } else {
+ g_assert_not_reached();
+ }
+
+ // create file chooser dialog
+ auto file_chooser = Gtk::FileChooserNative::create(dialog_title + "…", action, _("Select"));
+ file_chooser->set_select_multiple(_select_multiple);
+ file_chooser->set_do_overwrite_confirmation(true);
+ file_chooser->set_create_folders(true);
+
+ // set FileFilter according to 'filetype' attribute
+ if (!_filetypes.empty() && _mode != FOLDER && _mode != FOLDER_NEW) {
+ Glib::RefPtr<Gtk::FileFilter> file_filter = Gtk::FileFilter::create();
+
+ for (auto filetype : _filetypes) {
+ file_filter->add_pattern(Glib::ustring::compose("*.%1", filetype));
+ }
+
+ std::string filter_name = boost::algorithm::join(_filetypes, "+");
+ boost::algorithm::to_upper(filter_name);
+ file_filter->set_name(filter_name);
+
+ file_chooser->add_filter(file_filter);
+ }
+
+ // set current file/folder suitable for current value
+ // (use basepath of first filename; relative paths are considered relative to .inx file's location)
+ if (!_value.empty()) {
+ std::string first_filename = _value.substr(0, _value.find("|"));
+
+ if (!Glib::path_is_absolute(first_filename)) {
+ first_filename = Glib::build_filename(_extension->get_base_directory(), first_filename);
+ }
+
+ std::string dirname = Glib::path_get_dirname(first_filename);
+ if (Glib::file_test(dirname, Glib::FILE_TEST_IS_DIR)) {
+ file_chooser->set_current_folder(dirname);
+ }
+
+ if(_mode == FILE_NEW || _mode == FOLDER_NEW) {
+ file_chooser->set_current_name(Glib::path_get_basename(first_filename));
+ } else {
+ if (Glib::file_test(first_filename, Glib::FILE_TEST_EXISTS)) {
+ // TODO: This does not seem to work (at least on Windows)
+ // file_chooser->set_filename(first_filename);
+ }
+ }
+ }
+
+ // show dialog and parse result
+ int res = file_chooser->run();
+ if (res == Gtk::ResponseType::RESPONSE_ACCEPT) {
+ std::vector<std::string> filenames = file_chooser->get_filenames();
+ std::string filenames_joined = boost::algorithm::join(filenames, "|");
+ _entry->set_text(filenames_joined); // let the ParamPathEntry handle the rest (including setting the preference)
+ }
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-path.h b/src/extension/prefdialog/parameter-path.h
new file mode 100644
index 0000000..0fbf911
--- /dev/null
+++ b/src/extension/prefdialog/parameter-path.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Path parameter for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INK_EXTENSION_PARAM_PATH_H_SEEN
+#define INK_EXTENSION_PARAM_PATH_H_SEEN
+
+#include "parameter.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+class ParamPathEntry;
+
+class ParamPath : public InxParameter {
+public:
+ ParamPath(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** \brief Returns \c _value, with a \i const to protect it. */
+ const std::string& get() const { return _value; }
+ const std::string& set(const std::string &in);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+private:
+ enum Mode {
+ FILE, FOLDER, FILE_NEW, FOLDER_NEW
+ };
+
+ /** \brief Internal value. */
+ std::string _value;
+
+ /** selection mode for the file chooser: files or folders? */
+ Mode _mode = FILE;
+
+ /** selection mode for the file chooser: multiple items? */
+ bool _select_multiple = false;
+
+ /** filetypes that should be selectable in file chooser */
+ std::vector<std::string> _filetypes;
+
+ /** pointer to the parameters text entry
+ * keep this around, so we can update the value accordingly in \a on_button_clicked() */
+ ParamPathEntry *_entry;
+
+ void on_button_clicked();
+};
+
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif /* INK_EXTENSION_PARAM_PATH_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter-string.cpp b/src/extension/prefdialog/parameter-string.cpp
new file mode 100644
index 0000000..708731a
--- /dev/null
+++ b/src/extension/prefdialog/parameter-string.cpp
@@ -0,0 +1,228 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter-string.h"
+
+#include <gtkmm/box.h>
+#include <gtkmm/entry.h>
+#include <gtkmm/scrolledwindow.h>
+#include <gtkmm/textview.h>
+#include <glibmm/regex.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+#include "preferences.h"
+
+namespace Inkscape {
+namespace Extension {
+
+ParamString::ParamString(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxParameter(xml, ext)
+{
+ // get value
+ const char *value = nullptr;
+ if (xml->firstChild()) {
+ value = xml->firstChild()->content();
+ }
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ _value = prefs->getString(pref_name());
+
+ if (_value.empty() && value) {
+ _value = value;
+ }
+
+ // translate value
+ if (!_value.empty()) {
+ if (_translatable == YES) { // translate only if explicitly marked translatable
+ _value = get_translation(_value.c_str());
+ }
+ }
+
+ // max-length
+ const char *max_length = xml->attribute("max-length");
+ if (!max_length) {
+ max_length = xml->attribute("max_length"); // backwards-compatibility with old name (underscore)
+ }
+ if (max_length) {
+ _max_length = strtoul(max_length, nullptr, 0);
+ }
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "multiline")) {
+ _mode = MULTILINE;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of parameter '%s' in extension '%s'",
+ _appearance, _name, _extension->get_id());
+ }
+ }
+}
+
+/**
+ * A function to set the \c _value.
+ *
+ * This function sets the internal value, but it also sets the value
+ * in the preferences structure. To put it in the right place \c pref_name() is used.
+ *
+ * To copy the data into _value the old memory must be free'd first.
+ * It is important to note that \c g_free handles \c NULL just fine. Then
+ * the passed in value is duplicated using \c g_strdup().
+ *
+ * @param in The value to set to.
+ */
+const Glib::ustring& ParamString::set(const Glib::ustring in)
+{
+ _value = in;
+
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ prefs->setString(pref_name(), _value);
+
+ return _value;
+}
+
+std::string ParamString::value_to_string() const
+{
+ return _value.raw();
+}
+
+
+
+/** A special type of Gtk::Entry to handle string parameters. */
+class ParamStringEntry : public Gtk::Entry {
+private:
+ ParamString *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /**
+ * Build a string preference for the given parameter.
+ * @param pref Where to get the string from, and where to put it
+ * when it changes.
+ */
+ ParamStringEntry(ParamString *pref, sigc::signal<void> *changeSignal)
+ : Gtk::Entry()
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ this->set_text(_pref->get());
+ this->set_max_length(_pref->getMaxLength()); //Set the max length - default zero means no maximum
+ this->signal_changed().connect(sigc::mem_fun(this, &ParamStringEntry::changed_text));
+ };
+ void changed_text();
+};
+
+
+/**
+ * Respond to the text box changing.
+ *
+ * This function responds to the box changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void ParamStringEntry::changed_text()
+{
+ Glib::ustring data = this->get_text();
+ _pref->set(data.c_str());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+
+
+/** A special type of Gtk::TextView to handle multiline string parameters. */
+class ParamMultilineStringEntry : public Gtk::TextView {
+private:
+ ParamString *_pref;
+ sigc::signal<void> *_changeSignal;
+public:
+ /**
+ * Build a string preference for the given parameter.
+ * @param pref Where to get the string from, and where to put it
+ * when it changes.
+ */
+ ParamMultilineStringEntry(ParamString *pref, sigc::signal<void> *changeSignal)
+ : Gtk::TextView()
+ , _pref(pref)
+ , _changeSignal(changeSignal)
+ {
+ // replace literal '\n' with actual newlines for multiline strings
+ Glib::ustring value = Glib::Regex::create("\\\\n")->replace_literal(_pref->get(), 0, "\n", (Glib::RegexMatchFlags)0);
+
+ this->get_buffer()->set_text(value);
+ this->get_buffer()->signal_changed().connect(sigc::mem_fun(this, &ParamMultilineStringEntry::changed_text));
+ };
+ void changed_text();
+};
+
+/**
+ * Respond to the text box changing.
+ *
+ * This function responds to the box changing by grabbing the value
+ * from the text box and putting it in the parameter.
+ */
+void ParamMultilineStringEntry::changed_text()
+{
+ Glib::ustring data = this->get_buffer()->get_text();
+
+ // always store newlines as literal '\n'
+ data = Glib::Regex::create("\n")->replace_literal(data, 0, "\\n", (Glib::RegexMatchFlags)0);
+
+ _pref->set(data.c_str());
+ if (_changeSignal != nullptr) {
+ _changeSignal->emit();
+ }
+}
+
+
+
+/**
+ * Creates a text box for the string parameter.
+ *
+ * Builds a hbox with a label and a text box in it.
+ */
+Gtk::Widget *ParamString::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, GUI_PARAM_WIDGETS_SPACING));
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label(_text, Gtk::ALIGN_START));
+ label->show();
+ box->pack_start(*label, false, false);
+
+ if (_mode == MULTILINE) {
+ box->set_orientation(Gtk::ORIENTATION_VERTICAL);
+
+ Gtk::ScrolledWindow *textarea = Gtk::manage(new Gtk::ScrolledWindow());
+ textarea->set_vexpand();
+ textarea->set_shadow_type(Gtk::SHADOW_IN);
+
+ ParamMultilineStringEntry *entry = Gtk::manage(new ParamMultilineStringEntry(this, changeSignal));
+ entry->show();
+
+ textarea->add(*entry);
+ textarea->show();
+
+ box->pack_start(*textarea, true, true);
+ } else {
+ Gtk::Widget *entry = Gtk::manage(new ParamStringEntry(this, changeSignal));
+ entry->show();
+
+ box->pack_start(*entry, true, true);
+ }
+
+ box->show();
+
+ return dynamic_cast<Gtk::Widget *>(box);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/parameter-string.h b/src/extension/prefdialog/parameter-string.h
new file mode 100644
index 0000000..3af8311
--- /dev/null
+++ b/src/extension/prefdialog/parameter-string.h
@@ -0,0 +1,66 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#ifndef INK_EXTENSION_PARAMSTRING_H_SEEN
+#define INK_EXTENSION_PARAMSTRING_H_SEEN
+
+/*
+ * Copyright (C) 2005-2007 Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Jon A. Cruz <jon@joncruz.org>
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+
+#include <glibmm/ustring.h>
+
+
+namespace Inkscape {
+namespace Extension {
+
+class ParamString : public InxParameter {
+public:
+ enum AppearanceMode {
+ DEFAULT, MULTILINE
+ };
+
+ ParamString(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ /** \brief Returns \c _value, with a \i const to protect it. */
+ const Glib::ustring& get() const { return _value; }
+ const Glib::ustring& set(const Glib::ustring in);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+ std::string value_to_string() const override;
+
+ void setMaxLength(int maxLength) { _max_length = maxLength; }
+ int getMaxLength() { return _max_length; }
+
+private:
+ /** \brief Internal value. */
+ Glib::ustring _value;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+
+ /** \brief Maximum length of the string in characters (zero meaning unlimited). */
+ int _max_length = 0;
+};
+
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif /* INK_EXTENSION_PARAMSTRING_H_SEEN */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/parameter.cpp b/src/extension/prefdialog/parameter.cpp
new file mode 100644
index 0000000..b8d8386
--- /dev/null
+++ b/src/extension/prefdialog/parameter.cpp
@@ -0,0 +1,306 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Parameters for extensions.
+ */
+/* Author:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2005-2007 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <cstring>
+#include <list>
+
+#include <glibmm/i18n.h>
+#include <sigc++/sigc++.h>
+
+#include "parameter.h"
+#include "parameter-bool.h"
+#include "parameter-color.h"
+#include "parameter-float.h"
+#include "parameter-int.h"
+#include "parameter-notebook.h"
+#include "parameter-optiongroup.h"
+#include "parameter-path.h"
+#include "parameter-string.h"
+#include "widget.h"
+#include "widget-label.h"
+
+#include "extension/extension.h"
+
+#include "object/sp-defs.h"
+
+#include "ui/widget/color-notebook.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+
+// Re-implement ParamDescription for backwards-compatibility, deriving from both, WidgetLabel and InxParameter.
+// TODO: Should go away eventually...
+class ParamDescription : public virtual WidgetLabel, public virtual InxParameter {
+public:
+ ParamDescription(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : WidgetLabel(xml, ext)
+ , InxParameter(xml, ext)
+ {}
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override
+ {
+ return this->WidgetLabel::get_widget(changeSignal);
+ }
+
+ // Well, no, I don't have a value! That's why I should not be an InxParameter!
+ std::string value_to_string() const override { return ""; }
+};
+
+
+
+InxParameter *InxParameter::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext)
+{
+ InxParameter *param = nullptr;
+
+ try {
+ const char *type = in_repr->attribute("type");
+ if (!type) {
+ // we can't create a parameter without type
+ g_warning("Parameter without type in extension '%s'.", in_ext->get_id());
+ } else if(!strcmp(type, "bool") || !strcmp(type, "boolean")) { // support "boolean" for backwards-compatibility
+ param = new ParamBool(in_repr, in_ext);
+ } else if (!strcmp(type, "int")) {
+ param = new ParamInt(in_repr, in_ext);
+ } else if (!strcmp(type, "float")) {
+ param = new ParamFloat(in_repr, in_ext);
+ } else if (!strcmp(type, "string")) {
+ param = new ParamString(in_repr, in_ext);
+ } else if (!strcmp(type, "path")) {
+ param = new ParamPath(in_repr, in_ext);
+ } else if (!strcmp(type, "description")) {
+ // support deprecated "description" for backwards-compatibility
+ in_repr->setAttribute("gui-text", "description"); // TODO: hack to allow descriptions to be parameters
+ param = new ParamDescription(in_repr, in_ext);
+ } else if (!strcmp(type, "notebook")) {
+ in_repr->setAttribute("gui-text", "notebook"); // notebooks have no 'gui-text' (but Parameters need one)
+ param = new ParamNotebook(in_repr, in_ext);
+ } else if (!strcmp(type, "optiongroup")) {
+ param = new ParamOptionGroup(in_repr, in_ext);
+ } else if (!strcmp(type, "enum")) { // support deprecated "enum" for backwards-compatibility
+ in_repr->setAttribute("appearance", "combo");
+ param = new ParamOptionGroup(in_repr, in_ext);
+ } else if (!strcmp(type, "color")) {
+ param = new ParamColor(in_repr, in_ext);
+ } else {
+ g_warning("Unknown parameter type ('%s') in extension '%s'", type, in_ext->get_id());
+ }
+ } catch (const param_no_name&) {
+ } catch (const param_no_text&) {
+ }
+
+ // Note: param could equal nullptr
+ return param;
+}
+
+bool InxParameter::get_bool() const
+{
+ ParamBool const *boolpntr = dynamic_cast<ParamBool const *>(this);
+ if (!boolpntr) {
+ throw param_not_bool_param();
+ }
+ return boolpntr->get();
+}
+
+int InxParameter::get_int() const
+{
+ ParamInt const *intpntr = dynamic_cast<ParamInt const *>(this);
+ if (!intpntr) {
+ // This allows option groups to contain integers. Consider just using this.
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ return prefs->getInt(this->pref_name());
+ }
+ return intpntr->get();
+}
+
+double InxParameter::get_float() const
+{
+ ParamFloat const *floatpntr = dynamic_cast<ParamFloat const *>(this);
+ if (!floatpntr) {
+ throw param_not_float_param();
+ }
+ return floatpntr->get();
+}
+
+const char *InxParameter::get_string() const
+{
+ ParamString const *stringpntr = dynamic_cast<ParamString const *>(this);
+ if (!stringpntr) {
+ throw param_not_string_param();
+ }
+ return stringpntr->get().c_str();
+}
+
+const char *InxParameter::get_optiongroup() const
+{
+ ParamOptionGroup const *param = dynamic_cast<ParamOptionGroup const *>(this);
+ if (!param) {
+ throw param_not_optiongroup_param();
+ }
+ return param->get().c_str();
+}
+
+bool InxParameter::get_optiongroup_contains(const char *value) const
+{
+ ParamOptionGroup const *param = dynamic_cast<ParamOptionGroup const *>(this);
+ if (!param) {
+ throw param_not_optiongroup_param();
+ }
+ return param->contains(value);
+}
+
+unsigned int InxParameter::get_color() const
+{
+ ParamColor const *param = dynamic_cast<ParamColor const *>(this);
+ if (!param) {
+ throw param_not_color_param();
+ }
+ return param->get();
+}
+
+bool InxParameter::set_bool(bool in)
+{
+ ParamBool * boolpntr = dynamic_cast<ParamBool *>(this);
+ if (boolpntr == nullptr)
+ throw param_not_bool_param();
+ return boolpntr->set(in);
+}
+
+int InxParameter::set_int(int in)
+{
+ ParamInt *intpntr = dynamic_cast<ParamInt *>(this);
+ if (intpntr == nullptr)
+ throw param_not_int_param();
+ return intpntr->set(in);
+}
+
+double InxParameter::set_float(double in)
+{
+ ParamFloat * floatpntr;
+ floatpntr = dynamic_cast<ParamFloat *>(this);
+ if (floatpntr == nullptr)
+ throw param_not_float_param();
+ return floatpntr->set(in);
+}
+
+const char *InxParameter::set_string(const char *in)
+{
+ ParamString * stringpntr = dynamic_cast<ParamString *>(this);
+ if (stringpntr == nullptr)
+ throw param_not_string_param();
+ return stringpntr->set(in).c_str();
+}
+
+const char *InxParameter::set_optiongroup(const char *in)
+{
+ ParamOptionGroup *param = dynamic_cast<ParamOptionGroup *>(this);
+ if (!param) {
+ throw param_not_optiongroup_param();
+ }
+ return param->set(in).c_str();
+}
+
+unsigned int InxParameter::set_color(unsigned int in)
+{
+ ParamColor*param = dynamic_cast<ParamColor *>(this);
+ if (param == nullptr)
+ throw param_not_color_param();
+ return param->set(in);
+}
+
+
+InxParameter::InxParameter(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *ext)
+ : InxWidget(in_repr, ext)
+{
+ // name (mandatory for all parameters)
+ const char *name = in_repr->attribute("name");
+ if (name) {
+ _name = g_strstrip(g_strdup(name));
+ }
+ if (!_name || !strcmp(_name, "")) {
+ g_warning("Parameter without name in extension '%s'.", _extension->get_id());
+ throw param_no_name();
+ }
+
+ // gui-text
+ const char *gui_text = in_repr->attribute("gui-text");
+ if (!gui_text) {
+ gui_text = in_repr->attribute("_gui-text"); // backwards-compatibility with underscored variants
+ }
+ if (gui_text) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ gui_text = get_translation(gui_text);
+ }
+ _text = g_strdup(gui_text);
+ }
+ if (!_text && !_hidden) {
+ g_warning("Parameter '%s' in extension '%s' is visible but does not have a 'gui-text'.",
+ _name, _extension->get_id());
+ throw param_no_text();
+ }
+
+ // gui-description (optional)
+ const char *gui_description = in_repr->attribute("gui-description");
+ if (!gui_description) {
+ gui_description = in_repr->attribute("_gui-description"); // backwards-compatibility with underscored variants
+ }
+ if (gui_description) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ gui_description = get_translation(gui_description);
+ }
+ _description = g_strdup(gui_description);
+ }
+}
+
+InxParameter::~InxParameter()
+{
+ g_free(_name);
+ _name = nullptr;
+
+ g_free(_text);
+ _text = nullptr;
+
+ g_free(_description);
+ _description = nullptr;
+}
+
+Glib::ustring InxParameter::pref_name() const
+{
+ return Glib::ustring::compose("/extensions/%1.%2", _extension->get_id(), _name);
+}
+
+std::string InxParameter::value_to_string() const
+{
+ // if we end up here we're missing a definition of ::string() in one of the subclasses
+ g_critical("InxParameter::value_to_string called from parameter '%s' in extension '%s'", _name, _extension->get_id());
+ g_assert_not_reached();
+ return "";
+}
+
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/prefdialog/parameter.h b/src/extension/prefdialog/parameter.h
new file mode 100644
index 0000000..3ae68a3
--- /dev/null
+++ b/src/extension/prefdialog/parameter.h
@@ -0,0 +1,162 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Parameters for extensions.
+ */
+/* Authors:
+ * Ted Gould <ted@gould.cx>
+ * Jon A. Cruz <jon@joncruz.org>
+ *
+ * Copyright (C) 2005-2006 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_PARAM_H__
+#define SEEN_INK_EXTENSION_PARAM_H__
+
+#include "widget.h"
+
+
+namespace Glib {
+class ustring;
+}
+
+
+namespace Inkscape {
+namespace Extension {
+
+/**
+ * A class to represent the parameter of an extension.
+ *
+ * This is really a super class that allows them to abstract all
+ * the different types of parameters into some that can be passed
+ * around. There is also a few functions that are used by all the
+ * different parameters.
+ */
+class InxParameter : public InxWidget {
+public:
+ InxParameter(Inkscape::XML::Node *in_repr,
+ Inkscape::Extension::Extension *ext);
+
+ ~InxParameter() override;
+
+ /** Wrapper to cast to the object and use its function. */
+ bool get_bool() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ int get_int() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ double get_float() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *get_string() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *get_optiongroup() const;
+ bool get_optiongroup_contains(const char *value) const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ unsigned int get_color() const;
+
+ /** Wrapper to cast to the object and use it's function. */
+ bool set_bool(bool in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ int set_int(int in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ double set_float(double in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *set_string(const char *in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ const char *set_optiongroup(const char *in);
+
+ /** Wrapper to cast to the object and use it's function. */
+ unsigned int set_color(unsigned int in);
+
+ char const *name() const { return _name; }
+
+ /**
+ * Creates a new extension parameter for usage in a prefdialog.
+ *
+ * The type of widget created is parsed from the XML representation passed in,
+ * and the suitable subclass constructor is called.
+ *
+ * Called from base-class method of the same name.
+ *
+ * @param in_repr The XML representation describing the widget.
+ * @param in_ext The extension the widget belongs to.
+ * @return a pointer to a new Widget if applicable, null otherwise..
+ */
+ static InxParameter *make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext);
+
+ const char *get_tooltip() const override { return _description; }
+
+ /**
+ * Gets the current value of the parameter in a string form.
+ *
+ * @return String representation of the parameter's value.
+ *
+ * \internal Must be implemented by all derived classes.
+ * Unfortunately it seems we can't make this a pure virtual function,
+ * as InxParameter is not supposed to be abstract.
+ */
+ virtual std::string value_to_string() const;
+
+ /** Recommended spacing between the widgets making up a single Parameter (e.g. label and input) (in px) */
+ const static int GUI_PARAM_WIDGETS_SPACING = 4;
+
+
+ /** An error class for when a parameter is called on a type it is not */
+ class param_no_name {};
+ class param_no_text {};
+ class param_not_bool_param {};
+ class param_not_color_param {};
+ class param_not_float_param {};
+ class param_not_int_param {};
+ class param_not_optiongroup_param {};
+ class param_not_string_param {};
+
+
+protected:
+ /** The name of this parameter. */
+ char *_name = nullptr;
+
+ /** Parameter text to show as the GUI label. */
+ char *_text = nullptr;
+
+ /** Extended description of the parameter (currently shown as tooltip on hover). */
+ char *_description = nullptr;
+
+
+ /* **** member functions **** */
+
+ /**
+ * Build preference name for the current parameter.
+ *
+ * Returns a preference name that can be used with setters and getters from Inkscape::Preferences.
+ * The name is assembled from a fixed root ("/extensions/"), extension ID and parameter name.
+ *
+ * @return: Preference name
+ */
+ Glib::ustring pref_name() const;
+};
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_PARAM_H__
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/prefdialog/prefdialog.cpp b/src/extension/prefdialog/prefdialog.cpp
new file mode 100644
index 0000000..4c96e4d
--- /dev/null
+++ b/src/extension/prefdialog/prefdialog.cpp
@@ -0,0 +1,237 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2005-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "prefdialog.h"
+
+#include <gtkmm/checkbutton.h>
+#include <gtkmm/separator.h>
+#include <glibmm/i18n.h>
+
+#include "ui/dialog-events.h"
+#include "xml/repr.h"
+
+// Used to get SP_ACTIVE_DESKTOP
+#include "inkscape.h"
+#include "document.h"
+#include "document-undo.h"
+
+#include "extension/effect.h"
+#include "extension/execution-env.h"
+#include "extension/implementation/implementation.h"
+
+#include "parameter.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+
+/** \brief Creates a new preference dialog for extension preferences
+ \param name Name of the Extension whose dialog this is (should already be translated)
+ \param controls The extension specific widgets in the dialog
+
+ This function initializes the dialog with the name of the extension
+ in the title. It adds a few buttons and sets up handlers for
+ them. It also places the passed-in widgets into the dialog.
+*/
+PrefDialog::PrefDialog (Glib::ustring name, Gtk::Widget * controls, Effect * effect) :
+ Gtk::Dialog(name, true),
+ _name(name),
+ _button_ok(nullptr),
+ _button_cancel(nullptr),
+ _button_preview(nullptr),
+ _param_preview(nullptr),
+ _effect(effect),
+ _exEnv(nullptr)
+{
+ this->set_default_size(0,0); // we want the window to be as small as possible instead of clobbering up space
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ if (controls == nullptr) {
+ if (_effect == nullptr) {
+ std::cout << "AH!!! No controls and no effect!!!" << std::endl;
+ return;
+ }
+ controls = _effect->get_imp()->prefs_effect(_effect, SP_ACTIVE_DESKTOP, &_signal_param_change, nullptr);
+ _signal_param_change.connect(sigc::mem_fun(this, &PrefDialog::param_change));
+ }
+
+ hbox->pack_start(*controls, true, true, 0);
+ hbox->show();
+
+ this->get_content_area()->pack_start(*hbox, true, true, 0);
+
+ _button_cancel = add_button(_effect == nullptr ? _("_Cancel") : _("_Close"), Gtk::RESPONSE_CANCEL);
+ _button_ok = add_button(_effect == nullptr ? _("_OK") : _("_Apply"), Gtk::RESPONSE_OK);
+ set_default_response(Gtk::RESPONSE_OK);
+ _button_ok->grab_focus();
+
+ if (_effect != nullptr && !_effect->no_live_preview) {
+ if (_param_preview == nullptr) {
+ XML::Document * doc = sp_repr_read_mem(live_param_xml, strlen(live_param_xml), nullptr);
+ if (doc == nullptr) {
+ std::cout << "Error encountered loading live parameter XML !!!" << std::endl;
+ return;
+ }
+ _param_preview = InxParameter::make(doc->root(), _effect);
+ }
+
+ auto sep = Gtk::manage(new Gtk::Separator());
+ sep->show();
+
+ this->get_content_area()->pack_start(*sep, false, false, InxWidget::GUI_BOX_SPACING);
+
+ hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ hbox->set_border_width(InxWidget::GUI_BOX_MARGIN);
+ _button_preview = _param_preview->get_widget(&_signal_preview);
+ _button_preview->show();
+ hbox->pack_start(*_button_preview, true, true, 0);
+ hbox->show();
+
+ this->get_content_area()->pack_start(*hbox, false, false, 0);
+
+ Gtk::Box *hbox = dynamic_cast<Gtk::Box *>(_button_preview);
+ if (hbox != nullptr) {
+ _checkbox_preview = dynamic_cast<Gtk::CheckButton *>(hbox->get_children().front());
+ }
+
+ preview_toggle();
+ _signal_preview.connect(sigc::mem_fun(this, &PrefDialog::preview_toggle));
+ }
+
+ // Set window modality for effects that don't use live preview
+ if (_effect != nullptr && _effect->no_live_preview) {
+ set_modal(false);
+ }
+
+ return;
+}
+
+PrefDialog::~PrefDialog ( )
+{
+ if (_param_preview != nullptr) {
+ delete _param_preview;
+ _param_preview = nullptr;
+ }
+
+ if (_exEnv != nullptr) {
+ _exEnv->cancel();
+ delete _exEnv;
+ _exEnv = nullptr;
+ _effect->set_execution_env(_exEnv);
+ }
+
+ if (_effect != nullptr) {
+ _effect->set_pref_dialog(nullptr);
+ }
+ return;
+}
+
+void
+PrefDialog::preview_toggle () {
+ SPDocument *document = SP_ACTIVE_DOCUMENT;
+ bool modified = document->isModifiedSinceSave();
+ if(_param_preview->get_bool()) {
+ if (_exEnv == nullptr) {
+ set_modal(true);
+ _exEnv = new ExecutionEnv(_effect, SP_ACTIVE_DESKTOP, nullptr, false, false);
+ _effect->set_execution_env(_exEnv);
+ _exEnv->run();
+ }
+ } else {
+ set_modal(false);
+ if (_exEnv != nullptr) {
+ _exEnv->cancel();
+ _exEnv->undo();
+ _exEnv->reselect();
+ delete _exEnv;
+ _exEnv = nullptr;
+ _effect->set_execution_env(_exEnv);
+ }
+ }
+ document->setModifiedSinceSave(modified);
+}
+
+void
+PrefDialog::param_change () {
+ if (_exEnv != nullptr) {
+ if (!_effect->loaded()) {
+ _effect->set_state(Extension::STATE_LOADED);
+ }
+ _timersig.disconnect();
+ _timersig = Glib::signal_timeout().connect(sigc::mem_fun(this, &PrefDialog::param_timer_expire),
+ 250, /* ms */
+ Glib::PRIORITY_DEFAULT_IDLE);
+ }
+
+ return;
+}
+
+bool
+PrefDialog::param_timer_expire () {
+ if (_exEnv != nullptr) {
+ _exEnv->cancel();
+ _exEnv->undo();
+ _exEnv->reselect();
+ _exEnv->run();
+ }
+
+ return false;
+}
+
+void
+PrefDialog::on_response (int signal) {
+ if (signal == Gtk::RESPONSE_OK) {
+ if (_exEnv == nullptr) {
+ if (_effect != nullptr) {
+ _effect->effect(SP_ACTIVE_DESKTOP);
+ } else {
+ // Shutdown run()
+ return;
+ }
+ } else {
+ if (_exEnv->wait()) {
+ _exEnv->commit();
+ } else {
+ _exEnv->undo();
+ _exEnv->reselect();
+ }
+ delete _exEnv;
+ _exEnv = nullptr;
+ _effect->set_execution_env(_exEnv);
+ }
+ }
+
+ if (_param_preview != nullptr) {
+ _checkbox_preview->set_active(false);
+ }
+
+ if ((signal == Gtk::RESPONSE_CANCEL || signal == Gtk::RESPONSE_DELETE_EVENT) && _effect != nullptr) {
+ delete this;
+ }
+ return;
+}
+
+#include "extension/internal/clear-n_.h"
+
+const char * PrefDialog::live_param_xml = "<param name=\"__live_effect__\" type=\"bool\" gui-text=\"" N_("Live preview") "\" gui-description=\"" N_("Is the effect previewed live on canvas?") "\">false</param>";
+
+}; }; /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/prefdialog.h b/src/extension/prefdialog/prefdialog.h
new file mode 100644
index 0000000..9fe8acd
--- /dev/null
+++ b/src/extension/prefdialog/prefdialog.h
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2005,2007-2008 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_DIALOG_H__
+#define INKSCAPE_EXTENSION_DIALOG_H__
+
+#include <gtkmm/dialog.h>
+#include <glibmm/value.h>
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+class CheckButton;
+}
+
+namespace Inkscape {
+namespace Extension {
+class Effect;
+class ExecutionEnv;
+class InxParameter;
+
+/** \brief A class to represent the preferences for an extension */
+class PrefDialog : public Gtk::Dialog {
+ /** \brief Name of the extension */
+ Glib::ustring _name;
+
+ /** \brief A pointer to the OK button */
+ Gtk::Button *_button_ok;
+ /** \brief A pointer to the CANCEL button */
+ Gtk::Button *_button_cancel;
+
+ /** \brief Button to control live preview */
+ Gtk::Widget *_button_preview;
+ /** \brief Checkbox for the preview */
+ Gtk::CheckButton *_checkbox_preview;
+
+ /** \brief Parameter to control live preview */
+ InxParameter *_param_preview;
+
+ /** \brief XML to define the live effects parameter on the dialog */
+ static const char * live_param_xml;
+
+ /** \brief Signal that the user is changing the live effect state */
+ sigc::signal<void> _signal_preview;
+ /** \brief Signal that one of the parameters change */
+ sigc::signal<void> _signal_param_change;
+
+ /** \brief If this is the preferences for an effect, the effect
+ that we're working with. */
+ Effect *_effect;
+ /** \brief If we're executing in preview mode here is the execution
+ environment for the effect. */
+ ExecutionEnv *_exEnv;
+
+ /** \brief The timer used to make it so that parameters don't respond
+ directly and allows for changes. */
+ sigc::connection _timersig;
+
+ void preview_toggle();
+ void param_change();
+ bool param_timer_expire();
+ void on_response (int signal) override;
+
+public:
+ PrefDialog (Glib::ustring name,
+ Gtk::Widget * controls = nullptr,
+ Effect * effect = nullptr);
+ ~PrefDialog () override;
+};
+
+
+};}; /* namespace Inkscape, Extension */
+
+#endif /* INKSCAPE_EXTENSION_DIALOG_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/widget-box.cpp b/src/extension/prefdialog/widget-box.cpp
new file mode 100644
index 0000000..12c7b9e
--- /dev/null
+++ b/src/extension/prefdialog/widget-box.cpp
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Box widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-box.h"
+
+#include <gtkmm/box.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetBox::WidgetBox(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ // Decide orientation based on tagname (hbox vs. vbox)
+ const char *tagname = xml->name();
+ if (!strncmp(tagname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ tagname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (!strcmp(tagname, "hbox")) {
+ _orientation = HORIZONTAL;
+ } else if (!strcmp(tagname, "vbox")) {
+ _orientation = VERTICAL;
+ } else {
+ g_assert_not_reached();
+ }
+
+ // Read XML tree of box and parse child widgets
+ if (xml) {
+ Inkscape::XML::Node *child_repr = xml->firstChild();
+ while (child_repr) {
+ const char *chname = child_repr->name();
+ if (!strncmp(chname, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ chname += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (chname[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
+ chname++;
+ }
+
+ if (InxWidget::is_valid_widget_name(chname)) {
+ InxWidget *widget = InxWidget::make(child_repr, _extension);
+ if (widget) {
+ _children.push_back(widget);
+ }
+ } else if (child_repr->type() == XML::NodeType::ELEMENT_NODE) {
+ g_warning("Invalid child element ('%s') in box widget in extension '%s'.",
+ chname, _extension->get_id());
+ } else if (child_repr->type() != XML::NodeType::COMMENT_NODE){
+ g_warning("Invalid child element found in box widget in extension '%s'.", _extension->get_id());
+ }
+
+ child_repr = child_repr->next();
+ }
+ }
+}
+
+Gtk::Widget *WidgetBox::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Orientation orientation;
+ if (_orientation == HORIZONTAL) {
+ orientation = Gtk::ORIENTATION_HORIZONTAL;
+ } else {
+ orientation = Gtk::ORIENTATION_VERTICAL;
+ }
+
+ Gtk::Box *box = Gtk::manage(new Gtk::Box(orientation));
+ // box->set_border_width(GUI_BOX_MARGIN); // leave at zero for now, so box is purely for layouting (not grouping)
+ // revisit this later, possibly implementing GtkFrame or similar
+ box->set_spacing(GUI_BOX_SPACING);
+
+ if (_orientation == HORIZONTAL) {
+ box->set_vexpand(false);
+ } else {
+ box->set_hexpand(false);
+ }
+
+ // add child widgets onto page (if any)
+ for (auto child : _children) {
+ Gtk::Widget *child_widget = child->get_widget(changeSignal);
+ if (child_widget) {
+ int indent = child->get_indent();
+ child_widget->set_margin_start(indent * GUI_INDENTATION);
+ box->pack_start(*child_widget, false, true, 0); // fill=true does not have an effect here, but allows the
+ // child to choose to expand by setting hexpand/vexpand
+
+ const char *tooltip = child->get_tooltip();
+ if (tooltip) {
+ child_widget->set_tooltip_text(tooltip);
+ }
+ }
+ }
+
+ box->show();
+
+ return dynamic_cast<Gtk::Widget *>(box);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-box.h b/src/extension/prefdialog/widget-box.h
new file mode 100644
index 0000000..6fd7b90
--- /dev/null
+++ b/src/extension/prefdialog/widget-box.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Box widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_BOX_H
+#define SEEN_INK_EXTENSION_WIDGET_BOX_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A box widget */
+class WidgetBox : public InxWidget {
+public:
+ WidgetBox(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+private:
+ enum Orientation {
+ HORIZONTAL, VERTICAL
+ };
+
+ /** Layout orientation of the box (default is vertical) **/
+ Orientation _orientation = VERTICAL;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_BOX_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/widget-image.cpp b/src/extension/prefdialog/widget-image.cpp
new file mode 100644
index 0000000..4f5e11d
--- /dev/null
+++ b/src/extension/prefdialog/widget-image.cpp
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Image widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-image.h"
+
+#include <glibmm/fileutils.h>
+#include <glibmm/miscutils.h>
+#include <gtkmm/image.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetImage::WidgetImage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ std::string image_path;
+
+ // get path to image
+ const char *content = nullptr;
+ if (xml->firstChild()) {
+ content = xml->firstChild()->content();
+ }
+ if (content) {
+ image_path = content;
+ } else {
+ g_warning("Missing path for image widget in extension '%s'.", _extension->get_id());
+ return;
+ }
+
+ // make sure path is absolute (relative paths are relative to .inx file's location)
+ if (!Glib::path_is_absolute(image_path)) {
+ image_path = Glib::build_filename(_extension->get_base_directory(), image_path);
+ }
+
+ // check if image exists
+ if (Glib::file_test(image_path, Glib::FILE_TEST_IS_REGULAR)) {
+ _image_path = image_path;
+ } else {
+ g_warning("Image file ('%s') not found for image widget in extension '%s'.",
+ image_path.c_str(), _extension->get_id());
+ }
+
+ // parse width/height attributes
+ const char *width = xml->attribute("width");
+ const char *height = xml->attribute("height");
+ if (width && height) {
+ _width = strtoul(width, nullptr, 0);
+ _height = strtoul(height, nullptr, 0);
+ }
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetImage::get_widget(sigc::signal<void> * /*changeSignal*/)
+{
+ if (_hidden || _image_path.empty()) {
+ return nullptr;
+ }
+
+ Gtk::Image *image = Gtk::manage(new Gtk::Image(_image_path));
+
+ // resize if requested
+ if (_width && _height) {
+ Glib::RefPtr<Gdk::Pixbuf> pixbuf = image->get_pixbuf();
+ pixbuf = pixbuf->scale_simple(_width, _height, Gdk::INTERP_BILINEAR);
+ image->set(pixbuf);
+ }
+
+ image->show();
+
+ return dynamic_cast<Gtk::Widget *>(image);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-image.h b/src/extension/prefdialog/widget-image.h
new file mode 100644
index 0000000..95dd4d5
--- /dev/null
+++ b/src/extension/prefdialog/widget-image.h
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Image widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_IMAGE_H
+#define SEEN_INK_EXTENSION_WIDGET_IMAGE_H
+
+#include "widget.h"
+
+#include <string>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A label widget */
+class WidgetImage : public InxWidget {
+public:
+ WidgetImage(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+private:
+ /** \brief Path to image file (relative paths are relative to the .inx file location). */
+ std::string _image_path;
+
+ /** desired width of image when rendered on screen (in px) */
+ unsigned int _width = 0;
+ /** desired height of image when rendered on screen (in px) */
+ unsigned int _height = 0;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_IMAGE_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/widget-label.cpp b/src/extension/prefdialog/widget-label.cpp
new file mode 100644
index 0000000..56ba0c1
--- /dev/null
+++ b/src/extension/prefdialog/widget-label.cpp
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Description widget for extensions
+ *//*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2005-2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-label.h"
+
+#include <gtkmm/box.h>
+#include <gtkmm/label.h>
+#include <glibmm/markup.h>
+#include <glibmm/regex.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetLabel::WidgetLabel(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ // construct the text content by concatenating all (non-empty) text nodes,
+ // removing all other nodes (e.g. comment nodes) and replacing <extension:br> elements with "<br/>"
+ Inkscape::XML::Node * cur_child = xml->firstChild();
+ while (cur_child != nullptr) {
+ if (cur_child->type() == XML::NodeType::TEXT_NODE && cur_child->content() != nullptr) {
+ _value += cur_child->content();
+ } else if (cur_child->type() == XML::NodeType::ELEMENT_NODE && !g_strcmp0(cur_child->name(), "extension:br")) {
+ _value += "<br/>";
+ }
+ cur_child = cur_child->next();
+ }
+
+ // do replacements in the source string to account for the attribute xml:space="preserve"
+ // (those should match replacements potentially performed by xgettext to allow for proper translation)
+ if (g_strcmp0(xml->attribute("xml:space"), "preserve") == 0) {
+ // xgettext copies the source string verbatim in this case, so no changes needed
+ } else {
+ // remove all whitespace from start/end of string and replace intermediate whitespace with a single space
+ _value = Glib::Regex::create("^\\s+|\\s+$")->replace_literal(_value, 0, "", (Glib::RegexMatchFlags)0);
+ _value = Glib::Regex::create("\\s+")->replace_literal(_value, 0, " ", (Glib::RegexMatchFlags)0);
+ }
+
+ // translate value
+ if (!_value.empty()) {
+ if (_translatable != NO) { // translate unless explicitly marked untranslatable
+ _value = get_translation(_value.c_str());
+ }
+ }
+
+ // finally replace all remaining <br/> with a real newline character
+ _value = Glib::Regex::create("<br/>")->replace_literal(_value, 0, "\n", (Glib::RegexMatchFlags)0);
+
+ // parse appearance
+ if (_appearance) {
+ if (!strcmp(_appearance, "header")) {
+ _mode = HEADER;
+ } else if (!strcmp(_appearance, "url")) {
+ _mode = URL;
+ } else {
+ g_warning("Invalid value ('%s') for appearance of label widget in extension '%s'",
+ _appearance, _extension->get_id());
+ }
+ }
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetLabel::get_widget(sigc::signal<void> * /*changeSignal*/)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Glib::ustring newtext = _value;
+
+ Gtk::Label *label = Gtk::manage(new Gtk::Label());
+ if (_mode == HEADER) {
+ label->set_markup(Glib::ustring("<b>") + Glib::Markup::escape_text(newtext) + Glib::ustring("</b>"));
+ label->set_margin_top(5);
+ label->set_margin_bottom(5);
+ } else if (_mode == URL) {
+ Glib::ustring escaped_url = Glib::Markup::escape_text(newtext);
+ label->set_markup(Glib::ustring::compose("<a href='%1'>%1</a>", escaped_url));
+ } else {
+ label->set_text(newtext);
+ }
+ label->set_line_wrap();
+ label->set_xalign(0);
+
+ // TODO: Ugly "fix" for gtk3 width/height calculation of labels.
+ // - If not applying any limits long labels will make the window grow horizontally until it uses up
+ // most of the available space (i.e. most of the screen area) which is ridiculously wide.
+ // - By using "set_default_size(0,0)" in prefidalog.cpp we tell the window to shrink as much as possible,
+ // however this can result in a much too narrow dialog instead and a lot of unnecessary wrapping.
+ // - Here we set a lower limit of GUI_MAX_LINE_LENGTH characters per line that long texts will always use.
+ // This means texts can not shrink anymore (they can still grow, though) and it's also necessary
+ // to prevent https://bugzilla.gnome.org/show_bug.cgi?id=773572
+ int len = newtext.length();
+ label->set_width_chars(len > GUI_MAX_LINE_LENGTH ? GUI_MAX_LINE_LENGTH : len);
+
+ label->show();
+
+ Gtk::Box *hbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
+ hbox->pack_start(*label, true, true);
+ hbox->show();
+
+ return hbox;
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-label.h b/src/extension/prefdialog/widget-label.h
new file mode 100644
index 0000000..1c16550
--- /dev/null
+++ b/src/extension/prefdialog/widget-label.h
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Description widget for extensions
+ *//*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl> *
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2005-2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_LABEL_H
+#define SEEN_INK_EXTENSION_WIDGET_LABEL_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A label widget */
+class WidgetLabel : public InxWidget {
+public:
+ enum AppearanceMode {
+ DEFAULT, HEADER, URL
+ };
+
+ WidgetLabel(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+private:
+ /** \brief Internal value. */
+ Glib::ustring _value;
+
+ /** appearance mode **/
+ AppearanceMode _mode = DEFAULT;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_LABEL_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/widget-separator.cpp b/src/extension/prefdialog/widget-separator.cpp
new file mode 100644
index 0000000..d78894b
--- /dev/null
+++ b/src/extension/prefdialog/widget-separator.cpp
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Separator widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-separator.h"
+
+#include <gtkmm/separator.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetSeparator::WidgetSeparator(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetSeparator::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Separator *separator = Gtk::manage(new Gtk::Separator());
+ separator->show();
+
+ return dynamic_cast<Gtk::Widget *>(separator);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-separator.h b/src/extension/prefdialog/widget-separator.h
new file mode 100644
index 0000000..9533aba
--- /dev/null
+++ b/src/extension/prefdialog/widget-separator.h
@@ -0,0 +1,53 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Separator widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_SEPARATOR_H
+#define SEEN_INK_EXTENSION_WIDGET_SEPARATOR_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A separator widget */
+class WidgetSeparator : public InxWidget {
+public:
+ WidgetSeparator(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_SEPARATOR_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/widget-spacer.cpp b/src/extension/prefdialog/widget-spacer.cpp
new file mode 100644
index 0000000..8ffa6ac
--- /dev/null
+++ b/src/extension/prefdialog/widget-spacer.cpp
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Spacer widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "widget-spacer.h"
+
+#include <gtkmm/box.h>
+
+#include "xml/node.h"
+#include "extension/extension.h"
+
+namespace Inkscape {
+namespace Extension {
+
+
+WidgetSpacer::WidgetSpacer(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext)
+ : InxWidget(xml, ext)
+{
+ // get size
+ const char *size = xml->attribute("size");
+ if (size) {
+ _size = strtol(size, nullptr, 0);
+ if (_size == 0) {
+ if (!strcmp(size, "expand")) {
+ _expand = true;
+ } else {
+ g_warning("Invalid value ('%s') for size spacer in extension '%s'", size, _extension->get_id());
+ }
+ }
+ }
+}
+
+/** \brief Create a label for the description */
+Gtk::Widget *WidgetSpacer::get_widget(sigc::signal<void> *changeSignal)
+{
+ if (_hidden) {
+ return nullptr;
+ }
+
+ Gtk::Box *spacer = Gtk::manage(new Gtk::Box());
+ spacer->set_border_width(_size/2);
+
+ if (_expand) {
+ spacer->set_hexpand();
+ spacer->set_vexpand();
+ }
+
+ spacer->show();
+
+ return dynamic_cast<Gtk::Widget *>(spacer);
+}
+
+} /* namespace Extension */
+} /* namespace Inkscape */
diff --git a/src/extension/prefdialog/widget-spacer.h b/src/extension/prefdialog/widget-spacer.h
new file mode 100644
index 0000000..467b5f9
--- /dev/null
+++ b/src/extension/prefdialog/widget-spacer.h
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Spacer widget for extensions
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_SPACER_H
+#define SEEN_INK_EXTENSION_WIDGET_SPACER_H
+
+#include "widget.h"
+
+#include <glibmm/ustring.h>
+
+namespace Gtk {
+ class Widget;
+}
+
+namespace Inkscape {
+namespace Xml {
+ class Node;
+}
+
+namespace Extension {
+
+/** \brief A separator widget */
+class WidgetSpacer : public InxWidget {
+public:
+ WidgetSpacer(Inkscape::XML::Node *xml, Inkscape::Extension::Extension *ext);
+
+ Gtk::Widget *get_widget(sigc::signal<void> *changeSignal) override;
+
+private:
+ /** size of the spacer in px */
+ int _size = GUI_BOX_MARGIN;
+
+ /** should the spacer be flexible and expand? */
+ bool _expand = false;
+};
+
+} /* namespace Extension */
+} /* namespace Inkscape */
+
+#endif /* SEEN_INK_EXTENSION_WIDGET_SPACER_H */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/prefdialog/widget.cpp b/src/extension/prefdialog/widget.cpp
new file mode 100644
index 0000000..18639c5
--- /dev/null
+++ b/src/extension/prefdialog/widget.cpp
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Parameters for extensions.
+ *//*
+ * Author:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "parameter.h"
+#include "widget.h"
+#include "widget-box.h"
+#include "widget-image.h"
+#include "widget-label.h"
+#include "widget-separator.h"
+#include "widget-spacer.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include <sigc++/sigc++.h>
+
+#include "extension/extension.h"
+
+#include "xml/node.h"
+
+
+namespace Inkscape {
+namespace Extension {
+
+InxWidget *InxWidget::make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext)
+{
+ InxWidget *widget = nullptr;
+
+ const char *name = in_repr->name();
+ if (!strncmp(name, INKSCAPE_EXTENSION_NS_NC, strlen(INKSCAPE_EXTENSION_NS_NC))) {
+ name += strlen(INKSCAPE_EXTENSION_NS);
+ }
+ if (name[0] == '_') { // allow leading underscore in tag names for backwards-compatibility
+ name++;
+ }
+
+ // decide on widget type based on tag name
+ // keep in sync with list of names supported in InxWidget::is_valid_widget_name() below
+ if (!name) {
+ // we can't create a widget without name
+ g_warning("InxWidget without name in extension '%s'.", in_ext->get_id());
+ } else if (!strcmp(name, "hbox") || !strcmp(name, "vbox")) {
+ widget = new WidgetBox(in_repr, in_ext);
+ } else if (!strcmp(name, "image")) {
+ widget = new WidgetImage(in_repr, in_ext);
+ } else if (!strcmp(name, "label")) {
+ widget = new WidgetLabel(in_repr, in_ext);
+ } else if (!strcmp(name, "separator")) {
+ widget = new WidgetSeparator(in_repr, in_ext);
+ } else if (!strcmp(name, "spacer")) {
+ widget = new WidgetSpacer(in_repr, in_ext);
+ } else if (!strcmp(name, "param")) {
+ widget = InxParameter::make(in_repr, in_ext);
+ } else {
+ g_warning("Unknown widget name ('%s') in extension '%s'", name, in_ext->get_id());
+ }
+
+ // Note: widget could equal nullptr
+ return widget;
+}
+
+bool InxWidget::is_valid_widget_name(const char *name)
+{
+ // keep in sync with names supported in InxWidget::make() above
+ static const std::vector<std::string> valid_names =
+ {"hbox", "vbox", "image", "label", "separator", "spacer", "param"};
+
+ if (std::find(valid_names.begin(), valid_names.end(), name) != valid_names.end()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+InxWidget::InxWidget(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *ext)
+ : _extension(ext)
+{
+ // translatable (optional)
+ const char *translatable = in_repr->attribute("translatable");
+ if (translatable) {
+ if (!strcmp(translatable, "yes")) {
+ _translatable = YES;
+ } else if (!strcmp(translatable, "no")) {
+ _translatable = NO;
+ } else {
+ g_warning("Invalid value ('%s') for translatable attribute of widget '%s' in extension '%s'",
+ translatable, in_repr->name(), _extension->get_id());
+ }
+ }
+
+ // context (optional)
+ const char *context = in_repr->attribute("context");
+ if (!context) {
+ context = in_repr->attribute("msgctxt"); // backwards-compatibility with previous name
+ }
+ if (context) {
+ _context = g_strdup(context);
+ }
+
+ // gui-hidden (optional)
+ const char *gui_hidden = in_repr->attribute("gui-hidden");
+ if (gui_hidden != nullptr) {
+ if (strcmp(gui_hidden, "true") == 0) {
+ _hidden = true;
+ }
+ }
+
+ // indent (optional)
+ const char *indent = in_repr->attribute("indent");
+ if (indent != nullptr) {
+ _indent = strtol(indent, nullptr, 0);
+ }
+
+ // appearance (optional, does not apply to all parameters)
+ const char *appearance = in_repr->attribute("appearance");
+ if (appearance) {
+ _appearance = g_strdup(appearance);
+ }
+}
+
+InxWidget::~InxWidget()
+{
+ for (auto child : _children) {
+ delete child;
+ }
+
+ g_free(_context);
+ _context = nullptr;
+
+ g_free(_appearance);
+ _appearance = nullptr;
+}
+
+Gtk::Widget *
+InxWidget::get_widget(sigc::signal<void> * /*changeSignal*/)
+{
+ // if we end up here we're missing a definition of ::get_widget() in one of the subclasses
+ g_critical("InxWidget::get_widget called from widget of type '%s' in extension '%s'",
+ typeid(this).name(), _extension->get_id());
+ g_assert_not_reached();
+ return nullptr;
+}
+
+const char *InxWidget::get_translation(const char* msgid) {
+ return _extension->get_translation(msgid, _context);
+}
+
+void InxWidget::get_widgets(std::vector<InxWidget *> &list)
+{
+ list.push_back(this);
+ for (auto child : _children) {
+ child->get_widgets(list);
+ }
+}
+
+} // namespace Extension
+} // namespace Inkscape
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/prefdialog/widget.h b/src/extension/prefdialog/widget.h
new file mode 100644
index 0000000..843655c
--- /dev/null
+++ b/src/extension/prefdialog/widget.h
@@ -0,0 +1,149 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/** @file
+ * Base class for extension widgets.
+ *//*
+ * Authors:
+ * Patrick Storz <eduard.braun2@gmx.de>
+ *
+ * Copyright (C) 2019 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef SEEN_INK_EXTENSION_WIDGET_H
+#define SEEN_INK_EXTENSION_WIDGET_H
+
+#include <string>
+#include <vector>
+
+#include <sigc++/sigc++.h>
+
+namespace Gtk {
+class Widget;
+}
+
+namespace Inkscape {
+namespace XML {
+class Node;
+}
+
+namespace Extension {
+
+class Extension;
+
+
+/**
+ * Base class to represent all widgets of an extension (including parameters)
+ */
+class InxWidget {
+public:
+ InxWidget(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext);
+
+ virtual ~InxWidget();
+
+ /**
+ * Creates a new extension widget for usage in a prefdialog.
+ *
+ * The type of widget created is parsed from the XML representation passed in,
+ * and the suitable subclass constructor is called.
+ *
+ * For specialized widget types (like parameters) we defer to the subclass function of the same name.
+ *
+ * @param in_repr The XML representation describing the widget.
+ * @param in_ext The extension the widget belongs to.
+ * @return a pointer to a new Widget if applicable, null otherwise..
+ */
+ static InxWidget *make(Inkscape::XML::Node *in_repr, Inkscape::Extension::Extension *in_ext);
+
+ /** Checks if name is a valid widget name, i.e. a widget can be constructed from it using make() */
+ static bool is_valid_widget_name(const char *name);
+
+ /** Return the instance's GTK::Widget representation for usage in a GUI
+ *
+ * @param changeSignal Can be used to subscribe to parameter changes.
+ * Will be emitted whenever a parameter value changes.
+ *
+ * @teturn A Gtk::Widget for the \a InxWidget. \c nullptr if the widget is hidden.
+ */
+ virtual Gtk::Widget *get_widget(sigc::signal<void> *changeSignal);
+
+ virtual const char *get_tooltip() const { return nullptr; } // tool-tips are exclusive to InxParameters for now
+
+ /** Indicates if the widget is hidden or not */
+ bool get_hidden() const { return _hidden; }
+
+ /** Indentation level of the widget */
+ int get_indent() const { return _indent; }
+
+
+ /**
+ * Recursively construct a list containing the current widget and all of it's child widgets (if it has any)
+ *
+ * @param list Reference to a vector of pointers to \a InxWidget that will be appended with the new \a InxWidgets
+ */
+ virtual void get_widgets(std::vector<InxWidget *> &list);
+
+
+ /** Recommended margin of boxes containing multiple widgets (in px) */
+ const static int GUI_BOX_MARGIN = 10;
+ /** Recommended spacing between multiple widgets packed into a box (in px) */
+ const static int GUI_BOX_SPACING = 4;
+ /** Recommended indentation width of widgets(in px) */
+ const static int GUI_INDENTATION = 12;
+ /** Recommended maximum line length for wrapping textual wdgets (in chars) */
+ const static int GUI_MAX_LINE_LENGTH = 60;
+
+protected:
+ enum Translatable {
+ UNSET, YES, NO
+ };
+
+ /** Which extension is this Widget attached to. */
+ Inkscape::Extension::Extension *_extension = nullptr;
+
+ /** Child widgets of this widget (might be empty if there are none) */
+ std::vector<InxWidget *> _children;
+
+ /** Whether the widget is visible. */
+ bool _hidden = false;
+
+ /** Indentation level of the widget. */
+ int _indent = 0;
+
+ /** Appearance of the widget (not used by all widgets). */
+ char *_appearance = nullptr;
+
+ /** Is widget translatable? */
+ Translatable _translatable = UNSET;
+
+ /** context for translation of translatable strings. */
+ char *_context = nullptr;
+
+
+ /* **** member functions **** */
+
+ /** gets the gettext translation for msgid
+ *
+ * Handles translation domain of the extension and message context of the widget internally
+ *
+ * @param msgid String to translate
+ * @return Translated string
+ */
+ const char *get_translation(const char* msgid);
+};
+
+} // namespace Extension
+} // namespace Inkscape
+
+#endif // SEEN_INK_EXTENSION_WIDGET_H
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
diff --git a/src/extension/print.cpp b/src/extension/print.cpp
new file mode 100644
index 0000000..458b6f2
--- /dev/null
+++ b/src/extension/print.cpp
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "implementation/implementation.h"
+#include "print.h"
+
+/* Inkscape::Extension::Print */
+
+namespace Inkscape {
+namespace Extension {
+
+Print::Print (Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory)
+ : Extension(in_repr, in_imp, base_directory)
+ , base(nullptr)
+ , drawing(nullptr)
+ , root(nullptr)
+ , dkey(0)
+{
+}
+
+Print::~Print ()
+= default;
+
+bool
+Print::check ()
+{
+ return Extension::check();
+}
+
+unsigned int
+Print::setup ()
+{
+ return imp->setup(this);
+}
+
+unsigned int
+Print::set_preview ()
+{
+ return imp->set_preview(this);
+}
+
+unsigned int
+Print::begin (SPDocument *doc)
+{
+ return imp->begin(this, doc);
+}
+
+unsigned int
+Print::finish ()
+{
+ return imp->finish(this);
+}
+
+unsigned int
+Print::bind (const Geom::Affine &transform, float opacity)
+{
+ return imp->bind (this, transform, opacity);
+}
+
+unsigned int
+Print::release ()
+{
+ return imp->release(this);
+}
+
+unsigned int
+Print::fill (Geom::PathVector const &pathv, Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox)
+{
+ return imp->fill (this, pathv, ctm, style, pbox, dbox, bbox);
+}
+
+unsigned int
+Print::stroke (Geom::PathVector const &pathv, Geom::Affine const &ctm, SPStyle const *style,
+ Geom::OptRect const &pbox, Geom::OptRect const &dbox, Geom::OptRect const &bbox)
+{
+ return imp->stroke (this, pathv, ctm, style, pbox, dbox, bbox);
+}
+
+unsigned int
+Print::image (unsigned char *px, unsigned int w, unsigned int h, unsigned int rs,
+ const Geom::Affine &transform, const SPStyle *style)
+{
+ return imp->image (this, px, w, h, rs, transform, style);
+}
+
+unsigned int
+Print::text (char const *text, Geom::Point const &p, SPStyle const *style)
+{
+ return imp->text (this, text, p, style);
+}
+
+bool
+Print::textToPath ()
+{
+ return imp->textToPath(this);
+}
+
+//whether embed font in print output (EPS especially)
+bool
+Print::fontEmbedded ()
+{
+ return imp->fontEmbedded(this);
+}
+
+} } /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/print.h b/src/extension/print.h
new file mode 100644
index 0000000..c4bc267
--- /dev/null
+++ b/src/extension/print.h
@@ -0,0 +1,94 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_PRINT_H__
+#define INKSCAPE_EXTENSION_PRINT_H__
+
+#include <2geom/affine.h>
+#include <2geom/generic-rect.h>
+#include <2geom/pathvector.h>
+#include <2geom/point.h>
+#include "extension.h"
+
+class SPItem;
+class SPStyle;
+
+namespace Inkscape {
+
+class Drawing;
+class DrawingItem;
+
+namespace Extension {
+
+class Print : public Extension {
+
+public: /* TODO: These are public for the short term, but this should be fixed */
+ SPItem *base;
+ Inkscape::Drawing *drawing;
+ Inkscape::DrawingItem *root;
+ unsigned int dkey;
+
+public:
+ Print(Inkscape::XML::Node *in_repr, Implementation::Implementation *in_imp, std::string *base_directory);
+ ~Print() override;
+
+ bool check() override;
+
+ /* FALSE means user hit cancel */
+ unsigned int setup ();
+ unsigned int set_preview ();
+
+ unsigned int begin (SPDocument *doc);
+ unsigned int finish ();
+
+ /* Rendering methods */
+ unsigned int bind (Geom::Affine const &transform,
+ float opacity);
+ unsigned int release ();
+ unsigned int comment (const char * comment);
+ unsigned int fill (Geom::PathVector const &pathv,
+ Geom::Affine const &ctm,
+ SPStyle const *style,
+ Geom::OptRect const &pbox,
+ Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox);
+ unsigned int stroke (Geom::PathVector const &pathv,
+ Geom::Affine const &transform,
+ SPStyle const *style,
+ Geom::OptRect const &pbox,
+ Geom::OptRect const &dbox,
+ Geom::OptRect const &bbox);
+ unsigned int image (unsigned char *px,
+ unsigned int w,
+ unsigned int h,
+ unsigned int rs,
+ Geom::Affine const &transform,
+ SPStyle const *style);
+ unsigned int text (char const *text,
+ Geom::Point const &p,
+ SPStyle const *style);
+ bool textToPath ();
+ bool fontEmbedded ();
+};
+
+} } /* namespace Inkscape, Extension */
+#endif /* INKSCAPE_EXTENSION_PRINT_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/system.cpp b/src/extension/system.cpp
new file mode 100644
index 0000000..f4ba2dc
--- /dev/null
+++ b/src/extension/system.cpp
@@ -0,0 +1,710 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is file is kind of the junk file. Basically everything that
+ * didn't fit in one of the other well defined areas, well, it's now
+ * here. Which is good in someways, but this file really needs some
+ * definition. Hopefully that will come ASAP.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ * Johan Engelen <johan@shouraizou.nl>
+ * Jon A. Cruz <jon@joncruz.org>
+ * Abhishek Sharma
+ *
+ * Copyright (C) 2006-2007 Johan Engelen
+ * Copyright (C) 2002-2004 Ted Gould
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include "ui/interface.h"
+
+#include "system.h"
+#include "preferences.h"
+#include "extension.h"
+#include "db.h"
+#include "input.h"
+#include "output.h"
+#include "effect.h"
+#include "patheffect.h"
+#include "print.h"
+#include "implementation/script.h"
+#include "implementation/xslt.h"
+#include "xml/rebase-hrefs.h"
+#include "io/sys.h"
+#include "inkscape.h"
+#include "document-undo.h"
+#include "loader.h"
+
+#include <glibmm/miscutils.h>
+
+namespace Inkscape {
+namespace Extension {
+
+static void open_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
+static void save_internal(Inkscape::Extension::Extension *in_plug, gpointer in_data);
+
+/**
+ * \return A new document created from the filename passed in
+ * \brief This is a generic function to use the open function of
+ * a module (including Autodetect)
+ * \param key Identifier of which module to use
+ * \param filename The file that should be opened
+ *
+ * First things first, are we looking at an autodetection? Well if that's the case then the module
+ * needs to be found, and that is done with a database lookup through the module DB. The foreach
+ * function is called, with the parameter being a gpointer array. It contains both the filename
+ * (to find its extension) and where to write the module when it is found.
+ *
+ * If there is no autodetection, then the module database is queried with the key given.
+ *
+ * If everything is cool at this point, the module is loaded, and there is possibility for
+ * preferences. If there is a function, then it is executed to get the dialog to be displayed.
+ * After it is finished the function continues.
+ *
+ * Lastly, the open function is called in the module itself.
+ */
+SPDocument *open(Extension *key, gchar const *filename)
+{
+ Input *imod = nullptr;
+
+ if (key == nullptr) {
+ gpointer parray[2];
+ parray[0] = (gpointer)filename;
+ parray[1] = (gpointer)&imod;
+ db.foreach(open_internal, (gpointer)&parray);
+ } else {
+ imod = dynamic_cast<Input *>(key);
+ }
+
+ bool last_chance_svg = false;
+ if (key == nullptr && imod == nullptr) {
+ last_chance_svg = true;
+ imod = dynamic_cast<Input *>(db.get(SP_MODULE_KEY_INPUT_SVG));
+ }
+
+ if (imod == nullptr) {
+ throw Input::no_extension_found();
+ }
+
+ // Hide pixbuf extensions depending on user preferences.
+ //g_warning("Extension: %s", imod->get_id());
+
+ bool show = true;
+ if (strlen(imod->get_id()) > 21) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ bool ask = prefs->getBool("/dialogs/import/ask");
+ bool ask_svg = prefs->getBool("/dialogs/import/ask_svg");
+ Glib::ustring id = Glib::ustring(imod->get_id(), 22);
+ if (id.compare("org.inkscape.input.svg") == 0) {
+ if (ask_svg && prefs->getBool("/options/onimport", false)) {
+ show = true;
+ imod->set_gui(true);
+ } else {
+ show = false;
+ imod->set_gui(false);
+ }
+ } else if(strlen(imod->get_id()) > 27) {
+ id = Glib::ustring(imod->get_id(), 28);
+ if (!ask && id.compare( "org.inkscape.input.gdkpixbuf") == 0) {
+ show = false;
+ imod->set_gui(false);
+ }
+ }
+ }
+ imod->set_state(Extension::STATE_LOADED);
+
+ if (!imod->loaded()) {
+ throw Input::open_failed();
+ }
+
+ if (!imod->prefs(filename)) {
+ throw Input::open_cancelled();
+ }
+
+ SPDocument *doc = imod->open(filename);
+
+ if (!doc) {
+ if (last_chance_svg) {
+ if ( INKSCAPE.use_gui() ) {
+ sp_ui_error_dialog(_("Could not detect file format. Tried to open it as an SVG anyway but this also failed."));
+ } else {
+ g_warning("%s", _("Could not detect file format. Tried to open it as an SVG anyway but this also failed."));
+ }
+ }
+ throw Input::open_failed();
+ }
+ // If last_chance_svg is true here, it means we successfully opened a file as an svg
+ // and there's no need to warn the user about it, just do it.
+
+ doc->setDocumentFilename(filename);
+ if (!show) {
+ imod->set_gui(true);
+ }
+
+ return doc;
+}
+
+/**
+ * \return none
+ * \brief This is the function that searches each module to see
+ * if it matches the filename for autodetection.
+ * \param in_plug The module to be tested
+ * \param in_data An array of pointers containing the filename, and
+ * the place to put a successfully found module.
+ *
+ * Basically this function only looks at input modules as it is part of the open function. If the
+ * module is an input module, it then starts to take it apart, and the data that is passed in.
+ * Because the data being passed in is in such a weird format, there are a few casts to make it
+ * easier to use. While it looks like a lot of local variables, they'll all get removed by the
+ * compiler.
+ *
+ * First thing that is checked is if the filename is shorter than the extension itself. There is
+ * no way for a match in that case. If it's long enough then there is a string compare of the end
+ * of the filename (for the length of the extension), and the extension itself. If this passes
+ * then the pointer passed in is set to the current module.
+ */
+static void
+open_internal(Extension *in_plug, gpointer in_data)
+{
+ auto imod = dynamic_cast<Input *>(in_plug);
+ if (imod && !imod->deactivated()) {
+ gpointer *parray = (gpointer *)in_data;
+ gchar const *filename = (gchar const *)parray[0];
+ Input **pimod = (Input **)parray[1];
+
+ // skip all the rest if we already found a function to open it
+ // since they're ordered by preference now.
+ if (!*pimod) {
+ gchar const *ext = imod->get_extension();
+
+ gchar *filenamelower = g_utf8_strdown(filename, -1);
+ gchar *extensionlower = g_utf8_strdown(ext, -1);
+
+ if (g_str_has_suffix(filenamelower, extensionlower)) {
+ *pimod = imod;
+ }
+
+ g_free(filenamelower);
+ g_free(extensionlower);
+ }
+ }
+
+ return;
+}
+
+/**
+ * \return None
+ * \brief This is a generic function to use the save function of
+ * a module (including Autodetect)
+ * \param key Identifier of which module to use
+ * \param doc The document to be saved
+ * \param filename The file that the document should be saved to
+ * \param official (optional) whether to set :output_module and :modified in the
+ * document; is true for normal save, false for temporary saves
+ *
+ * First things first, are we looking at an autodetection? Well if that's the case then the module
+ * needs to be found, and that is done with a database lookup through the module DB. The foreach
+ * function is called, with the parameter being a gpointer array. It contains both the filename
+ * (to find its extension) and where to write the module when it is found.
+ *
+ * If there is no autodetection the module database is queried with the key given.
+ *
+ * If everything is cool at this point, the module is loaded, and there is possibility for
+ * preferences. If there is a function, then it is executed to get the dialog to be displayed.
+ * After it is finished the function continues.
+ *
+ * Lastly, the save function is called in the module itself.
+ */
+void
+save(Extension *key, SPDocument *doc, gchar const *filename, bool check_overwrite, bool official,
+ Inkscape::Extension::FileSaveMethod save_method)
+{
+ Output *omod;
+ if (key == nullptr) {
+ gpointer parray[2];
+ parray[0] = (gpointer)filename;
+ parray[1] = (gpointer)&omod;
+ omod = nullptr;
+ db.foreach(save_internal, (gpointer)&parray);
+
+ /* This is a nasty hack, but it is required to ensure that
+ autodetect will always save with the Inkscape extensions
+ if they are available. */
+ if (omod != nullptr && !strcmp(omod->get_id(), SP_MODULE_KEY_OUTPUT_SVG)) {
+ omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE));
+ }
+ /* If autodetect fails, save as Inkscape SVG */
+ if (omod == nullptr) {
+ // omod = dynamic_cast<Output *>(db.get(SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)); use exception and let user choose
+ }
+ } else {
+ omod = dynamic_cast<Output *>(key);
+ }
+
+ if (!dynamic_cast<Output *>(omod)) {
+ g_warning("Unable to find output module to handle file: %s\n", filename);
+ throw Output::no_extension_found();
+ }
+
+ omod->set_state(Extension::STATE_LOADED);
+ if (!omod->loaded()) {
+ throw Output::save_failed();
+ }
+
+ if (!omod->prefs()) {
+ throw Output::save_cancelled();
+ }
+
+ gchar *fileName = g_strdup(filename);
+
+ if (check_overwrite && !sp_ui_overwrite_file(fileName)) {
+ g_free(fileName);
+ throw Output::no_overwrite();
+ }
+
+ // test if the file exists and is writable
+ // the test only checks the file attributes and might pass where ACL does not allow writes
+ if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_is_writable(filename)) {
+ g_free(fileName);
+ throw Output::file_read_only();
+ }
+
+ Inkscape::XML::Node *repr = doc->getReprRoot();
+
+
+ // remember attributes in case this is an unofficial save and/or overwrite fails
+ gchar *saved_filename = g_strdup(doc->getDocumentFilename());
+ gchar *saved_output_extension = nullptr;
+ gchar *saved_dataloss = nullptr;
+ bool saved_modified = doc->isModifiedSinceSave();
+ saved_output_extension = g_strdup(get_file_save_extension(save_method).c_str());
+ saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss"));
+ if (official) {
+ // The document is changing name/uri.
+ doc->changeFilenameAndHrefs(fileName);
+ }
+
+ // Update attributes:
+ {
+ {
+ DocumentUndo::ScopedInsensitive _no_undo(doc);
+ // also save the extension for next use
+ store_file_extension_in_prefs (omod->get_id(), save_method);
+ // set the "dataloss" attribute if the chosen extension is lossy
+ repr->removeAttribute("inkscape:dataloss");
+ if (omod->causes_dataloss()) {
+ repr->setAttribute("inkscape:dataloss", "true");
+ }
+ }
+ doc->setModifiedSinceSave(false);
+ }
+
+ try {
+ omod->save(doc, fileName);
+ }
+ catch(...) {
+ // revert attributes in case of official and overwrite
+ if(check_overwrite && official) {
+ {
+ DocumentUndo::ScopedInsensitive _no_undo(doc);
+ store_file_extension_in_prefs (saved_output_extension, save_method);
+ repr->setAttribute("inkscape:dataloss", saved_dataloss);
+ }
+ doc->changeFilenameAndHrefs(saved_filename);
+ }
+ doc->setModifiedSinceSave(saved_modified);
+ // free used resources
+ g_free(saved_output_extension);
+ g_free(saved_dataloss);
+ g_free(saved_filename);
+
+ g_free(fileName);
+
+ throw;
+ }
+
+ // If it is an unofficial save, set the modified attributes back to what they were.
+ if ( !official) {
+ {
+ DocumentUndo::ScopedInsensitive _no_undo(doc);
+ store_file_extension_in_prefs (saved_output_extension, save_method);
+ repr->setAttribute("inkscape:dataloss", saved_dataloss);
+ }
+ doc->setModifiedSinceSave(saved_modified);
+
+ g_free(saved_output_extension);
+ g_free(saved_dataloss);
+ }
+
+ g_free(fileName);
+ return;
+}
+
+/**
+ * \return none
+ * \brief This is the function that searches each module to see
+ * if it matches the filename for autodetection.
+ * \param in_plug The module to be tested
+ * \param in_data An array of pointers containing the filename, and
+ * the place to put a successfully found module.
+ *
+ * Basically this function only looks at output modules as it is part of the open function. If the
+ * module is an output module, it then starts to take it apart, and the data that is passed in.
+ * Because the data being passed in is in such a weird format, there are a few casts to make it
+ * easier to use. While it looks like a lot of local variables, they'll all get removed by the
+ * compiler.
+ *
+ * First thing that is checked is if the filename is shorter than the extension itself. There is
+ * no way for a match in that case. If it's long enough then there is a string compare of the end
+ * of the filename (for the length of the extension), and the extension itself. If this passes
+ * then the pointer passed in is set to the current module.
+ */
+static void
+save_internal(Extension *in_plug, gpointer in_data)
+{
+ auto omod = dynamic_cast<Output *>(in_plug);
+ if (omod && !omod->deactivated()) {
+ gpointer *parray = (gpointer *)in_data;
+ gchar const *filename = (gchar const *)parray[0];
+ Output **pomod = (Output **)parray[1];
+
+ // skip all the rest if we already found someone to save it
+ // since they're ordered by preference now.
+ if (!*pomod) {
+ gchar const *ext = omod->get_extension();
+
+ gchar *filenamelower = g_utf8_strdown(filename, -1);
+ gchar *extensionlower = g_utf8_strdown(ext, -1);
+
+ if (g_str_has_suffix(filenamelower, extensionlower)) {
+ *pomod = omod;
+ }
+
+ g_free(filenamelower);
+ g_free(extensionlower);
+ }
+ }
+
+ return;
+}
+
+Print *
+get_print(gchar const *key)
+{
+ return dynamic_cast<Print *>(db.get(key));
+}
+
+/**
+ * \return true if extension successfully parsed, false otherwise
+ * A true return value does not guarantee an extension was actually registered,
+ * but indicates no errors occurred while parsing the extension.
+ * \brief Creates a module from a Inkscape::XML::Document describing the module
+ * \param doc The XML description of the module
+ *
+ * This function basically has two segments. The first is that it goes through the Repr tree
+ * provided, and determines what kind of module this is, and what kind of implementation to use.
+ * All of these are then stored in two enums that are defined in this function. This makes it
+ * easier to add additional types (which will happen in the future, I'm sure).
+ *
+ * Second, there is case statements for these enums. The first one is the type of module. This is
+ * the one where the module is actually created. After that, then the implementation is applied to
+ * get the load and unload functions. If there is no implementation then these are not set. This
+ * case could apply to modules that are built in (like the SVG load/save functions).
+ */
+bool
+build_from_reprdoc(Inkscape::XML::Document *doc, Implementation::Implementation *in_imp, std::string* baseDir)
+{
+ ModuleImpType module_implementation_type = MODULE_UNKNOWN_IMP;
+ ModuleFuncType module_functional_type = MODULE_UNKNOWN_FUNC;
+
+ g_return_val_if_fail(doc != nullptr, false);
+
+ Inkscape::XML::Node *repr = doc->root();
+
+ if (strcmp(repr->name(), INKSCAPE_EXTENSION_NS "inkscape-extension")) {
+ g_warning("Extension definition started with <%s> instead of <" INKSCAPE_EXTENSION_NS "inkscape-extension>. Extension will not be created. See http://wiki.inkscape.org/wiki/index.php/Extensions for reference.\n", repr->name());
+ return false;
+ }
+
+ Inkscape::XML::Node *child_repr = repr->firstChild();
+ while (child_repr != nullptr) {
+ char const *element_name = child_repr->name();
+ /* printf("Child: %s\n", child_repr->name()); */
+ if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "input")) {
+ module_functional_type = MODULE_INPUT;
+ } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "output")) {
+ module_functional_type = MODULE_OUTPUT;
+ } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "effect")) {
+ module_functional_type = MODULE_FILTER;
+ } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "print")) {
+ module_functional_type = MODULE_PRINT;
+ } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "path-effect")) {
+ module_functional_type = MODULE_PATH_EFFECT;
+ } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "script")) {
+ module_implementation_type = MODULE_EXTENSION;
+ } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "xslt")) {
+ module_implementation_type = MODULE_XSLT;
+ } else if (!strcmp(element_name, INKSCAPE_EXTENSION_NS "plugin")) {
+ module_implementation_type = MODULE_PLUGIN;
+ }
+
+ //Inkscape::XML::Node *old_repr = child_repr;
+ child_repr = child_repr->next();
+ //Inkscape::GC::release(old_repr);
+ }
+
+ Implementation::Implementation *imp;
+ if (in_imp == nullptr) {
+ switch (module_implementation_type) {
+ case MODULE_EXTENSION: {
+ Implementation::Script *script = new Implementation::Script();
+ imp = static_cast<Implementation::Implementation *>(script);
+ break;
+ }
+ case MODULE_XSLT: {
+ Implementation::XSLT *xslt = new Implementation::XSLT();
+ imp = static_cast<Implementation::Implementation *>(xslt);
+ break;
+ }
+ case MODULE_PLUGIN: {
+ Inkscape::Extension::Loader loader = Inkscape::Extension::Loader();
+ if( baseDir != nullptr){
+ loader.set_base_directory ( *baseDir );
+ }
+ imp = loader.load_implementation(doc);
+ break;
+ }
+ default: {
+ imp = nullptr;
+ break;
+ }
+ }
+ } else {
+ imp = in_imp;
+ }
+
+ Extension *module = nullptr;
+ try {
+ switch (module_functional_type) {
+ case MODULE_INPUT: {
+ module = new Input(repr, imp, baseDir);
+ break;
+ }
+ case MODULE_OUTPUT: {
+ module = new Output(repr, imp, baseDir);
+ break;
+ }
+ case MODULE_FILTER: {
+ module = new Effect(repr, imp, baseDir);
+ break;
+ }
+ case MODULE_PRINT: {
+ module = new Print(repr, imp, baseDir);
+ break;
+ }
+ case MODULE_PATH_EFFECT: {
+ module = new PathEffect(repr, imp, baseDir);
+ break;
+ }
+ default: {
+ g_warning("Extension of unknown type!"); // TODO: Should not happen! Is this even useful?
+ module = new Extension(repr, imp, baseDir);
+ break;
+ }
+ }
+ } catch (const Extension::extension_no_id& e) {
+ g_warning("Building extension failed. Extension does not have a valid ID");
+ } catch (const Extension::extension_no_name& e) {
+ g_warning("Building extension failed. Extension does not have a valid name");
+ } catch (const Extension::extension_not_compatible& e) {
+ return true; // This is not an actual error; just silently ignore the extension
+ }
+
+ if (module) {
+ return true;
+ }
+
+ return false;
+}
+
+/**
+ * \brief This function creates a module from a filename of an
+ * XML description.
+ * \param filename The file holding the XML description of the module.
+ *
+ * This function calls build_from_reprdoc with using sp_repr_read_file to create the reprdoc.
+ */
+void
+build_from_file(gchar const *filename)
+{
+ std::string dir = Glib::path_get_dirname(filename);
+
+ Inkscape::XML::Document *doc = sp_repr_read_file(filename, INKSCAPE_EXTENSION_URI);
+ if (!doc) {
+ g_critical("Inkscape::Extension::build_from_file() - XML description loaded from '%s' not valid.", filename);
+ return;
+ }
+
+ if (!build_from_reprdoc(doc, nullptr, &dir)) {
+ g_warning("Inkscape::Extension::build_from_file() - Could not parse extension from '%s'.", filename);
+ }
+
+ Inkscape::GC::release(doc);
+}
+
+/**
+ * \brief This function creates a module from a buffer holding an
+ * XML description.
+ * \param buffer The buffer holding the XML description of the module.
+ *
+ * This function calls build_from_reprdoc with using sp_repr_read_mem to create the reprdoc. It
+ * finds the length of the buffer using strlen.
+ */
+void
+build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp)
+{
+ Inkscape::XML::Document *doc = sp_repr_read_mem(buffer, strlen(buffer), INKSCAPE_EXTENSION_URI);
+ if (!doc) {
+ g_critical("Inkscape::Extension::build_from_mem() - XML description loaded from memory buffer not valid.");
+ return;
+ }
+
+ if (!build_from_reprdoc(doc, in_imp, nullptr)) {
+ g_critical("Inkscape::Extension::build_from_mem() - Could not parse extension from memory buffer.");
+ }
+
+ Inkscape::GC::release(doc);
+}
+
+/*
+ * TODO: Is it guaranteed that the returned extension is valid? If so, we can remove the check for
+ * filename_extension in sp_file_save_dialog().
+ */
+Glib::ustring
+get_file_save_extension (Inkscape::Extension::FileSaveMethod method) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring extension;
+ switch (method) {
+ case FILE_SAVE_METHOD_SAVE_AS:
+ case FILE_SAVE_METHOD_TEMPORARY:
+ extension = prefs->getString("/dialogs/save_as/default");
+ break;
+ case FILE_SAVE_METHOD_SAVE_COPY:
+ extension = prefs->getString("/dialogs/save_copy/default");
+ break;
+ case FILE_SAVE_METHOD_INKSCAPE_SVG:
+ extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE;
+ break;
+ case FILE_SAVE_METHOD_EXPORT:
+ /// \todo no default extension set for Export? defaults to SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE is ok?
+ break;
+ }
+
+ if(extension.empty()) {
+ extension = SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE;
+ }
+
+ return extension;
+}
+
+Glib::ustring
+get_file_save_path (SPDocument *doc, FileSaveMethod method) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ Glib::ustring path;
+ bool use_current_dir = true;
+ switch (method) {
+ case FILE_SAVE_METHOD_SAVE_AS:
+ {
+ use_current_dir = prefs->getBool("/dialogs/save_as/use_current_dir", true);
+ if (doc->getDocumentFilename() && use_current_dir) {
+ path = Glib::path_get_dirname(doc->getDocumentFilename());
+ } else {
+ path = prefs->getString("/dialogs/save_as/path");
+ }
+ break;
+ }
+ case FILE_SAVE_METHOD_TEMPORARY:
+ path = prefs->getString("/dialogs/save_as/path");
+ break;
+ case FILE_SAVE_METHOD_SAVE_COPY:
+ use_current_dir = prefs->getBool("/dialogs/save_copy/use_current_dir", prefs->getBool("/dialogs/save_as/use_current_dir", true));
+ if (doc->getDocumentFilename() && use_current_dir) {
+ path = Glib::path_get_dirname(doc->getDocumentFilename());
+ } else {
+ path = prefs->getString("/dialogs/save_copy/path");
+ }
+ break;
+ case FILE_SAVE_METHOD_INKSCAPE_SVG:
+ if (doc->getDocumentFilename()) {
+ path = Glib::path_get_dirname(doc->getDocumentFilename());
+ } else {
+ // FIXME: should we use the save_as path here or something else? Maybe we should
+ // leave this as a choice to the user.
+ path = prefs->getString("/dialogs/save_as/path");
+ }
+ break;
+ case FILE_SAVE_METHOD_EXPORT:
+ /// \todo no default path set for Export?
+ // defaults to g_get_home_dir()
+ break;
+ }
+
+ if(path.empty()) {
+ path = g_get_home_dir(); // Is this the most sensible solution? Note that we should avoid
+ // g_get_current_dir because this leads to problems on OS X where
+ // Inkscape opens the dialog inside application bundle when it is
+ // invoked for the first teim.
+ }
+
+ return path;
+}
+
+void
+store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ switch (method) {
+ case FILE_SAVE_METHOD_SAVE_AS:
+ case FILE_SAVE_METHOD_TEMPORARY:
+ prefs->setString("/dialogs/save_as/default", extension);
+ break;
+ case FILE_SAVE_METHOD_SAVE_COPY:
+ prefs->setString("/dialogs/save_copy/default", extension);
+ break;
+ case FILE_SAVE_METHOD_INKSCAPE_SVG:
+ case FILE_SAVE_METHOD_EXPORT:
+ // do nothing
+ break;
+ }
+}
+
+void
+store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method) {
+ Inkscape::Preferences *prefs = Inkscape::Preferences::get();
+ switch (method) {
+ case FILE_SAVE_METHOD_SAVE_AS:
+ case FILE_SAVE_METHOD_TEMPORARY:
+ prefs->setString("/dialogs/save_as/path", path);
+ break;
+ case FILE_SAVE_METHOD_SAVE_COPY:
+ prefs->setString("/dialogs/save_copy/path", path);
+ break;
+ case FILE_SAVE_METHOD_INKSCAPE_SVG:
+ case FILE_SAVE_METHOD_EXPORT:
+ // do nothing
+ break;
+ }
+}
+
+} } /* namespace Inkscape::Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/system.h b/src/extension/system.h
new file mode 100644
index 0000000..937cdde
--- /dev/null
+++ b/src/extension/system.h
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * This is file is kind of the junk file. Basically everything that
+ * didn't fit in one of the other well defined areas, well, it's now
+ * here. Which is good in someways, but this file really needs some
+ * definition. Hopefully that will come ASAP.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2002-2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_SYSTEM_H__
+#define INKSCAPE_EXTENSION_SYSTEM_H__
+
+#include <glibmm/ustring.h>
+
+class SPDocument;
+
+namespace Inkscape {
+
+namespace Extension {
+class Extension;
+class Print;
+
+namespace Implementation {
+class Implementation;
+}
+
+/**
+ * Used to distinguish between the various invocations of the save dialogs (and thus to determine
+ * the file type and save path offered in the dialog)
+ */
+enum FileSaveMethod {
+ FILE_SAVE_METHOD_SAVE_AS,
+ FILE_SAVE_METHOD_SAVE_COPY,
+ FILE_SAVE_METHOD_EXPORT,
+ // Fallback for special cases (e.g., when saving a document for the first time or after saving
+ // it in a lossy format)
+ FILE_SAVE_METHOD_INKSCAPE_SVG,
+ // For saving temporary files; we return the same data as for FILE_SAVE_METHOD_SAVE_AS
+ FILE_SAVE_METHOD_TEMPORARY,
+};
+
+SPDocument *open(Extension *key, gchar const *filename);
+void save(Extension *key, SPDocument *doc, gchar const *filename,
+ bool check_overwrite, bool official,
+ Inkscape::Extension::FileSaveMethod save_method);
+Print *get_print(gchar const *key);
+void build_from_file(gchar const *filename);
+void build_from_mem(gchar const *buffer, Implementation::Implementation *in_imp);
+
+/**
+ * Determine the desired default file extension depending on the given file save method.
+ * The returned string is guaranteed to be non-empty.
+ *
+ * @param method the file save method of the dialog
+ * @return the corresponding default file extension
+ */
+Glib::ustring get_file_save_extension (FileSaveMethod method);
+
+/**
+ * Determine the desired default save path depending on the given FileSaveMethod.
+ * The returned string is guaranteed to be non-empty.
+ *
+ * @param method the file save method of the dialog
+ * @param doc the file's document
+ * @return the corresponding default save path
+ */
+Glib::ustring get_file_save_path (SPDocument *doc, FileSaveMethod method);
+
+/**
+ * Write the given file extension back to prefs so that it can be used later on.
+ *
+ * @param extension the file extension which should be written to prefs
+ * @param method the file save method of the dialog
+ */
+void store_file_extension_in_prefs (Glib::ustring extension, FileSaveMethod method);
+
+/**
+ * Write the given path back to prefs so that it can be used later on.
+ *
+ * @param path the path which should be written to prefs
+ * @param method the file save method of the dialog
+ */
+void store_save_path_in_prefs (Glib::ustring path, FileSaveMethod method);
+
+} } /* namespace Inkscape::Extension */
+
+#endif /* INKSCAPE_EXTENSION_SYSTEM_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/timer.cpp b/src/extension/timer.cpp
new file mode 100644
index 0000000..4533b0d
--- /dev/null
+++ b/src/extension/timer.cpp
@@ -0,0 +1,211 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Here is where the extensions can get timed on when they load and
+ * unload. All of the timing is done in here.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <glibmm/main.h>
+
+#include "extension.h"
+#include "timer.h"
+
+namespace Inkscape {
+namespace Extension {
+
+#define TIMER_SCALE_VALUE 20
+
+ExpirationTimer * ExpirationTimer::timer_list = nullptr;
+ExpirationTimer * ExpirationTimer::idle_start = nullptr;
+long ExpirationTimer::timeout = 240;
+bool ExpirationTimer::timer_started = false;
+
+/** \brief Create a new expiration timer
+ \param in_extension Which extension this timer is related to
+
+ This function creates the timer, and sets the time to the current
+ time, plus what ever the current timeout is. Also, if this is
+ the first timer extension, the timer is kicked off. This function
+ also sets up the circularly linked list of all the timers.
+*/
+ExpirationTimer::ExpirationTimer (Extension * in_extension):
+ locked(0),
+ extension(in_extension)
+{
+ /* Fix Me! */
+ if (timer_list == nullptr) {
+ next = this;
+ timer_list = this;
+ } else {
+ next = timer_list->next;
+ timer_list->next = this;
+ }
+
+ expiration = Glib::DateTime::create_now_utc().add_seconds(timeout);
+
+ if (!timer_started) {
+ Glib::signal_timeout().connect(sigc::ptr_fun(&timer_func), timeout * 1000 / TIMER_SCALE_VALUE);
+ timer_started = true;
+ }
+
+ return;
+}
+
+/** \brief Deletes a \c ExpirationTimer
+
+ The most complex thing that this function does is remove the timer
+ from the circularly linked list. If this is the only entry in the
+ list that is easy, otherwise all the entries must be found, and this
+ one removed from the list.
+*/
+ExpirationTimer::~ExpirationTimer()
+{
+ if (this != next) {
+ /* This will remove this entry from the circularly linked
+ list. */
+ ExpirationTimer * prev;
+ for (prev = timer_list;
+ prev->next != this;
+ prev = prev->next){};
+ prev->next = next;
+
+ if (idle_start == this)
+ idle_start = next;
+
+ /* This may occur more than you think, just because the guy
+ doing most of the deletions is the idle function, who tracks
+ where it is looking using the \c timer_list variable. */
+ if (timer_list == this)
+ timer_list = next;
+ } else {
+ /* If we're the only entry in the list, the list needs to go
+ to NULL */
+ timer_list = nullptr;
+ idle_start = nullptr;
+ }
+
+ return;
+}
+
+/** \brief Touches the timer to extend the length before it expires
+
+ Basically it adds more time to the timer. One thing that is kinda
+ tricky is that it adds half the time remaining back into the timer.
+ This allows for some extensions that are used regularly to having
+ extended expiration times. So, in the end, they stay loaded longer.
+ Extensions that are only used once will expire at a standard rate
+ set by \c timeout.
+*/
+void
+ExpirationTimer::touch ()
+{
+ auto const current = Glib::DateTime::create_now_utc();
+
+ auto time_left = expiration.difference(current);
+ if (time_left < 0) time_left = 0;
+ time_left /= 2;
+
+ expiration = current.add(time_left).add_seconds(timeout);
+ return;
+}
+
+/** \brief Check to see if the timer has expired
+
+ Checks the time against the current time.
+*/
+bool
+ExpirationTimer::expired () const
+{
+ if (locked > 0) return false;
+
+ auto const current = Glib::DateTime::create_now_utc();
+ return expiration.difference(current) < 0;
+}
+
+// int idle_cnt = 0;
+
+/** \brief This function goes in the idle loop to find expired extensions
+ \return Whether the function should be requeued or not
+
+ This function first insures that there is a timer list, and then checks
+ to see if the one on the top of the list has expired. If it has
+ expired it unloads the module. By unloading the module, the timer
+ gets deleted (happens in the unload function). If the list is
+ no empty, the function returns that it should be dequeued and sets
+ the \c timer_started variable so that the timer will be reissued when
+ a timer is added. If there is entries left, but the next one is
+ where this function started, then the timer is set up. The timer
+ will then re-add the idle loop function when it runs.
+*/
+bool
+ExpirationTimer::idle_func ()
+{
+ // std::cout << "Idle func pass: " << idle_cnt++ << " timer list: " << timer_list << std::endl;
+
+ /* see if this is the last */
+ if (timer_list == nullptr) {
+ timer_started = false;
+ return false;
+ }
+
+ /* evaluate current */
+ if (timer_list->expired()) {
+ timer_list->extension->set_state(Extension::STATE_UNLOADED);
+ }
+
+ /* see if this is the last */
+ if (timer_list == nullptr) {
+ timer_started = false;
+ return false;
+ }
+
+ if (timer_list->next == idle_start) {
+ /* if so, set up the timer and return FALSE */
+ /* Note: This may cause one to be missed on the evaluation if
+ the one before it expires and it is last in the list.
+ While this could be taken care of, it isn't worth the
+ complexity for this lazy removal that we're doing. It
+ should get picked up next time */
+ Glib::signal_timeout().connect(sigc::ptr_fun(&timer_func), timeout * 1000 / TIMER_SCALE_VALUE);
+ return false;
+ }
+
+ /* If nothing else, continue on */
+ timer_list = timer_list->next;
+ return true;
+}
+
+/** \brief A timer function to set up the idle function
+ \return Always false -- to disable the timer
+
+ This function sets up the idle loop when it runs. The idle loop is
+ the one that unloads all the extensions.
+*/
+bool
+ExpirationTimer::timer_func ()
+{
+ // std::cout << "Timer func" << std::endl;
+ idle_start = timer_list;
+ // idle_cnt = 0;
+ Glib::signal_idle().connect(sigc::ptr_fun(&idle_func));
+ return false;
+}
+
+}; }; /* namespace Inkscape, Extension */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/extension/timer.h b/src/extension/timer.h
new file mode 100644
index 0000000..ce9c495
--- /dev/null
+++ b/src/extension/timer.h
@@ -0,0 +1,75 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Here is where the extensions can get timed on when they load and
+ * unload. All of the timing is done in here.
+ *
+ * Authors:
+ * Ted Gould <ted@gould.cx>
+ *
+ * Copyright (C) 2004 Authors
+ *
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#ifndef INKSCAPE_EXTENSION_TIMER_H__
+#define INKSCAPE_EXTENSION_TIMER_H__
+
+#include <cstddef>
+#include <sigc++/sigc++.h>
+#include <glibmm/datetime.h>
+
+namespace Inkscape {
+namespace Extension {
+
+class Extension;
+
+class ExpirationTimer {
+ /** \brief Circularly linked list of all timers */
+ static ExpirationTimer * timer_list;
+ /** \brief Which timer was on top when we started the idle loop */
+ static ExpirationTimer * idle_start;
+ /** \brief What the current timeout is (in seconds) */
+ static long timeout;
+ /** \brief Has the timer been started? */
+ static bool timer_started;
+
+ /** \brief Is this extension locked from being unloaded? */
+ int locked;
+ /** \brief Next entry in the list */
+ ExpirationTimer * next;
+ /** \brief When this timer expires */
+ Glib::DateTime expiration;
+ /** \brief What extension this function relates to */
+ Extension * extension;
+
+ bool expired() const;
+
+ static bool idle_func ();
+ static bool timer_func ();
+
+public:
+ ExpirationTimer(Extension * in_extension);
+ virtual ~ExpirationTimer();
+
+ void touch ();
+ void lock () { locked++; };
+ void unlock () { locked--; };
+
+ /** \brief Set the timeout variable */
+ static void set_timeout (long in_seconds) { timeout = in_seconds; };
+};
+
+}; }; /* namespace Inkscape, Extension */
+
+#endif /* INKSCAPE_EXTENSION_TIMER_H__ */
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :